From 17fd3eb906876ca3eeffe743a652916b2138d578 Mon Sep 17 00:00:00 2001 From: Aaron de Mello Date: Wed, 10 Sep 2025 10:10:27 -0400 Subject: [PATCH] fix: resolve CJS build issue causing "TypeError: Nylas is not a constructor" error --- CHANGELOG.md | 5 ++ cjs-wrapper.d.ts | 14 ++++ cjs-wrapper.js | 13 ++++ examples/cjs-only/README.md | 78 ++++++++++++++++++++ examples/cjs-only/index.js | 82 +++++++++++++++++++++ examples/cjs-only/package-lock.json | 70 ++++++++++++++++++ examples/cjs-only/package.json | 16 ++++ examples/cjs-only/utils/logger.js | 46 ++++++++++++ examples/edge-environment/package-lock.json | 2 +- package.json | 11 ++- src/nylas.ts | 4 +- tsconfig.cjs.json | 3 + tsconfig.json | 2 +- 13 files changed, 340 insertions(+), 6 deletions(-) create mode 100644 cjs-wrapper.d.ts create mode 100644 cjs-wrapper.js create mode 100644 examples/cjs-only/README.md create mode 100644 examples/cjs-only/index.js create mode 100644 examples/cjs-only/package-lock.json create mode 100644 examples/cjs-only/package.json create mode 100644 examples/cjs-only/utils/logger.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b284d0..c2332692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ 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] + +### Fixed +- Broken CJS build outputs resulted in a "TypeError: Nylas is not a constructor" error + ## [7.13.0] - 2025-09-01 ### Added diff --git a/cjs-wrapper.d.ts b/cjs-wrapper.d.ts new file mode 100644 index 00000000..8ddae4bb --- /dev/null +++ b/cjs-wrapper.d.ts @@ -0,0 +1,14 @@ +/** + * TypeScript definitions for the CommonJS wrapper + * This provides proper typing for CommonJS imports + */ + +// Re-export all types from the main module +export * from './lib/types/models/index.js'; + +// Import the main Nylas class type +import Nylas from './lib/types/nylas.js'; + +// Export as both default and named for maximum compatibility +declare const nylasExport: typeof Nylas; +export = nylasExport; diff --git a/cjs-wrapper.js b/cjs-wrapper.js new file mode 100644 index 00000000..7179adcc --- /dev/null +++ b/cjs-wrapper.js @@ -0,0 +1,13 @@ +/** + * CommonJS wrapper for the Nylas SDK + * This file provides CommonJS compatibility by re-exporting the default export + * as the main module export while preserving named exports + */ + +const nylasModule = require('./lib/cjs/nylas.js'); + +// Export the default export as the main module export for CJS compatibility +module.exports = nylasModule.default; + +// Preserve all named exports +Object.assign(module.exports, nylasModule); diff --git a/examples/cjs-only/README.md b/examples/cjs-only/README.md new file mode 100644 index 00000000..f247f73b --- /dev/null +++ b/examples/cjs-only/README.md @@ -0,0 +1,78 @@ +# CommonJS-Only Nylas SDK Example + +This example demonstrates how to use the Nylas Node.js SDK in a pure CommonJS (CJS) environment without ES module syntax. + +## Purpose + +- Shows CommonJS `require()` syntax with the Nylas SDK +- Demonstrates environment variable handling in CommonJS +- Provides a simple messages listing example +- Serves as a reference for projects that must use CommonJS + +## Key Differences from ESM + +This example showcases the CommonJS equivalent of the ESM-only example: + +| ESM Syntax | CommonJS Syntax | +|------------|-----------------| +| `import Nylas from 'nylas'` | `const Nylas = require('nylas')` | +| `import dotenv from 'dotenv'` | `const dotenv = require('dotenv')` | +| `import path from 'node:path'` | `const path = require('path')` | +| `import.meta.dirname` | `__dirname` | +| `export { logger }` | `module.exports = { logger }` | + +## Setup + +1. **Install dependencies:** + ```bash + cd examples/cjs-only + npm install + ``` + +2. **Set up environment variables:** + - Copy `examples/.env.example` to `examples/.env` + - Fill in your `NYLAS_API_KEY` and `NYLAS_GRANT_ID` + +3. **Run the example:** + ```bash + npm start + # or + node index.js + ``` + +## Requirements + +- Node.js (any version - CommonJS is supported in all Node.js versions) +- Valid Nylas API credentials +- A grant with message access permissions + +## What This Example Does + +1. Loads environment variables using `dotenv` +2. Validates required API credentials +3. Initializes the Nylas client +4. Lists messages from the specified grant +5. Logs the results with proper error handling + +## File Structure + +``` +cjs-only/ +├── index.js # Main example file (CommonJS) +├── package.json # Package configuration (no "type": "module") +├── utils/ +│ └── logger.js # Logger utility (CommonJS exports) +└── README.md # This file +``` + +## Troubleshooting + +- **"NYLAS_API_KEY environment variable is not set"**: Make sure you've created the `.env` file in the `examples/` directory with your API key +- **"NYLAS_GRANT_ID environment variable is not set"**: Add your grant ID to the `.env` file +- **Module not found errors**: Run `npm install` to install dependencies +- **Permission errors**: Ensure your API key and grant have the necessary permissions to list messages + +## Related Examples + +- `../esm-only/` - ESM version of this same example +- `../messages/` - More comprehensive message handling examples diff --git a/examples/cjs-only/index.js b/examples/cjs-only/index.js new file mode 100644 index 00000000..379f6acc --- /dev/null +++ b/examples/cjs-only/index.js @@ -0,0 +1,82 @@ +/** + * CommonJS-Only Nylas SDK Example + * + * This example demonstrates how to use the Nylas Node.js SDK in a pure CommonJS + * (CJS) environment without ES module syntax. + * + * Purpose: + * - Shows CommonJS require() syntax with the Nylas SDK + * - Demonstrates environment variable handling in CommonJS + * - Provides a simple messages listing example + * + * Usage: + * 1. Copy the parent examples/.env.example to examples/.env + * 2. Fill in your NYLAS_API_KEY and NYLAS_GRANT_ID in the .env file + * 3. Run: node index.js (or npm start) + * + * Requirements: + * - Node.js with CommonJS support (any Node.js version) + * - Valid Nylas API credentials + * - A grant with message access permissions + */ + +const dotenv = require('dotenv'); +const path = require('path'); +const Nylas = require('nylas'); +const { logger, maskSecret } = require('./utils/logger.js'); + +// Load from parent directory since this example lives in a subdirectory +dotenv.config({ path: path.resolve(__dirname, '../.env') }); + +// Fail fast if credentials are missing to provide clear error messages +const apiKey = process.env.NYLAS_API_KEY || ''; +if (!apiKey) { + throw new Error('NYLAS_API_KEY environment variable is not set'); +} + +const grantId = process.env.NYLAS_GRANT_ID || ''; +if (!grantId) { + throw new Error('NYLAS_GRANT_ID environment variable is not set'); +} + +// Initialize the Nylas client +const nylas = new Nylas({ + apiKey, + apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com', +}); + +/** + * Main function to demonstrate basic Nylas SDK usage in CommonJS environment + */ +async function main() { + try { + logger.info('Listing messages...'); + + // Log runtime config for debugging without exposing sensitive data + logger.debug('Runtime config', { + apiKey: maskSecret(apiKey), + grantId, + apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com', + }); + + // Use basic list operation to verify SDK functionality and connectivity + const messages = await nylas.messages.list({ + identifier: grantId, + }); + + logger.success('Messages listed successfully'); + + // Extract only essential fields to avoid logging sensitive message content + logger.info( + 'Message subjects and ids', + messages.data.map((m) => ({ id: m.id, subject: m.subject })) + ); + } catch (error) { + logger.error('Failed to list messages'); + logger.debug('Error details', error); + // Exit with error code to indicate failure for automation/CI purposes + process.exit(1); + } +} + +main(); diff --git a/examples/cjs-only/package-lock.json b/examples/cjs-only/package-lock.json new file mode 100644 index 00000000..354d551e --- /dev/null +++ b/examples/cjs-only/package-lock.json @@ -0,0 +1,70 @@ +{ + "name": "cjs-only", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cjs-only", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^17.2.2", + "nylas": "file:../../" + } + }, + "../..": { + "name": "nylas", + "version": "7.13.0", + "license": "MIT", + "dependencies": { + "change-case": "^4.1.2", + "form-data-encoder": "^4.1.0", + "formdata-node": "^6.0.3", + "mime-types": "^2.1.35", + "node-fetch": "^3.3.2", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@babel/core": "^7.3.3", + "@types/jest": "^29.5.2", + "@types/mime-types": "^2.1.2", + "@types/node": "^22.15.21", + "@types/uuid": "^8.3.4", + "@typescript-eslint/eslint-plugin": "^2.25.0", + "@typescript-eslint/parser": "^2.25.0", + "eslint": "^5.14.0", + "eslint-config-prettier": "^4.0.0", + "eslint-plugin-custom-rules": "^0.0.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-prettier": "^3.0.1", + "jest": "^29.6.1", + "jest-fetch-mock": "^3.0.3", + "prettier": "^3.5.3", + "ts-jest": "^29.1.1", + "typedoc": "^0.28.4", + "typedoc-plugin-rename-defaults": "^0.7.3", + "typescript": "^5.8.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/nylas": { + "resolved": "../..", + "link": true + } + } +} diff --git a/examples/cjs-only/package.json b/examples/cjs-only/package.json new file mode 100644 index 00000000..df5f68d0 --- /dev/null +++ b/examples/cjs-only/package.json @@ -0,0 +1,16 @@ +{ + "name": "cjs-only", + "version": "1.0.0", + "description": "A CommonJS-only example of the Nylas NodeJS SDK usage", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node index.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "nylas": "file:../../", + "dotenv": "^17.2.2" + } +} diff --git a/examples/cjs-only/utils/logger.js b/examples/cjs-only/utils/logger.js new file mode 100644 index 00000000..c19796d6 --- /dev/null +++ b/examples/cjs-only/utils/logger.js @@ -0,0 +1,46 @@ +// Pretty logger for CommonJS +const COLORS = { + reset: '\x1b[0m', + dim: '\x1b[2m', + gray: '\x1b[90m', + bold: '\x1b[1m', + info: '\x1b[36m', + success: '\x1b[32m', + warn: '\x1b[33m', + error: '\x1b[31m', + debug: '\x1b[35m', +}; + +function ts() { + return new Date().toISOString(); +} + +function maskSecret(value, visibleStart = 8, visibleEnd = 0) { + if (!value) return ''; + const start = value.slice(0, visibleStart); + const end = visibleEnd ? value.slice(-visibleEnd) : ''; + const hidden = Math.max(0, value.length - start.length - end.length); + return `${start}${'•'.repeat(hidden)}${end}`; +} + +function print(level, symbol, message, meta) { + const color = COLORS[level] || COLORS.info; + const time = `${COLORS.dim}${ts()}${COLORS.reset}`; + const label = `${color}${symbol} ${level.toUpperCase()}${COLORS.reset}`; + const line = + typeof message === 'string' ? message : JSON.stringify(message, null, 2); + console.log(`${time} ${label} ${line}`); + if (meta !== undefined) { + console.dir(meta, { depth: null, colors: true, maxArrayLength: 100 }); + } +} + +const logger = { + info: (msg, meta) => print('info', 'ℹ', msg, meta), + success: (msg, meta) => print('success', '✔', msg, meta), + warn: (msg, meta) => print('warn', '⚠', msg, meta), + error: (msg, meta) => print('error', '✖', msg, meta), + debug: (msg, meta) => print('debug', '🐛', msg, meta), +}; + +module.exports = { logger, maskSecret }; diff --git a/examples/edge-environment/package-lock.json b/examples/edge-environment/package-lock.json index f509a049..79c343a6 100644 --- a/examples/edge-environment/package-lock.json +++ b/examples/edge-environment/package-lock.json @@ -21,7 +21,7 @@ }, "../..": { "name": "nylas", - "version": "7.11.0", + "version": "7.13.0", "license": "MIT", "dependencies": { "change-case": "^4.1.2", diff --git a/package.json b/package.json index 9b169fe6..d2625507 100644 --- a/package.json +++ b/package.json @@ -72,8 +72,13 @@ "url": "https://github.com/nylas/nylas-nodejs.git" }, "exports": { - "import": "./lib/esm/nylas.js", - "require": "./lib/cjs/nylas.js", - "types": "./lib/types/nylas.d.ts" + "import": { + "types": "./lib/types/nylas.d.ts", + "default": "./lib/esm/nylas.js" + }, + "require": { + "types": "./cjs-wrapper.d.ts", + "default": "./cjs-wrapper.js" + } } } diff --git a/src/nylas.ts b/src/nylas.ts index 2c94c76a..ef1dac5e 100644 --- a/src/nylas.ts +++ b/src/nylas.ts @@ -24,7 +24,7 @@ import { Notetakers } from './resources/notetakers.js'; * A Nylas instance holds a configured http client pointing to a base URL and is intended to be reused and shared * across threads and time. */ -export default class Nylas { +class Nylas { /** * Access the Applications API */ @@ -122,3 +122,5 @@ export default class Nylas { return this; } } + +export default Nylas; diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index 7b7a07c2..2ee5ebc7 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -3,5 +3,8 @@ "compilerOptions": { "module": "commonjs", "outDir": "lib/cjs", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "moduleResolution": "node" }, } diff --git a/tsconfig.json b/tsconfig.json index 125bc2e0..d1799ecb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "target": "es2021", "declaration": true, "declarationDir": "lib/types", - "esModuleInterop": false, + "esModuleInterop": true, "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "moduleResolution": "node",