diff --git a/CHANGELOG.md b/CHANGELOG.md
index cbd3b9af..4c2eb23b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,16 @@ 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]
+
+### Changed
+- Upgraded node-fetch from v2 to v3 for better ESM support and compatibility with edge environments
+
+### Fixed
+- Fixed test expectations for file attachment sizes and content types to match actual implementation behavior
+- Updated Jest configuration to properly handle ESM modules from node-fetch v3
+- Removed incompatible AbortSignal import from node-fetch externals (now uses native Node.js AbortSignal)
+
## [7.11.0] - 2025-06-23
### Added
diff --git a/examples/edge-environment/.gitignore b/examples/edge-environment/.gitignore
new file mode 100644
index 00000000..64ead2b7
--- /dev/null
+++ b/examples/edge-environment/.gitignore
@@ -0,0 +1,53 @@
+# Dependencies
+node_modules/
+
+# Wrangler output
+dist/
+.wrangler/
+
+# Environment variables (keep .dev.vars.example but not .dev.vars)
+.dev.vars
+
+# Logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Coverage directory used by tools like istanbul
+coverage/
+
+# nyc test coverage
+.nyc_output
+
+# Dependency directories
+jspm_packages/
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# macOS
+.DS_Store
+
+# Windows
+Thumbs.db
+ehthumbs.db
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# vars
+.*.vars
\ No newline at end of file
diff --git a/examples/edge-environment/README.md b/examples/edge-environment/README.md
new file mode 100644
index 00000000..c11f36dd
--- /dev/null
+++ b/examples/edge-environment/README.md
@@ -0,0 +1,320 @@
+# Nylas Cloudflare Worker - Email Attachments Example
+
+This example demonstrates how to use the Nylas SDK in a Cloudflare Worker to send email attachments. It provides a simple web interface for uploading files and sending them as email attachments.
+
+## 🚀 Features
+
+- **Simple Web UI**: Drag & drop or click to upload files
+- **File Validation**: Automatic file size and type validation (10MB max)
+- **Real-time Feedback**: Loading states and success/error messages
+- **Email Sending**: Uses the Nylas SDK to send emails with attachments
+- **Edge Computing**: Runs on Cloudflare's global edge network
+- **Node.js Compatibility**: Uses `nodejs_compat` flag for seamless Node.js module support
+
+## 📋 Prerequisites
+
+Before getting started, you'll need:
+
+1. **Nylas Account**: [Sign up for free](https://dashboard.nylas.com/register)
+2. **Cloudflare Account**: [Sign up for free](https://dash.cloudflare.com/sign-up)
+3. **Node.js**: Version 16 or higher
+4. **Nylas Application**: Created in your Nylas Dashboard
+5. **Connected Email Account**: At least one email account connected to your Nylas application
+
+## 🛠️ Setup
+
+### 1. Install Dependencies
+
+```bash
+# Navigate to the edge-environment directory
+cd examples/edge-environment
+
+# Install dependencies
+npm install
+```
+
+### 2. Configure Environment Variables
+
+Copy the `.dev.vars` file and update it with your Nylas credentials:
+
+```bash
+# Update .dev.vars with your actual values
+NYLAS_API_KEY=your_nylas_api_key_here
+NYLAS_API_URI=https://api.us.nylas.com
+NYLAS_GRANT_ID=your_grant_id_here
+TEST_EMAIL=test@example.com
+```
+
+**How to get these values:**
+
+- **NYLAS_API_KEY**: Found in your [Nylas Dashboard](https://dashboard.nylas.com/applications) under your application settings
+- **NYLAS_GRANT_ID**: The ID of a connected email account (found in Dashboard > Grants)
+- **NYLAS_API_URI**: Use `https://api.us.nylas.com` for US region, or your specific region's API URL
+
+### 3. Update Wrangler Configuration
+
+Edit `wrangler.toml` and update the name and environment variables as needed:
+
+```toml
+name = "your-worker-name" # Choose a unique name for your worker
+
+[env.development.vars]
+NYLAS_API_KEY = "your_api_key"
+NYLAS_GRANT_ID = "your_grant_id"
+# ... other variables
+```
+
+## 🏗️ Development
+
+### Start Local Development Server
+
+```bash
+npm run dev
+```
+
+This will:
+- Start the Wrangler development server
+- Make your worker available at `http://localhost:8787`
+- Enable hot-reloading for code changes
+- Load environment variables from `.dev.vars`
+
+### Test the Application
+
+1. Open `http://localhost:8787` in your browser
+2. Fill in the email form:
+ - **Recipient Email**: Enter a valid email address
+ - **Subject**: Enter an email subject
+ - **Message**: Enter your message content
+ - **Attachment**: Upload a file (max 10MB)
+3. Click "Send Email with Attachment"
+4. Check the recipient's inbox for the email
+
+### Development Tips
+
+- **File Size Limit**: The worker is configured with a 10MB file size limit
+- **Supported Files**: All file types are supported (PDFs, images, documents, etc.)
+- **Error Handling**: Check the browser console and worker logs for debugging
+- **Hot Reloading**: Code changes will automatically reload the worker
+
+## 🚀 Deployment
+
+### Option 1: Deploy to Cloudflare Workers (Recommended)
+
+#### 1. Set Production Secrets
+
+For security, use Wrangler secrets instead of environment variables in production:
+
+```bash
+# Set your API key as a secret
+wrangler secret put NYLAS_API_KEY
+# Enter your API key when prompted
+
+# Set your Grant ID as a secret
+wrangler secret put NYLAS_GRANT_ID
+# Enter your Grant ID when prompted
+
+# Set default recipient email (optional)
+wrangler secret put TEST_EMAIL
+# Enter default email when prompted
+```
+
+#### 2. Deploy to Production
+
+```bash
+npm run deploy
+```
+
+This will:
+- Build and upload your worker to Cloudflare
+- Make it available at `https://your-worker-name.your-subdomain.workers.dev`
+- Use the production environment configuration
+
+#### 3. Test Production Deployment
+
+Visit your worker's URL and test the file upload functionality.
+
+### Option 2: Deploy with Custom Domain
+
+If you have a custom domain managed by Cloudflare:
+
+1. **Add a route in `wrangler.toml`:**
+```toml
+[[routes]]
+pattern = "attachments.yourdomain.com/*"
+zone_name = "yourdomain.com"
+```
+
+2. **Deploy with the route:**
+```bash
+wrangler deploy
+```
+
+## 📚 How It Works
+
+### Architecture
+
+```
+User Browser → Cloudflare Worker → Nylas API → Email Provider
+```
+
+1. **File Upload**: User uploads a file through the web interface
+2. **Form Processing**: Worker receives multipart form data
+3. **File Processing**: File is converted to Buffer for efficient binary transfer
+4. **Email Sending**: Nylas SDK processes and sends the email with attachment
+5. **Response**: User receives confirmation or error message
+
+### Key Files
+
+- **`src/worker.ts`**: Main worker logic and request handling
+- **`wrangler.toml`**: Cloudflare Worker configuration with Node.js compatibility
+- **`package.json`**: Dependencies and scripts
+- **`.dev.vars`**: Development environment variables
+
+### Technical Implementation
+
+This worker uses the **Nylas SDK** with optimizations for Cloudflare Workers:
+
+- **SDK Integration**: Uses the official Nylas Node.js SDK
+- **Buffer Attachments**: Uses native Buffer objects for efficient binary data handling
+- **Direct Binary Transfer**: No base64 encoding overhead (33% smaller than base64)
+- **Edge Optimized**: Designed specifically for Cloudflare Workers runtime
+
+### Node.js Compatibility
+
+This worker uses:
+- **`compatibility_date = "2024-09-23"`**: Enables automatic Node.js built-in module support
+- **`nodejs_compat` compatibility flag**: Additional Node.js compatibility features
+
+These settings enable Node.js built-in modules like `crypto`, `path`, `fs`, and `stream` that are required by the Nylas SDK. The Buffer attachment approach ensures optimal performance and compatibility with the Cloudflare Workers edge environment.
+
+### API Endpoints
+
+- **`GET /`**: Serves the HTML upload interface
+- **`POST /send-attachment`**: Handles file upload and email sending
+- **`OPTIONS /*`**: Handles CORS preflight requests
+
+## 🔧 Customization
+
+### Modify File Size Limits
+
+Update the file size limit in `src/worker.ts`:
+
+```typescript
+// Check file size (10MB limit)
+const maxSize = 10 * 1024 * 1024; // Change this value
+```
+
+### Use ReadableStream for Very Large Files
+
+For extremely large files (>25MB), you can use ReadableStream instead of Buffer:
+
+```typescript
+// Alternative: Use ReadableStream for very large files
+const stream = file.stream();
+const sendRequest: SendMessageRequest = {
+ // ... other fields
+ attachments: [{
+ filename: file.name,
+ contentType: getContentType(file.name),
+ content: stream, // Use stream instead of buffer
+ size: file.size,
+ }],
+};
+```
+
+### Customize Email Template
+
+Modify the email HTML template in `src/worker.ts`:
+
+```typescript
+body: `
+
+
Your Custom Email Template
+
${message}
+
+
+`,
+```
+
+### Add File Type Restrictions
+
+Add file type validation in the worker:
+
+```typescript
+// Validate file type
+const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
+if (!allowedTypes.includes(getContentType(file.name))) {
+ return new Response(
+ JSON.stringify({ error: 'File type not allowed' }),
+ { status: 400, headers: { 'Content-Type': 'application/json' } }
+ );
+}
+```
+
+## 🐛 Troubleshooting
+
+### Common Issues
+
+**Error: "Missing required environment variables"**
+- Ensure all environment variables are set in `.dev.vars` (development) or as secrets (production)
+- Check variable names match exactly
+
+**Error: "Could not resolve [module]" (crypto, path, fs, stream)**
+- Ensure `compatibility_date = "2024-09-23"` or later is set in `wrangler.toml`
+- Ensure `compatibility_flags = ["nodejs_compat"]` is also set
+- These settings enable Node.js built-in modules required by the Nylas SDK
+- The worker uses Buffer objects for efficient binary attachment handling
+
+**Error: "File size exceeds 10MB limit"**
+- Reduce file size or increase the limit in the worker code
+- Note: Cloudflare Workers have memory and CPU time limits
+
+**Error: "Invalid file upload"**
+- Ensure you're uploading a valid file
+- Check that the form is submitting properly
+
+**Error: "optionParams.form.getHeaders is not a function"**
+- This was an old SDK compatibility issue, now resolved
+- The worker uses Buffer attachments instead of problematic form-data
+- This error should not occur with the current implementation
+
+**Email not received**
+- Verify the recipient email address
+- Check spam/junk folders
+- Verify your Nylas grant has send permissions
+- Check Nylas Dashboard for any API errors
+
+### Debug Mode
+
+Add logging to the worker for debugging:
+
+```typescript
+console.log('File details:', {
+ name: file.name,
+ size: file.size,
+ type: file.type
+});
+```
+
+View logs with:
+```bash
+wrangler tail
+```
+
+## 📖 Related Examples
+
+- **Local Attachment Examples**: See `../messages/examples/` for Node.js examples
+- **Nylas SDK Documentation**: [https://developer.nylas.com](https://developer.nylas.com)
+- **Cloudflare Workers Docs**: [https://developers.cloudflare.com/workers/](https://developers.cloudflare.com/workers/)
+
+## 🤝 Contributing
+
+Found an issue or want to improve this example? Please:
+
+1. Check existing issues in the [Nylas Node.js SDK repository](https://github.com/nylas/nylas-nodejs)
+2. Create a new issue or pull request
+3. Follow the contributing guidelines
+
+## 📄 License
+
+This example is part of the Nylas Node.js SDK and is licensed under the MIT License.
\ No newline at end of file
diff --git a/examples/edge-environment/package-lock.json b/examples/edge-environment/package-lock.json
new file mode 100644
index 00000000..907ba6ae
--- /dev/null
+++ b/examples/edge-environment/package-lock.json
@@ -0,0 +1,1681 @@
+{
+ "name": "nylas-cloudflare-worker-attachments",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "nylas-cloudflare-worker-attachments",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^2.1.35",
+ "nylas": "file:../../"
+ },
+ "devDependencies": {
+ "@cloudflare/workers-types": "^4.20231218.0",
+ "@types/mime-types": "^2.1.4",
+ "typescript": "^5.3.0",
+ "wrangler": "^3.22.0"
+ }
+ },
+ "../..": {
+ "name": "nylas",
+ "version": "7.11.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/node-fetch": "^2.6.4",
+ "@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",
+ "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/@cloudflare/kv-asset-handler": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz",
+ "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==",
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "mime": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.13"
+ }
+ },
+ "node_modules/@cloudflare/unenv-preset": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz",
+ "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==",
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "peerDependencies": {
+ "unenv": "2.0.0-rc.14",
+ "workerd": "^1.20250124.0"
+ },
+ "peerDependenciesMeta": {
+ "workerd": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@cloudflare/workerd-darwin-64": {
+ "version": "1.20250408.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250408.0.tgz",
+ "integrity": "sha512-bxhIwBWxaNItZLXDNOKY2dCv0FHjDiDkfJFpwv4HvtvU5MKcrivZHVmmfDzLW85rqzfcDOmKbZeMPVfiKxdBZw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-darwin-arm64": {
+ "version": "1.20250408.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250408.0.tgz",
+ "integrity": "sha512-5XZ2Oykr8bSo7zBmERtHh18h5BZYC/6H1YFWVxEj3PtalF3+6SHsO4KZsbGvDml9Pu7sHV277jiZE5eny8Hlyw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-linux-64": {
+ "version": "1.20250408.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250408.0.tgz",
+ "integrity": "sha512-WbgItXWln6G5d7GvYLWcuOzAVwafysZaWunH3UEfsm95wPuRofpYnlDD861gdWJX10IHSVgMStGESUcs7FLerQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-linux-arm64": {
+ "version": "1.20250408.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250408.0.tgz",
+ "integrity": "sha512-pAhEywPPvr92SLylnQfZEPgXz+9pOG9G9haAPLpEatncZwYiYd9yiR6HYWhKp2erzCoNrOqKg9IlQwU3z1IDiw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-windows-64": {
+ "version": "1.20250408.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250408.0.tgz",
+ "integrity": "sha512-nJ3RjMKGae2aF2rZ/CNeBvQPM+W5V1SUK0FYWG/uomyr7uQ2l4IayHna1ODg/OHHTEgIjwom0Mbn58iXb0WOcQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workers-types": {
+ "version": "4.20250718.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250718.0.tgz",
+ "integrity": "sha512-RpYLgb81veUGtlLQINwGldsXQDcaK2/Z6QGeSq88yyd9o4tZYw7dzMu34sHgoCeb0QiPQWtetXiPf99PrIj+YQ==",
+ "dev": true,
+ "license": "MIT OR Apache-2.0"
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
+ "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild-plugins/node-globals-polyfill": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz",
+ "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==",
+ "dev": true,
+ "license": "ISC",
+ "peerDependencies": {
+ "esbuild": "*"
+ }
+ },
+ "node_modules/@esbuild-plugins/node-modules-polyfill": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz",
+ "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0",
+ "rollup-plugin-node-polyfills": "^0.2.1"
+ },
+ "peerDependencies": {
+ "esbuild": "*"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
+ "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
+ "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
+ "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
+ "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
+ "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
+ "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
+ "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
+ "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
+ "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
+ "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
+ "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
+ "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
+ "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
+ "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
+ "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
+ "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
+ "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
+ "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
+ "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
+ "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
+ "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
+ "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@fastify/busboy": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
+ "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
+ "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
+ "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
+ "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
+ "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
+ "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
+ "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
+ "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
+ "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
+ "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
+ "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
+ "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.0.5"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
+ "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
+ "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
+ "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
+ "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
+ "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
+ "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
+ "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
+ "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@types/mime-types": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
+ "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/acorn": {
+ "version": "8.14.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
+ "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/as-table": {
+ "version": "1.0.55",
+ "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz",
+ "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "printable-characters": "^1.0.42"
+ }
+ },
+ "node_modules/blake3-wasm": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",
+ "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/data-uri-to-buffer": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz",
+ "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/defu": {
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
+ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
+ "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.17.19",
+ "@esbuild/android-arm64": "0.17.19",
+ "@esbuild/android-x64": "0.17.19",
+ "@esbuild/darwin-arm64": "0.17.19",
+ "@esbuild/darwin-x64": "0.17.19",
+ "@esbuild/freebsd-arm64": "0.17.19",
+ "@esbuild/freebsd-x64": "0.17.19",
+ "@esbuild/linux-arm": "0.17.19",
+ "@esbuild/linux-arm64": "0.17.19",
+ "@esbuild/linux-ia32": "0.17.19",
+ "@esbuild/linux-loong64": "0.17.19",
+ "@esbuild/linux-mips64el": "0.17.19",
+ "@esbuild/linux-ppc64": "0.17.19",
+ "@esbuild/linux-riscv64": "0.17.19",
+ "@esbuild/linux-s390x": "0.17.19",
+ "@esbuild/linux-x64": "0.17.19",
+ "@esbuild/netbsd-x64": "0.17.19",
+ "@esbuild/openbsd-x64": "0.17.19",
+ "@esbuild/sunos-x64": "0.17.19",
+ "@esbuild/win32-arm64": "0.17.19",
+ "@esbuild/win32-ia32": "0.17.19",
+ "@esbuild/win32-x64": "0.17.19"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
+ "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/exit-hook": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz",
+ "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
+ "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/get-source": {
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz",
+ "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==",
+ "dev": true,
+ "license": "Unlicense",
+ "dependencies": {
+ "data-uri-to-buffer": "^2.0.0",
+ "source-map": "^0.6.1"
+ }
+ },
+ "node_modules/glob-to-regexp": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "node_modules/mime": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/miniflare": {
+ "version": "3.20250408.2",
+ "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250408.2.tgz",
+ "integrity": "sha512-uTs7cGWFErgJTKtBdmtctwhuoxniuCQqDT8+xaEiJdEC8d+HsaZVYfZwIX2NuSmdAiHMe7NtbdZYjFMbIXtJsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "0.8.1",
+ "acorn": "8.14.0",
+ "acorn-walk": "8.3.2",
+ "exit-hook": "2.2.1",
+ "glob-to-regexp": "0.4.1",
+ "stoppable": "1.1.0",
+ "undici": "^5.28.5",
+ "workerd": "1.20250408.0",
+ "ws": "8.18.0",
+ "youch": "3.3.4",
+ "zod": "3.22.3"
+ },
+ "bin": {
+ "miniflare": "bootstrap.js"
+ },
+ "engines": {
+ "node": ">=16.13"
+ }
+ },
+ "node_modules/mustache": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
+ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mustache": "bin/mustache"
+ }
+ },
+ "node_modules/nylas": {
+ "resolved": "../..",
+ "link": true
+ },
+ "node_modules/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/printable-characters": {
+ "version": "1.0.42",
+ "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz",
+ "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==",
+ "dev": true,
+ "license": "Unlicense"
+ },
+ "node_modules/rollup-plugin-inject": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz",
+ "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==",
+ "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "estree-walker": "^0.6.1",
+ "magic-string": "^0.25.3",
+ "rollup-pluginutils": "^2.8.1"
+ }
+ },
+ "node_modules/rollup-plugin-node-polyfills": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz",
+ "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "rollup-plugin-inject": "^3.0.0"
+ }
+ },
+ "node_modules/rollup-pluginutils": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
+ "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "estree-walker": "^0.6.1"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
+ "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "color": "^4.2.3",
+ "detect-libc": "^2.0.3",
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.33.5",
+ "@img/sharp-darwin-x64": "0.33.5",
+ "@img/sharp-libvips-darwin-arm64": "1.0.4",
+ "@img/sharp-libvips-darwin-x64": "1.0.4",
+ "@img/sharp-libvips-linux-arm": "1.0.5",
+ "@img/sharp-libvips-linux-arm64": "1.0.4",
+ "@img/sharp-libvips-linux-s390x": "1.0.4",
+ "@img/sharp-libvips-linux-x64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
+ "@img/sharp-linux-arm": "0.33.5",
+ "@img/sharp-linux-arm64": "0.33.5",
+ "@img/sharp-linux-s390x": "0.33.5",
+ "@img/sharp-linux-x64": "0.33.5",
+ "@img/sharp-linuxmusl-arm64": "0.33.5",
+ "@img/sharp-linuxmusl-x64": "0.33.5",
+ "@img/sharp-wasm32": "0.33.5",
+ "@img/sharp-win32-ia32": "0.33.5",
+ "@img/sharp-win32-x64": "0.33.5"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "deprecated": "Please use @jridgewell/sourcemap-codec instead",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stacktracey": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz",
+ "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==",
+ "dev": true,
+ "license": "Unlicense",
+ "dependencies": {
+ "as-table": "^1.0.36",
+ "get-source": "^2.0.12"
+ }
+ },
+ "node_modules/stoppable": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
+ "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
+ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici": {
+ "version": "5.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
+ "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@fastify/busboy": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.0"
+ }
+ },
+ "node_modules/unenv": {
+ "version": "2.0.0-rc.14",
+ "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.14.tgz",
+ "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defu": "^6.1.4",
+ "exsolve": "^1.0.1",
+ "ohash": "^2.0.10",
+ "pathe": "^2.0.3",
+ "ufo": "^1.5.4"
+ }
+ },
+ "node_modules/workerd": {
+ "version": "1.20250408.0",
+ "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250408.0.tgz",
+ "integrity": "sha512-bBUX+UsvpzAqiWFNeZrlZmDGddiGZdBBbftZJz2wE6iUg/cIAJeVQYTtS/3ahaicguoLBz4nJiDo8luqM9fx1A==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "workerd": "bin/workerd"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "optionalDependencies": {
+ "@cloudflare/workerd-darwin-64": "1.20250408.0",
+ "@cloudflare/workerd-darwin-arm64": "1.20250408.0",
+ "@cloudflare/workerd-linux-64": "1.20250408.0",
+ "@cloudflare/workerd-linux-arm64": "1.20250408.0",
+ "@cloudflare/workerd-windows-64": "1.20250408.0"
+ }
+ },
+ "node_modules/wrangler": {
+ "version": "3.114.11",
+ "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.114.11.tgz",
+ "integrity": "sha512-g0KhNj0AzlDXrW/XNzOrbfjBnDHbmf3a+3+UR67BbKnS8EUC9yTPrKCRymERdFKfiSvlPs34tHGAzOTw62Jb4g==",
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "@cloudflare/kv-asset-handler": "0.3.4",
+ "@cloudflare/unenv-preset": "2.0.2",
+ "@esbuild-plugins/node-globals-polyfill": "0.2.3",
+ "@esbuild-plugins/node-modules-polyfill": "0.2.2",
+ "blake3-wasm": "2.1.5",
+ "esbuild": "0.17.19",
+ "miniflare": "3.20250408.2",
+ "path-to-regexp": "6.3.0",
+ "unenv": "2.0.0-rc.14",
+ "workerd": "1.20250408.0"
+ },
+ "bin": {
+ "wrangler": "bin/wrangler.js",
+ "wrangler2": "bin/wrangler.js"
+ },
+ "engines": {
+ "node": ">=16.17.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2",
+ "sharp": "^0.33.5"
+ },
+ "peerDependencies": {
+ "@cloudflare/workers-types": "^4.20250408.0"
+ },
+ "peerDependenciesMeta": {
+ "@cloudflare/workers-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/youch": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz",
+ "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^0.7.1",
+ "mustache": "^4.2.0",
+ "stacktracey": "^2.1.8"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.22.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
+ "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/examples/edge-environment/package.json b/examples/edge-environment/package.json
new file mode 100644
index 00000000..7955f674
--- /dev/null
+++ b/examples/edge-environment/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "nylas-cloudflare-worker-attachments",
+ "version": "1.0.0",
+ "description": "Cloudflare Worker example for sending email attachments using Nylas SDK",
+ "main": "src/worker.ts",
+ "scripts": {
+ "dev": "wrangler dev",
+ "deploy": "wrangler deploy",
+ "build": "tsc && wrangler deploy --dry-run",
+ "type-check": "tsc --noEmit"
+ },
+ "keywords": ["cloudflare", "worker", "nylas", "email", "attachments"],
+ "author": "Nylas",
+ "license": "MIT",
+ "devDependencies": {
+ "@cloudflare/workers-types": "^4.20231218.0",
+ "@types/mime-types": "^2.1.4",
+ "typescript": "^5.3.0",
+ "wrangler": "^3.22.0"
+ },
+ "dependencies": {
+ "nylas": "file:../../",
+ "mime-types": "^2.1.35"
+ }
+}
\ No newline at end of file
diff --git a/examples/edge-environment/setup.sh b/examples/edge-environment/setup.sh
new file mode 100755
index 00000000..21f007e2
--- /dev/null
+++ b/examples/edge-environment/setup.sh
@@ -0,0 +1,113 @@
+#!/bin/bash
+
+# Nylas Cloudflare Worker Setup Script
+# This script helps you set up the environment for the Nylas attachment worker
+
+echo "🚀 Nylas Cloudflare Worker Setup"
+echo "=================================="
+echo ""
+
+# Check if required tools are installed
+echo "📋 Checking prerequisites..."
+
+# Check Node.js
+if ! command -v node &> /dev/null; then
+ echo "❌ Node.js is not installed. Please install Node.js 16+ and try again."
+ exit 1
+fi
+
+NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
+if [ "$NODE_VERSION" -lt 16 ]; then
+ echo "❌ Node.js version $NODE_VERSION is too old. Please install Node.js 16+ and try again."
+ exit 1
+fi
+
+echo "✅ Node.js $(node -v) is installed"
+
+# Check npm
+if ! command -v npm &> /dev/null; then
+ echo "❌ npm is not installed. Please install npm and try again."
+ exit 1
+fi
+
+echo "✅ npm $(npm -v) is installed"
+
+# Install dependencies if not already installed
+if [ ! -d "node_modules" ]; then
+ echo ""
+ echo "📦 Installing dependencies..."
+ npm install
+ if [ $? -ne 0 ]; then
+ echo "❌ Failed to install dependencies"
+ exit 1
+ fi
+ echo "✅ Dependencies installed successfully"
+else
+ echo "✅ Dependencies already installed"
+fi
+
+# Check if wrangler is available
+if ! command -v npx wrangler &> /dev/null; then
+ echo "❌ Wrangler is not available. Dependencies may not be installed correctly."
+ exit 1
+fi
+
+echo "✅ Wrangler is available"
+
+echo ""
+echo "🔧 Configuration Setup"
+echo "======================"
+
+# Check if .dev.vars exists and has the required variables
+if [ ! -f ".dev.vars" ]; then
+ echo "❌ .dev.vars file not found"
+ echo ""
+ echo "Please create a .dev.vars file with your Nylas credentials:"
+ echo ""
+ cat << EOF
+NYLAS_API_KEY=your_nylas_api_key_here
+NYLAS_API_URI=https://api.us.nylas.com
+NYLAS_GRANT_ID=your_grant_id_here
+TEST_EMAIL=test@example.com
+EOF
+ echo ""
+ exit 1
+fi
+
+echo "✅ .dev.vars file found"
+
+# Check if the required environment variables are set (not just empty)
+if grep -q "your_nylas_api_key_here" .dev.vars || grep -q "your_grant_id_here" .dev.vars; then
+ echo "⚠️ WARNING: .dev.vars still contains placeholder values"
+ echo ""
+ echo "Please update .dev.vars with your actual Nylas credentials:"
+ echo "- NYLAS_API_KEY: Get this from your Nylas Dashboard"
+ echo "- NYLAS_GRANT_ID: Get this from your connected accounts"
+ echo ""
+ echo "You can find these values at: https://dashboard.nylas.com"
+ echo ""
+fi
+
+# Check wrangler.toml
+if [ ! -f "wrangler.toml" ]; then
+ echo "❌ wrangler.toml not found"
+ exit 1
+fi
+
+echo "✅ wrangler.toml found"
+
+echo ""
+echo "🎉 Setup Complete!"
+echo "=================="
+echo ""
+echo "Next steps:"
+echo "1. Update .dev.vars with your actual Nylas credentials"
+echo "2. Run 'npm run dev' to start the development server"
+echo "3. Open http://localhost:8787 in your browser"
+echo "4. Test uploading a file and sending an email"
+echo ""
+echo "For deployment:"
+echo "1. Set production secrets: wrangler secret put NYLAS_API_KEY"
+echo "2. Deploy: npm run deploy"
+echo ""
+echo "Need help? Check the README.md for detailed instructions."
\ No newline at end of file
diff --git a/examples/edge-environment/src/worker.ts b/examples/edge-environment/src/worker.ts
new file mode 100644
index 00000000..d2f8dc04
--- /dev/null
+++ b/examples/edge-environment/src/worker.ts
@@ -0,0 +1,443 @@
+
+import Nylas, { SendMessageRequest } from 'nylas';
+import * as mimeTypes from 'mime-types';
+
+// Define the environment interface for TypeScript
+interface Env {
+ NYLAS_API_KEY: string;
+ NYLAS_API_URI: string;
+ NYLAS_GRANT_ID: string;
+ TEST_EMAIL: string;
+}
+
+// Simple HTML interface for file upload
+const HTML_INTERFACE = `
+
+
+
+
+
+ Nylas Attachment Sender
+
+
+
+
+
📧 Nylas Attachment Sender
+
+ Upload a file and send it as an email attachment using the Nylas SDK in a Cloudflare Worker
+
+
+
+
+
+
+
+
+
+
+`;
+
+// Helper function to parse multipart form data
+async function parseFormData(request: Request): Promise<{ [key: string]: any }> {
+ const formData = await request.formData();
+ const result: { [key: string]: any } = {};
+
+ for (const [key, value] of formData.entries()) {
+ result[key] = value;
+ }
+
+ return result;
+}
+
+// Helper function to get file content type
+function getContentType(filename: string): string {
+ return mimeTypes.lookup(filename) || 'application/octet-stream';
+}
+
+// Main request handler
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ const url = new URL(request.url);
+
+ // CORS headers for the response
+ const corsHeaders = {
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type',
+ };
+
+ // Handle CORS preflight requests
+ if (request.method === 'OPTIONS') {
+ return new Response(null, { headers: corsHeaders });
+ }
+
+ // Serve the HTML interface on GET requests
+ if (request.method === 'GET' && url.pathname === '/') {
+ return new Response(HTML_INTERFACE, {
+ headers: {
+ 'Content-Type': 'text/html',
+ ...corsHeaders,
+ },
+ });
+ }
+
+ // Handle file upload and email sending on POST requests
+ if (request.method === 'POST' && url.pathname === '/send-attachment') {
+ try {
+ // Validate environment variables
+ if (!env.NYLAS_API_KEY || !env.NYLAS_GRANT_ID) {
+ return new Response(
+ JSON.stringify({ error: 'Missing required environment variables (NYLAS_API_KEY, NYLAS_GRANT_ID)' }),
+ {
+ status: 500,
+ headers: { 'Content-Type': 'application/json', ...corsHeaders },
+ }
+ );
+ }
+
+ // Parse form data
+ const formData = await parseFormData(request);
+ const recipientEmail = formData.recipientEmail as string;
+ const subject = formData.subject as string;
+ const message = formData.message as string;
+ const file = formData.file;
+
+ // Validate that file is actually a File object
+ if (typeof file === 'string' || !file) {
+ return new Response(
+ JSON.stringify({ error: 'Invalid file upload' }),
+ {
+ status: 400,
+ headers: { 'Content-Type': 'application/json', ...corsHeaders },
+ }
+ );
+ }
+
+ // Validate required fields
+ if (!recipientEmail || !subject || !message || !file) {
+ return new Response(
+ JSON.stringify({ error: 'Missing required fields' }),
+ {
+ status: 400,
+ headers: { 'Content-Type': 'application/json', ...corsHeaders },
+ }
+ );
+ }
+
+ // Check file size (10MB limit)
+ const maxSize = 10 * 1024 * 1024; // 10MB
+ if (file.size > maxSize) {
+ return new Response(
+ JSON.stringify({ error: 'File size exceeds 10MB limit' }),
+ {
+ status: 400,
+ headers: { 'Content-Type': 'application/json', ...corsHeaders },
+ }
+ );
+ }
+
+ // Initialize Nylas client
+ const nylas = new Nylas({
+ apiKey: env.NYLAS_API_KEY,
+ apiUri: env.NYLAS_API_URI || 'https://api.us.nylas.com',
+ });
+
+ // Convert file to Buffer for SDK
+ const fileBuffer = await file.arrayBuffer();
+ const buffer = Buffer.from(fileBuffer);
+
+ // Prepare the email request using the SDK
+ const sendRequest: SendMessageRequest = {
+ to: [{ email: recipientEmail }],
+ subject: subject,
+ body: `
+
+
Email from Cloudflare Worker
+
${message.replace(/\n/g, '
')}
+
+
+ This email was sent from a Cloudflare Worker using the Nylas SDK.
+ Attachment: ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)
+
+
+ `,
+ attachments: [
+ {
+ filename: file.name,
+ contentType: getContentType(file.name),
+ content: buffer,
+ size: file.size,
+ },
+ ],
+ };
+
+ // Send the email using the Nylas SDK
+ const response = await nylas.messages.send({
+ identifier: env.NYLAS_GRANT_ID,
+ requestBody: sendRequest,
+ });
+
+ // Return success response
+ return new Response(
+ JSON.stringify({
+ success: true,
+ messageId: response.data.id,
+ message: 'Email sent successfully',
+ }),
+ {
+ headers: { 'Content-Type': 'application/json', ...corsHeaders },
+ }
+ );
+
+ } catch (error) {
+ console.error('Error sending email:', error);
+
+ return new Response(
+ JSON.stringify({
+ error: error instanceof Error ? error.message : 'Unknown error occurred',
+ }),
+ {
+ status: 500,
+ headers: { 'Content-Type': 'application/json', ...corsHeaders },
+ }
+ );
+ }
+ }
+
+ // 404 for other routes
+ return new Response('Not Found', {
+ status: 404,
+ headers: corsHeaders,
+ });
+ },
+};
\ No newline at end of file
diff --git a/examples/edge-environment/tsconfig.json b/examples/edge-environment/tsconfig.json
new file mode 100644
index 00000000..f92d08de
--- /dev/null
+++ b/examples/edge-environment/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "types": ["@cloudflare/workers-types"],
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "outDir": "dist",
+ "rootDir": "src"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
\ No newline at end of file
diff --git a/examples/edge-environment/wrangler.toml b/examples/edge-environment/wrangler.toml
new file mode 100644
index 00000000..c52714b2
--- /dev/null
+++ b/examples/edge-environment/wrangler.toml
@@ -0,0 +1,24 @@
+name = "nylas-attachment-worker"
+main = "src/worker.ts"
+compatibility_date = "2024-09-23"
+compatibility_flags = ["nodejs_compat"]
+
+# Environment variables for development
+[env.development.vars]
+NYLAS_API_KEY = ""
+NYLAS_API_URI = "https://api.us.nylas.com"
+NYLAS_GRANT_ID = ""
+TEST_EMAIL = ""
+
+# Environment variables for production
+[env.production.vars]
+NYLAS_API_URI = "https://api.us.nylas.com"
+
+# For production, use wrangler secret to set these:
+# wrangler secret put NYLAS_API_KEY
+# wrangler secret put NYLAS_GRANT_ID
+# wrangler secret put TEST_EMAIL
+
+# File size limits (10MB max for attachments)
+[limits]
+cpu_ms = 10000
\ No newline at end of file
diff --git a/examples/folders/folders.ts b/examples/folders/folders.ts
index 4e006f0b..07bf2bff 100644
--- a/examples/folders/folders.ts
+++ b/examples/folders/folders.ts
@@ -86,222 +86,3 @@ if (!GRANT_ID) {
} else {
listFolders().catch(console.error);
}
-=======
-import dotenv from 'dotenv';
-import path from 'path';
-import * as process from 'process';
-import Nylas, {
- Folder,
- NylasResponse,
- NylasListResponse,
- NylasApiError,
- ListFolderQueryParams
-} from 'nylas';
-
-// Load environment variables from .env file
-dotenv.config({ path: path.resolve(__dirname, '../.env') });
-
-const NYLAS_API_KEY = process.env.NYLAS_API_KEY;
-const NYLAS_GRANT_ID = process.env.NYLAS_GRANT_ID;
-
-if (!NYLAS_API_KEY) {
- console.error('NYLAS_API_KEY is required. Please add it to your .env file.');
- process.exit(1);
-}
-
-if (!NYLAS_GRANT_ID) {
- console.error('NYLAS_GRANT_ID is required. Please add it to your .env file.');
- process.exit(1);
-}
-
-async function listFoldersExample() {
- try {
- // Initialize Nylas client
- const nylas = new Nylas({
- apiKey: NYLAS_API_KEY!,
- apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com',
- });
-
- console.log('=== Nylas Folders API Demo ===\n');
-
- // 1. List all folders (default behavior - multi-level hierarchy)
- console.log('1. Listing all folders (multi-level hierarchy):');
- const allFolders: NylasListResponse = await nylas.folders.list({
- identifier: NYLAS_GRANT_ID!,
- queryParams: {}
- });
-
- console.log(`Found ${allFolders.data.length} folders total:`);
- allFolders.data.forEach((folder, index) => {
- console.log(` ${index + 1}. ${folder.name} (ID: ${folder.id})`);
- if (folder.parentId) {
- console.log(` └─ Parent ID: ${folder.parentId}`);
- }
- if (folder.childCount !== undefined) {
- console.log(` └─ Child Count: ${folder.childCount}`);
- }
- });
- console.log();
-
- // 2. List folders with single-level hierarchy (Microsoft only)
- console.log('2. Listing folders with single-level hierarchy (Microsoft only):');
- const singleLevelFolders: NylasListResponse = await nylas.folders.list({
- identifier: NYLAS_GRANT_ID!,
- queryParams: {
- singleLevel: true
- } as any
- });
-
- console.log(`Found ${singleLevelFolders.data.length} folders at single level:`);
- singleLevelFolders.data.forEach((folder, index) => {
- console.log(` ${index + 1}. ${folder.name} (ID: ${folder.id})`);
- if (folder.parentId) {
- console.log(` └─ Parent ID: ${folder.parentId}`);
- }
- });
- console.log();
-
- // 3. List folders with both singleLevel and parentId parameters
- const rootFolders = allFolders.data.filter(folder => !folder.parentId);
- if (rootFolders.length > 0) {
- const rootFolder = rootFolders[0];
- console.log(`3. Listing child folders of "${rootFolder.name}" with single-level hierarchy:`);
-
- const childFolders: NylasListResponse = await nylas.folders.list({
- identifier: NYLAS_GRANT_ID!,
- queryParams: {
- parentId: rootFolder.id,
- singleLevel: true
- } as any
- });
-
- console.log(`Found ${childFolders.data.length} direct child folders:`);
- childFolders.data.forEach((folder, index) => {
- console.log(` ${index + 1}. ${folder.name} (ID: ${folder.id})`);
- });
- } else {
- console.log('3. No root folders found to demonstrate parentId + singleLevel combination.');
- }
- console.log();
-
- // 4. Compare single-level vs multi-level for the same parent
- if (rootFolders.length > 0) {
- const rootFolder = rootFolders[0];
- console.log('4. Comparing single-level vs multi-level hierarchy:');
-
- // Multi-level (default)
- const multiLevelChildren: NylasListResponse = await nylas.folders.list({
- identifier: NYLAS_GRANT_ID!,
- queryParams: {
- parentId: rootFolder.id,
- singleLevel: false // explicit false
- } as any
- });
-
- // Single-level
- const singleLevelChildren: NylasListResponse = await nylas.folders.list({
- identifier: NYLAS_GRANT_ID!,
- queryParams: {
- parentId: rootFolder.id,
- singleLevel: true
- } as any
- });
-
- console.log(`Multi-level hierarchy: ${multiLevelChildren.data.length} folders`);
- console.log(`Single-level hierarchy: ${singleLevelChildren.data.length} folders`);
-
- if (multiLevelChildren.data.length !== singleLevelChildren.data.length) {
- console.log('📝 Note: Different folder counts indicate the singleLevel parameter is working correctly.');
- console.log(' Multi-level includes nested folders, single-level shows only direct children.');
- }
- }
-
- } catch (error) {
- if (error instanceof NylasApiError) {
- console.error('Nylas API Error:', error.message);
- console.error('Status Code:', error.statusCode);
- console.error('Error Type:', error.type);
- } else {
- console.error('Unexpected error:', error);
- }
- }
-}
-
-// Enhanced demonstration with detailed explanations
-async function detailedFoldersDemo() {
- try {
- const nylas = new Nylas({
- apiKey: NYLAS_API_KEY!,
- apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com',
- });
-
- console.log('\n=== Detailed singleLevel Parameter Demo ===\n');
-
- console.log('The singleLevel parameter controls folder hierarchy traversal:');
- console.log('• singleLevel: true → Returns only direct children (single level)');
- console.log('• singleLevel: false → Returns all descendants (multi-level, default)');
- console.log('• Microsoft accounts only - ignored for other providers\n');
-
- // Show all query parameter combinations
- const queryVariations: Array<{name: string, params: any}> = [
- {
- name: 'Default (multi-level)',
- params: {}
- },
- {
- name: 'Explicit multi-level',
- params: { singleLevel: false }
- },
- {
- name: 'Single-level only',
- params: { singleLevel: true }
- }
- ];
-
- for (const variation of queryVariations) {
- console.log(`--- ${variation.name} ---`);
- console.log(`Query params: ${JSON.stringify(variation.params)}`);
-
- try {
- const folders: NylasListResponse = await nylas.folders.list({
- identifier: NYLAS_GRANT_ID!,
- queryParams: variation.params
- });
-
- console.log(`Result: ${folders.data.length} folders found`);
-
- // Show folder hierarchy structure
- const rootFolders = folders.data.filter(f => !f.parentId);
- const childFolders = folders.data.filter(f => f.parentId);
-
- console.log(`├─ Root folders: ${rootFolders.length}`);
- console.log(`└─ Child folders: ${childFolders.length}`);
- } catch (error) {
- console.log(`Error: ${error instanceof NylasApiError ? error.message : 'Unknown error'}`);
- }
- console.log();
- }
-
- } catch (error) {
- console.error('Demo error:', error);
- }
-}
-
-// Run the examples
-async function main() {
- await listFoldersExample();
- await detailedFoldersDemo();
-
- console.log('=== Folders API Demo Complete ===');
- console.log('\nKey takeaways:');
- console.log('1. The singleLevel parameter is Microsoft-specific');
- console.log('2. Use singleLevel: true to get only direct children');
- console.log('3. Use singleLevel: false (or omit) for full hierarchy');
- console.log('4. Combine with parentId to control which folder to start from');
-}
-
-if (require.main === module) {
- main().catch(console.error);
-}
-
-export default main;
diff --git a/examples/messages/.gitignore b/examples/messages/.gitignore
new file mode 100644
index 00000000..fbf51c73
--- /dev/null
+++ b/examples/messages/.gitignore
@@ -0,0 +1 @@
+test-*.*
\ No newline at end of file
diff --git a/examples/messages/README.md b/examples/messages/README.md
index 09856915..50b53925 100644
--- a/examples/messages/README.md
+++ b/examples/messages/README.md
@@ -1,59 +1,175 @@
-# Messages Examples
+# Nylas Messages Examples
-This directory contains examples of how to use the Nylas Messages API to list, find, send, and work with messages.
+This directory contains examples demonstrating how to work with messages using the Nylas Node.js SDK.
-## Features Demonstrated
+## Examples
-This example demonstrates:
+### Send Attachments (`send-attachments-cli.ts`)
+**🎯 Core attachment examples with optional CLI interface**
-- **Message Operations**: List, find, send, update, and delete messages
-- **Message Fields**: Using different `fields` query parameters to get different data:
- - `standard` - Standard message payload
- - `include_headers` - Message with custom headers
- - `include_tracking_options` - Message with tracking settings
- - `raw_mime` - Raw MIME data (Base64url-encoded)
-- **Message Tracking**: Working with tracking options for opens, thread replies, and link clicks
-- **Scheduled Messages**: Creating, listing, and managing scheduled messages
-- **Message Cleaning**: Using the clean messages API to process message content
-- **Error Handling**: Proper error handling with NylasApiError
+This file demonstrates the four main ways to send attachments with the Nylas SDK:
-## Prerequisites
+1. **File Path Attachments** - Most common and efficient approach
+2. **Stream Attachments** - For more control over streams
+3. **Buffer Attachments** - When you need to process content in memory
+4. **String Content Attachments** - For dynamically generated text content
-Before running this example, make sure you have:
+The file is structured with:
+- **Core examples at the top** - Focus on Nylas SDK integration
+- **CLI interface at the bottom** - Optional interactive/batch modes
-1. A Nylas application with an API key
-2. A connected email account (grant)
-3. The required environment variables set up
+### Basic Messages (`messages.ts`)
+Shows basic message operations including reading, sending, and drafting messages.
-## Environment Variables
-
-Create a `.env` file in the parent `examples` directory with:
+## Quick Start
+### 1. Set up environment
+Create a `.env` file in the `examples` directory:
```bash
-# Required
NYLAS_API_KEY=your_api_key_here
NYLAS_GRANT_ID=your_grant_id_here
-
-# Optional
-NYLAS_API_URI=https://api.us.nylas.com
+TEST_EMAIL=recipient@example.com # Optional: for testing message sending
+NYLAS_API_URI=https://api.us.nylas.com # Optional: defaults to US API
```
-## Running the Example
-
+### 2. Install dependencies
```bash
cd examples
npm install
-npx ts-node messages/messages.ts
```
-## Example Output
+### 3. Ensure test files exist
+The examples expect test files in the `attachments/` subdirectory:
+- `test-small-26B.txt` (small text file)
+- `test-image-19KB.jpg` (small image)
+- `test-document-12MB.pdf` (large PDF)
+- `test-image-10MB.jpg` (large image)
+
+### 4. Run the examples
+
+**Attachment examples (interactive mode):**
+```bash
+npm run send-attachments
+```
+
+**Attachment examples (batch mode):**
+```bash
+npm run send-attachments small --format file --email test@example.com
+npm run send-attachments large --format stream --email test@example.com
+```
+
+**Basic messages:**
+```bash
+npm run messages
+```
+
+## Understanding Attachment Methods
+
+The core examples demonstrate four different approaches to sending attachments:
+
+### 1. File Path Method (Recommended)
+```typescript
+const attachment = createFileRequestBuilder('test-image.jpg');
+```
+- Most efficient and common approach
+- Uses streams internally for memory efficiency
+- Perfect for files on disk
+
+### 2. Stream Method
+```typescript
+const attachment = {
+ filename: 'file.jpg',
+ contentType: 'image/jpeg',
+ content: fs.createReadStream('path/to/file.jpg'),
+ size: fileSize
+};
+```
+- Good when you already have a stream
+- Useful for processing files from other sources
+- Memory efficient for large files
+
+### 3. Buffer Method
+```typescript
+const attachment = {
+ filename: 'file.jpg',
+ contentType: 'image/jpeg',
+ content: fs.readFileSync('path/to/file.jpg'),
+ size: buffer.length
+};
+```
+- Loads entire file into memory
+- Good for small files or when you need to process content
+- Simple but uses more memory
+
+### 4. String Content Method
+```typescript
+const attachment = {
+ filename: 'data.txt',
+ contentType: 'text/plain',
+ content: 'Your text content here',
+ size: Buffer.byteLength(content, 'utf8')
+};
+```
+- Perfect for dynamically generated content
+- Works for text files, JSON, XML, etc.
+- Great for reports, logs, or generated data
+
+## File Structure
+
+```
+messages/
+├── send-attachments-cli.ts # 🎯 Attachment examples + CLI tool
+├── utils/
+│ └── attachment-file-manager.ts # File utilities (extracted for reusability)
+├── attachments/ # Test files directory
+│ ├── test-small-26B.txt
+│ ├── test-image-19KB.jpg
+│ ├── test-document-12MB.pdf
+│ └── test-image-10MB.jpg
+├── messages.ts # Basic message operations
+└── README.md # This file
+```
+
+## CLI Tool Features
+
+The attachment examples include an optional CLI interface for easy testing:
+
+- **Interactive Mode**: Guided prompts for choosing examples
+- **Batch Mode**: Non-interactive commands for automation
+- **File Status Checking**: Verify test files are available
+- **Multiple Formats**: Test all attachment processing methods
+
+**Interactive mode (default):**
+```bash
+npm run send-attachments
+```
+
+**Batch mode examples:**
+```bash
+npm run send-attachments small --format stream --email test@example.com
+npm run send-attachments large --format buffer --email test@example.com
+npm run send-attachments status # Check file availability
+```
+
+## Best Practices
+
+1. **Use file paths** for most cases - it's the most efficient method
+2. **Use streams** when working with large files or when you already have streams
+3. **Use buffers** for small files when you need to process the content
+4. **Use strings** for dynamically generated text content
+5. **Always handle errors** appropriately with try/catch blocks
+6. **Check file existence** before creating attachments from file paths
+
+## Troubleshooting
+
+**"File not found" errors:**
+- Ensure test files exist in the `attachments/` directory
+- Run `npm run send-attachments-cli status` to check file availability
-The example will demonstrate:
-1. Listing messages with different field options
-2. Finding specific messages with tracking data
-3. Sending messages with tracking enabled
-4. Working with raw MIME data
-5. Managing scheduled messages
-6. Cleaning message content
+**"Environment variable not set" errors:**
+- Create a `.env` file in the `examples` directory with required variables
+- See the environment setup section above
-Each operation includes detailed console output showing the API responses and data structures.
\ No newline at end of file
+**TypeScript import errors:**
+- Ensure you've run `npm install` in the examples directory
+- The utils are properly exported from the attachment-file-manager module
\ No newline at end of file
diff --git a/examples/messages/cli-interface.ts b/examples/messages/cli-interface.ts
new file mode 100644
index 00000000..16ef2773
--- /dev/null
+++ b/examples/messages/cli-interface.ts
@@ -0,0 +1,162 @@
+import chalk from 'chalk';
+import { Command } from 'commander';
+import inquirer from 'inquirer';
+import { Message, NylasApiError, NylasResponse } from 'nylas';
+import type { SendAttachmentsExamples } from './examples';
+import { FileFormat, TestFileManager } from './utils/attachment-file-manager';
+
+interface CliOptions {
+ attachmentSize: 'small' | 'large';
+ format: FileFormat;
+ testEmail?: string;
+}
+
+async function getCliOptions(fileManager: TestFileManager): Promise {
+ console.log(chalk.blue.bold('\n🚀 Nylas Send Attachments Examples\n'));
+
+ fileManager.checkFileStatus();
+
+ const smallFiles = fileManager.getSmallFiles();
+ const largeFiles = fileManager.getLargeFiles();
+
+ if (smallFiles.length === 0 && largeFiles.length === 0) {
+ console.log(chalk.red('\n❌ No test files found! Please create the required test files in attachments/'));
+ process.exit(1);
+ }
+
+ const answers = await inquirer.prompt([
+ {
+ type: 'list',
+ name: 'format',
+ message: 'Which attachment method would you like to demonstrate?',
+ choices: [
+ { name: '📁 File paths (recommended)', value: 'file' },
+ { name: '🌊 Streams (advanced)', value: 'stream' },
+ { name: '💾 Buffers (small files)', value: 'buffer' },
+ { name: '📝 String content (dynamic)', value: 'string' },
+ ]
+ },
+ {
+ type: 'list',
+ name: 'attachmentSize',
+ message: 'What size attachments?',
+ choices: [
+ { name: `📎 Small (${smallFiles.length} available)`, value: 'small', disabled: smallFiles.length === 0 },
+ { name: `📋 Large (${largeFiles.length} available)`, value: 'large', disabled: largeFiles.length === 0 }
+ ]
+ },
+ {
+ type: 'input',
+ name: 'testEmail',
+ message: 'Recipient email address:',
+ default: process.env.TEST_EMAIL || '',
+ validate: (input: string) => input.includes('@') || 'Please enter a valid email address'
+ }
+ ]);
+
+ return answers as CliOptions;
+}
+
+async function runExample(examples: SendAttachmentsExamples, fileManager: TestFileManager, options: CliOptions): Promise {
+ const { format, testEmail, attachmentSize } = options;
+
+ if (!testEmail) {
+ console.log(chalk.yellow('⚠️ No email provided. Skipping send.'));
+ return;
+ }
+
+ try {
+ console.log(chalk.blue(`\n📤 Running ${format} attachment example (${attachmentSize} files)...\n`));
+
+ let result: NylasResponse;
+ const isLarge = attachmentSize === 'large';
+
+ // Route to the appropriate example based on format
+ switch (format) {
+ case 'file':
+ result = await examples.sendFilePathAttachments(fileManager, testEmail, isLarge);
+ break;
+ case 'stream':
+ result = await examples.sendStreamAttachments(fileManager, testEmail, isLarge);
+ break;
+ case 'buffer':
+ result = await examples.sendBufferAttachments(fileManager, testEmail, isLarge);
+ break;
+ case 'string':
+ result = await examples.sendStringAttachments(fileManager, testEmail, isLarge);
+ break;
+ default:
+ result = await examples.sendAttachmentsByFormat(fileManager, format, testEmail, attachmentSize);
+ }
+
+ console.log(chalk.green.bold('\n✅ Message sent successfully!'));
+ console.log(chalk.green(`📧 Message ID: ${result.data.id}`));
+ console.log(chalk.green(`📎 Attachments: ${result.data.attachments?.length || 0}`));
+
+ } catch (error) {
+ console.log(chalk.red.bold('\n❌ Error sending message:'));
+ if (error instanceof NylasApiError) {
+ console.log(chalk.red(` ${error.message} (${error.statusCode})`));
+ } else if (error instanceof Error) {
+ console.log(chalk.red(` ${error.message}`));
+ }
+ }
+}
+
+async function runBatchMode(examples: SendAttachmentsExamples, fileManager: TestFileManager, size: 'small' | 'large', format: FileFormat, email?: string): Promise {
+ const options: CliOptions = {
+ attachmentSize: size,
+ format,
+ testEmail: email
+ };
+
+ console.log(chalk.blue.bold('\n🚀 Nylas Send Attachments (Batch Mode)\n'));
+ fileManager.checkFileStatus();
+
+ await runExample(examples, fileManager, options);
+}
+
+export async function startCli(examples: SendAttachmentsExamples, fileManager: TestFileManager, testEmail: string): Promise {
+ const program = new Command();
+
+ program
+ .name('send-attachments')
+ .description('Nylas SDK attachment examples')
+ .version('1.0.0');
+
+ program
+ .command('interactive', { isDefault: true })
+ .description('Run interactive examples')
+ .action(async () => {
+ const options = await getCliOptions(fileManager);
+ await runExample(examples, fileManager, options);
+ });
+
+ program
+ .command('small')
+ .description('Send small attachments')
+ .option('-f, --format ', 'format (file|stream|buffer|string)', 'file')
+ .option('-e, --email ', 'recipient email')
+ .action(async (options) => {
+ await runBatchMode(examples, fileManager, 'small', options.format as FileFormat, options.email || testEmail);
+ });
+
+ program
+ .command('large')
+ .description('Send large attachment')
+ .option('-f, --format ', 'format (file|stream|buffer|string)', 'file')
+ .option('-e, --email ', 'recipient email')
+ .action(async (options) => {
+ await runBatchMode(examples, fileManager, 'large', options.format as FileFormat, options.email || testEmail);
+ });
+
+ program
+ .command('status')
+ .description('Check test files')
+ .action(() => {
+ console.log(chalk.blue.bold('\n📁 Test Files Status\n'));
+ fileManager.checkFileStatus();
+ });
+
+ await program.parseAsync();
+}
\ No newline at end of file
diff --git a/examples/messages/examples/buffer-attachments.ts b/examples/messages/examples/buffer-attachments.ts
new file mode 100644
index 00000000..2060b084
--- /dev/null
+++ b/examples/messages/examples/buffer-attachments.ts
@@ -0,0 +1,67 @@
+import * as dotenv from 'dotenv';
+ import Nylas, { NylasResponse, Message, SendMessageRequest } from 'nylas';
+import * as path from 'path';
+import * as process from 'process';
+import { TestFileManager } from '../utils/attachment-file-manager';
+
+// Load environment variables from .env file
+dotenv.config({ path: path.resolve(__dirname, '../../.env') });
+
+// Initialize the Nylas client
+const nylas = new Nylas({
+ apiKey: process.env.NYLAS_API_KEY || '',
+ apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com'
+});
+
+const grantId: string = process.env.NYLAS_GRANT_ID || '';
+
+/**
+ * Example 3: Buffer Attachments (For Small Files)
+ *
+ * Loads the entire file into memory as a Buffer.
+ * Good for small files or when you need to process content.
+ */
+export async function sendBufferAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false): Promise> {
+ console.log('💾 Sending attachments using buffers...');
+
+ let sizeDescription;
+
+ let files;
+ if (large) {
+ // Send one large attachment
+ files = [fileManager.getLargeFiles()[0]];
+ sizeDescription = 'large';
+ } else {
+ // Send multiple small attachments
+ files = fileManager.getSmallFiles().slice(0, 2);
+ sizeDescription = 'small';
+ }
+
+ // Create attachment using a buffer and use file info for name/type
+ const bufferAttachments = files.map(file => ({
+ filename: file.filename,
+ contentType: file.contentType,
+ content: file.asBuffer(),
+ size: file.size,
+ }));
+
+ const requestBody: SendMessageRequest = {
+ to: [{ name: 'Test Recipient', email: recipientEmail }],
+ subject: 'Nylas SDK - Buffer Attachments',
+ body: `
+ Buffer Attachments Example
+ This demonstrates sending attachments using Node.js Buffer objects.
+ Good for small files when you need the content in memory.
+ `,
+ attachments: bufferAttachments
+ };
+
+ // For large files, use a longer timeout (5 minutes)
+ const overrides = large ? { timeout: 300 } : undefined;
+
+ return await nylas.messages.send({
+ identifier: grantId,
+ requestBody,
+ overrides
+ });
+}
\ No newline at end of file
diff --git a/examples/messages/examples/file-path-attachments.ts b/examples/messages/examples/file-path-attachments.ts
new file mode 100644
index 00000000..dfa3b073
--- /dev/null
+++ b/examples/messages/examples/file-path-attachments.ts
@@ -0,0 +1,62 @@
+import * as dotenv from 'dotenv';
+import Nylas, { NylasResponse, Message, SendMessageRequest } from 'nylas';
+import * as path from 'path';
+import * as process from 'process';
+import { createFileRequestBuilder, TestFileManager } from '../utils/attachment-file-manager';
+
+// Load environment variables from .env file
+dotenv.config({ path: path.resolve(__dirname, '../../.env') });
+
+// Initialize the Nylas client
+const nylas = new Nylas({
+ apiKey: process.env.NYLAS_API_KEY || '',
+ apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com'
+});
+
+const grantId: string = process.env.NYLAS_GRANT_ID || '';
+
+/**
+ * Example 1: File Path Attachments (Most Common & Efficient)
+ *
+ * This is the recommended approach for most use cases.
+ * Uses streams internally for memory efficiency.
+ */
+export async function sendFilePathAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false): Promise> {
+ console.log('📁 Sending attachments using file paths...');
+
+ let attachments;
+ let sizeDescription;
+
+ if (large) {
+ // Send one large attachment
+ const file = fileManager.getLargeFiles()[0];
+ attachments = [createFileRequestBuilder(file.path)];
+ sizeDescription = 'large';
+ } else {
+ // Send multiple small attachments
+ const files = fileManager.getSmallFiles().slice(0, 2);
+ attachments = files.map(file => createFileRequestBuilder(file.path));
+ sizeDescription = 'small';
+ }
+
+ const requestBody: SendMessageRequest = {
+ to: [{ name: 'Test Recipient', email: recipientEmail }],
+ subject: `Nylas SDK - File Path Attachments (${sizeDescription})`,
+ body: `
+ File Path Attachments Example
+ This demonstrates the most common way to send attachments using file paths.
+ The SDK uses streams internally for memory efficiency.
+ Attachment size: ${sizeDescription} (${attachments.length} file${attachments.length > 1 ? 's' : ''})
+ `,
+ attachments
+ };
+
+ // For large files, use a longer timeout (5 minutes)
+ const overrides = large ? { timeout: 300 } : undefined;
+
+ return await nylas.messages.send({
+ identifier: grantId,
+ requestBody,
+ overrides
+ });
+}
\ No newline at end of file
diff --git a/examples/messages/examples/flexible-attachments.ts b/examples/messages/examples/flexible-attachments.ts
new file mode 100644
index 00000000..72795267
--- /dev/null
+++ b/examples/messages/examples/flexible-attachments.ts
@@ -0,0 +1,62 @@
+import * as dotenv from 'dotenv';
+import Nylas, { NylasResponse, Message, SendMessageRequest, CreateAttachmentRequest } from 'nylas';
+import * as path from 'path';
+import * as process from 'process';
+import { TestFileManager, FileFormat } from '../utils/attachment-file-manager';
+
+// Load environment variables from .env file
+dotenv.config({ path: path.resolve(__dirname, '../../.env') });
+
+// Initialize the Nylas client
+const nylas = new Nylas({
+ apiKey: process.env.NYLAS_API_KEY || '',
+ apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com'
+});
+
+const grantId: string = process.env.NYLAS_GRANT_ID || '';
+
+/**
+ * Flexible attachment sending based on format choice
+ */
+export async function sendAttachmentsByFormat(fileManager: TestFileManager, format: FileFormat, recipientEmail: string, attachmentSize: 'small' | 'large' = 'small'): Promise> {
+
+ let attachments: CreateAttachmentRequest[] = [];
+ let subject: string;
+
+ if (attachmentSize === 'small') {
+ // Send two small attachments
+ const files = fileManager.getSmallFiles().slice(0, 2);
+ for (const file of files) {
+ attachments.push(fileManager.createAttachmentRequest(file.filename, format));
+ }
+ subject = `Nylas SDK - Small Attachments (${format} format)`;
+ } else {
+ // Send one large attachment
+ const file = fileManager.getLargeFiles()[0];
+ attachments.push(fileManager.createAttachmentRequest(file.filename, format));
+ subject = `Nylas SDK - Large Attachment (${format} format)`;
+ }
+
+ const requestBody: SendMessageRequest = {
+ to: [{ name: 'Test Recipient', email: recipientEmail }],
+ subject,
+ body: `
+ Attachment Format Test: ${format}
+ This message demonstrates sending attachments using the ${format} format.
+ Files attached: ${attachments.length}
+
+ ${attachments.map(att => `- ${att.filename} (${att.size} bytes)
`).join('')}
+
+ `,
+ attachments
+ };
+
+ // For large files, use a longer timeout (5 minutes)
+ const overrides = attachmentSize === 'large' ? { timeout: 300 } : undefined;
+
+ return await nylas.messages.send({
+ identifier: grantId,
+ requestBody,
+ overrides
+ });
+}
\ No newline at end of file
diff --git a/examples/messages/examples/index.ts b/examples/messages/examples/index.ts
new file mode 100644
index 00000000..7264115f
--- /dev/null
+++ b/examples/messages/examples/index.ts
@@ -0,0 +1,13 @@
+import { sendFilePathAttachments } from './file-path-attachments';
+import { sendStreamAttachments } from './stream-attachments';
+import { sendBufferAttachments } from './buffer-attachments';
+import { sendStringAttachments } from './string-attachments';
+import { sendAttachmentsByFormat } from './flexible-attachments';
+
+export type SendAttachmentsExamples = {
+ sendFilePathAttachments: typeof sendFilePathAttachments,
+ sendStreamAttachments: typeof sendStreamAttachments,
+ sendBufferAttachments: typeof sendBufferAttachments,
+ sendStringAttachments: typeof sendStringAttachments,
+ sendAttachmentsByFormat: typeof sendAttachmentsByFormat
+};
\ No newline at end of file
diff --git a/examples/messages/examples/stream-attachments.ts b/examples/messages/examples/stream-attachments.ts
new file mode 100644
index 00000000..ac40c185
--- /dev/null
+++ b/examples/messages/examples/stream-attachments.ts
@@ -0,0 +1,72 @@
+import * as dotenv from 'dotenv';
+import Nylas, { NylasResponse, Message, SendMessageRequest, CreateAttachmentRequest } from 'nylas';
+import * as path from 'path';
+import * as process from 'process';
+import { TestFileManager } from '../utils/attachment-file-manager';
+
+// Load environment variables from .env file
+dotenv.config({ path: path.resolve(__dirname, '../../.env') });
+
+// Initialize the Nylas client
+const nylas = new Nylas({
+ apiKey: process.env.NYLAS_API_KEY || '',
+ apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com'
+});
+
+const grantId: string = process.env.NYLAS_GRANT_ID || '';
+
+/**
+ * Example 2: Stream Attachments (For More Control)
+ *
+ * Useful when you're working with streams from other sources
+ * or need more control over the stream processing.
+ */
+export async function sendStreamAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false): Promise> {
+ console.log('🌊 Sending attachments using streams...');
+
+ let attachments: CreateAttachmentRequest[] = [];
+ let sizeDescription;
+
+ if (large) {
+ // Send one large attachment
+ const file = fileManager.getLargeFiles()[0];
+ attachments = [{
+ filename: file.filename,
+ contentType: file.contentType,
+ content: file.asStream(),
+ size: file.size,
+ }];
+ sizeDescription = 'large';
+ } else {
+ // Send multiple small attachments
+ const files = fileManager.getSmallFiles().slice(0, 2);
+ attachments = files.map(file => ({
+ filename: file.filename,
+ contentType: file.contentType,
+ content: file.asStream(),
+ size: file.size,
+ }));
+ sizeDescription = 'small';
+ }
+
+ const requestBody: SendMessageRequest = {
+ to: [{ name: 'Test Recipient', email: recipientEmail }],
+ subject: `Nylas SDK - Stream Attachments (${sizeDescription})`,
+ body: `
+ Stream Attachments Example
+ This demonstrates sending attachments using readable streams.
+ Useful when you have streams from other sources.
+ Attachment size: ${sizeDescription} (${attachments.length} file${attachments.length > 1 ? 's' : ''})
+ `,
+ attachments
+ };
+
+ // For large files, use a longer timeout (5 minutes)
+ const overrides = large ? { timeout: 300 } : undefined;
+
+ return await nylas.messages.send({
+ identifier: grantId,
+ requestBody,
+ overrides
+ });
+}
\ No newline at end of file
diff --git a/examples/messages/examples/string-attachments.ts b/examples/messages/examples/string-attachments.ts
new file mode 100644
index 00000000..74f47f35
--- /dev/null
+++ b/examples/messages/examples/string-attachments.ts
@@ -0,0 +1,98 @@
+import * as dotenv from 'dotenv';
+import Nylas, { NylasResponse, Message, SendMessageRequest, CreateAttachmentRequest } from 'nylas';
+import { TestFileManager } from '../utils/attachment-file-manager';
+import * as path from 'path';
+import * as process from 'process';
+
+// Load environment variables from .env file
+dotenv.config({ path: path.resolve(__dirname, '../../.env') });
+
+// Initialize the Nylas client
+const nylas = new Nylas({
+ apiKey: process.env.NYLAS_API_KEY || '',
+ apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com'
+});
+
+const grantId: string = process.env.NYLAS_GRANT_ID || '';
+
+/**
+ * Example 4: String Content Attachments (Base64 Encoded Files)
+ *
+ * Perfect for sending existing files as base64 encoded strings.
+ * This example pulls the same files used by other examples but encodes them as base64 strings.
+ */
+export async function sendStringAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false): Promise> {
+ console.log('📝 Sending base64 encoded file attachments as strings...');
+
+ let stringAttachments: CreateAttachmentRequest[] = [];
+ let sizeDescription = '';
+
+ if (large) {
+ // Send one large attachment - use the large PDF file
+ const largeFiles = fileManager.getLargeFiles();
+ if (largeFiles.length > 0) {
+ const file = largeFiles.find(f => f.filename.includes('pdf')) || largeFiles[0];
+ const fileBuffer = file.asBuffer();
+ const base64Content = fileBuffer.toString('base64');
+
+ stringAttachments = [{
+ filename: file.filename,
+ contentType: file.contentType,
+ content: base64Content,
+ size: Buffer.byteLength(base64Content, 'utf8'),
+ }];
+ sizeDescription = 'large';
+ } else {
+ throw new Error('No large files available for testing');
+ }
+ } else {
+ // Send multiple small attachments - use text file and image file
+ const smallFiles = fileManager.getSmallFiles();
+ if (smallFiles.length >= 2) {
+ // Get the text file and image file
+ const textFile = smallFiles.find(f => f.filename.includes('.txt')) || smallFiles[0];
+ const imageFile = smallFiles.find(f => f.filename.includes('.jpg')) || smallFiles[1];
+
+ const files = [textFile, imageFile];
+
+ stringAttachments = files.map(file => {
+ const fileBuffer = file.asBuffer();
+ const base64Content = fileBuffer.toString('base64');
+
+ return {
+ filename: file.filename,
+ contentType: file.contentType,
+ content: base64Content,
+ size: Buffer.byteLength(base64Content, 'utf8'),
+ };
+ });
+ sizeDescription = 'small';
+ } else {
+ throw new Error('Not enough small files available for testing');
+ }
+ }
+
+ const requestBody: SendMessageRequest = {
+ to: [{ name: 'Test Recipient', email: recipientEmail }],
+ subject: `Nylas SDK - Base64 String Attachments (${sizeDescription})`,
+ body: `
+ Base64 String Attachments Example
+ This demonstrates sending existing files as base64 encoded strings.
+ Files are converted from the same test files used in other examples.
+ Attachment size: ${sizeDescription} (${stringAttachments.length} file${stringAttachments.length > 1 ? 's' : ''})
+
+ ${stringAttachments.map(att => `- ${att.filename} (${att.size} bytes base64 encoded)
`).join('')}
+
+ `,
+ attachments: stringAttachments
+ };
+
+ // For large files, use a longer timeout (5 minutes)
+ const overrides = large ? { timeout: 300 } : undefined;
+
+ return await nylas.messages.send({
+ identifier: grantId,
+ requestBody,
+ overrides
+ });
+}
\ No newline at end of file
diff --git a/examples/messages/send-attachments-cli.ts b/examples/messages/send-attachments-cli.ts
new file mode 100644
index 00000000..50949fe5
--- /dev/null
+++ b/examples/messages/send-attachments-cli.ts
@@ -0,0 +1,70 @@
+import * as dotenv from 'dotenv';
+import * as path from 'path';
+import * as process from 'process';
+import { TestFileManager } from './utils/attachment-file-manager';
+
+// =============================================================================
+// 🎯 NYLAS SDK ATTACHMENT EXAMPLES - Import clean, focused examples
+// =============================================================================
+
+// Example 1: File Path Attachments (Most Common & Efficient)
+import { sendFilePathAttachments } from './examples/file-path-attachments';
+
+// Example 2: Stream Attachments (For More Control)
+import { sendStreamAttachments } from './examples/stream-attachments';
+
+// Example 3: Buffer Attachments (For Small Files)
+import { sendBufferAttachments } from './examples/buffer-attachments';
+
+// Example 4: String Content Attachments (For Dynamic Content)
+import { sendStringAttachments } from './examples/string-attachments';
+
+// Flexible format-based attachment sending
+import { sendAttachmentsByFormat } from './examples/flexible-attachments';
+
+// =============================================================================
+// 📂 File Manager - Manage test files
+// =============================================================================
+// Available test files in the examples/messages/attachments directory
+const testFileManager = new TestFileManager(path.resolve(__dirname, './attachments'), [
+ 'test-small-26B.txt',
+ 'test-image-512KB.jpg',
+ 'test-document-12MB.pdf',
+ 'test-image-10MB.jpg'
+]);
+
+
+// =============================================================================
+// 🛠️ CLI Interface - Start CLI when run directly
+// =============================================================================
+
+import { startCli } from './cli-interface';
+import type { SendAttachmentsExamples } from './examples';
+
+const sendAttachmentsExamples: SendAttachmentsExamples = {
+ sendFilePathAttachments,
+ sendStreamAttachments,
+ sendBufferAttachments,
+ sendStringAttachments,
+ sendAttachmentsByFormat
+};
+const grantId: string = process.env.NYLAS_GRANT_ID || '';
+
+// Check for required environment variables
+if (!process.env.NYLAS_API_KEY) {
+ throw new Error('NYLAS_API_KEY environment variable is not set');
+}
+if (!grantId) {
+ throw new Error('NYLAS_GRANT_ID environment variable is not set');
+}
+
+// Load environment variables from .env file
+dotenv.config({ path: path.resolve(__dirname, '../.env') });
+
+// Run the CLI
+if (require.main === module) {
+ startCli(sendAttachmentsExamples, testFileManager, process.env.TEST_EMAIL || '').catch(error => {
+ console.error('Error:', error.message);
+ process.exit(1);
+ });
+}
\ No newline at end of file
diff --git a/examples/messages/utils/attachment-file-manager.ts b/examples/messages/utils/attachment-file-manager.ts
new file mode 100644
index 00000000..963d492e
--- /dev/null
+++ b/examples/messages/utils/attachment-file-manager.ts
@@ -0,0 +1,245 @@
+import * as path from 'path';
+import * as fs from 'fs';
+import * as mime from 'mime-types';
+import { CreateAttachmentRequest } from 'nylas';
+
+
+/**
+ * File format types for different ways to handle attachments
+ */
+export type FileFormat = 'file' | 'stream' | 'buffer' | 'string';
+
+/**
+ * Maximum size for small files
+ */
+export const MAX_SMALL_FILE_SIZE_LIMIT = 1024 * 1024 * 3; // 3MB
+
+/**
+ * Interface for file information and content access
+ */
+interface FileHandler {
+ path: string;
+ exists: boolean;
+ filename: string;
+ size: number;
+ contentType: string;
+
+ // Methods to get content in different formats
+ asFileRequest(): CreateAttachmentRequest;
+ asStream(): fs.ReadStream;
+ asBuffer(): Buffer;
+ asString(): string;
+}
+
+/**
+ * Common utility class for handling test files in different formats
+ */
+export class TestFileHandler implements FileHandler {
+ public readonly path: string;
+ public readonly exists: boolean;
+ public readonly filename: string;
+ public readonly size: number;
+ public readonly contentType: string;
+
+ constructor(fileName: string, baseDir?: string) {
+ // Default to attachments subdirectory relative to the messages folder
+ const attachmentsDir = baseDir || path.resolve(__dirname, '../attachments');
+ this.path = path.resolve(attachmentsDir, fileName);
+ this.exists = fs.existsSync(this.path);
+ this.filename = path.basename(this.path);
+
+ if (this.exists) {
+ const stats = fs.statSync(this.path);
+ this.size = stats.size;
+ this.contentType = mime.lookup(this.path) || 'application/octet-stream';
+ } else {
+ this.size = 0;
+ this.contentType = 'application/octet-stream';
+ }
+ }
+
+ /**
+ * Get file as CreateAttachmentRequest using file stream (original method)
+ */
+ asFileRequest(): CreateAttachmentRequest {
+ if (!this.exists) {
+ throw new Error(`File not found: ${this.filename}`);
+ }
+
+ return {
+ filename: this.filename,
+ contentType: this.contentType,
+ content: fs.createReadStream(this.path),
+ size: this.size,
+ };
+ }
+
+ /**
+ * Get file as a readable stream
+ */
+ asStream(): fs.ReadStream {
+ if (!this.exists) {
+ throw new Error(`File not found: ${this.filename}`);
+ }
+ return fs.createReadStream(this.path);
+ }
+
+ /**
+ * Get file as a Buffer
+ */
+ asBuffer(): Buffer {
+ if (!this.exists) {
+ throw new Error(`File not found: ${this.filename}`);
+ }
+ return fs.readFileSync(this.path);
+ }
+
+ /**
+ * Get file as a string (only works for text files)
+ */
+ asString(): string {
+ if (!this.exists) {
+ throw new Error(`File not found: ${this.filename}`);
+ }
+
+ // Check if it's likely a text file
+ const textTypes = ['text/', 'application/json', 'application/xml'];
+ const isTextFile = textTypes.some(type => this.contentType.startsWith(type));
+
+ if (!isTextFile && this.size > MAX_SMALL_FILE_SIZE_LIMIT) { // > 1MB
+ throw new Error(`File ${this.filename} is too large or not a text file to read as string`);
+ }
+
+ return fs.readFileSync(this.path, 'utf8');
+ }
+}
+
+/**
+ * File manager utility to handle all test files
+ */
+export class TestFileManager {
+ private files: Map = new Map();
+ private baseDir: string;
+
+ constructor(baseDir?: string, files?: string[]) {
+ // Default to attachments subdirectory relative to the messages folder
+ this.baseDir = baseDir || path.resolve(__dirname, '../attachments');
+
+ // Initialize all test files
+ files?.forEach(fileName => {
+ this.files.set(fileName, new TestFileHandler(fileName, this.baseDir));
+ });
+ }
+
+ /**
+ * Get a file handler by filename
+ */
+ getFile(fileName: string): TestFileHandler {
+ const handler = this.files.get(fileName);
+ if (!handler) {
+ throw new Error(`Unknown test file: ${fileName}`);
+ }
+ return handler;
+ }
+
+ /**
+ * Get all available files
+ */
+ getAllFiles(): TestFileHandler[] {
+ return Array.from(this.files.values());
+ }
+
+ /**
+ * Get only files that exist
+ */
+ getExistingFiles(): TestFileHandler[] {
+ return this.getAllFiles().filter(file => file.exists);
+ }
+
+ /**
+ * Get small files (< 1MB)
+ */
+ getSmallFiles(): TestFileHandler[] {
+ return this.getExistingFiles().filter(file => file.size < MAX_SMALL_FILE_SIZE_LIMIT);
+ }
+
+ /**
+ * Get large files (>= 1MB)
+ */
+ getLargeFiles(): TestFileHandler[] {
+ return this.getExistingFiles().filter(file => file.size >= MAX_SMALL_FILE_SIZE_LIMIT);
+ }
+
+ /**
+ * Check status of all test files
+ */
+ checkFileStatus(): void {
+ console.log('\nChecking test file status:');
+ this.getAllFiles().forEach(file => {
+ const status = file.exists ? `✓ Found (${file.size} bytes)` : '✗ Not found';
+ const sizeLabel = file.size >= MAX_SMALL_FILE_SIZE_LIMIT ? 'LARGE' : 'SMALL';
+ console.log(` ${file.filename}: ${status} [${sizeLabel}]`);
+ });
+ }
+
+ /**
+ * Create attachment request for a file in the specified format
+ */
+ createAttachmentRequest(fileName: string, format: FileFormat): CreateAttachmentRequest {
+ const file = this.getFile(fileName);
+
+ switch (format) {
+ case 'file':
+ return file.asFileRequest();
+
+ case 'stream':
+ return {
+ filename: file.filename,
+ contentType: file.contentType,
+ content: file.asStream(),
+ size: file.size,
+ };
+
+ case 'buffer':
+ return {
+ filename: file.filename,
+ contentType: file.contentType,
+ content: file.asBuffer(),
+ size: file.size,
+ };
+
+ case 'string':
+ const stringContent = file.asString();
+ return {
+ filename: file.filename,
+ contentType: file.contentType,
+ content: stringContent,
+ size: Buffer.byteLength(stringContent, 'utf8'),
+ };
+
+ default:
+ throw new Error(`Unsupported format: ${format}`);
+ }
+ }
+}
+
+/**
+ * Helper function to create a file request builder for any file path
+ * This maintains backward compatibility with the original function
+ */
+export function createFileRequestBuilder(filePath: string): CreateAttachmentRequest {
+ // If it's not an absolute path, assume it's in the attachments subdirectory
+ const fullPath = path.resolve(__dirname, filePath);
+
+ const stats = fs.statSync(fullPath);
+ const filename = path.basename(fullPath);
+ const contentType = mime.lookup(fullPath) || 'application/octet-stream';
+ const content = fs.createReadStream(fullPath);
+
+ return {
+ filename,
+ contentType,
+ content,
+ size: stats.size,
+ };
+}
\ No newline at end of file
diff --git a/examples/package-lock.json b/examples/package-lock.json
index e8a177c4..873702b8 100644
--- a/examples/package-lock.json
+++ b/examples/package-lock.json
@@ -8,10 +8,16 @@
"name": "nylas-examples",
"version": "1.0.0",
"dependencies": {
+ "chalk": "^5.3.0",
+ "commander": "^11.1.0",
"dotenv": "^16.0.0",
+ "inquirer": "^9.2.12",
+ "mime-types": "^2.1.35",
"nylas": "file:.."
},
"devDependencies": {
+ "@types/inquirer": "^9.0.7",
+ "@types/mime-types": "^2.1.4",
"@types/node": "^18.11.9",
"ts-node": "^10.8.0",
"typescript": "^5.0.0"
@@ -19,11 +25,12 @@
},
"..": {
"name": "nylas",
- "version": "7.8.0",
+ "version": "7.11.0",
"license": "MIT",
"dependencies": {
"change-case": "^4.1.2",
- "form-data": "^4.0.0",
+ "form-data-encoder": "^4.1.0",
+ "formdata-node": "^6.0.3",
"mime-types": "^2.1.35",
"node-fetch": "^2.6.12",
"uuid": "^8.3.2"
@@ -32,6 +39,7 @@
"@babel/core": "^7.3.3",
"@types/jest": "^29.5.2",
"@types/mime-types": "^2.1.2",
+ "@types/node": "^22.15.21",
"@types/node-fetch": "^2.6.4",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^2.25.0",
@@ -42,11 +50,11 @@
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-prettier": "^3.0.1",
"jest": "^29.6.1",
- "prettier": "^1.19.1",
+ "prettier": "^3.5.3",
"ts-jest": "^29.1.1",
- "typedoc": "^0.24.8",
- "typedoc-plugin-rename-defaults": "^0.6.5",
- "typescript": "^4.9.5"
+ "typedoc": "^0.28.4",
+ "typedoc-plugin-rename-defaults": "^0.7.3",
+ "typescript": "^5.8.3"
},
"engines": {
"node": ">=16"
@@ -65,6 +73,15 @@
"node": ">=12"
}
},
+ "node_modules/@inquirer/figures": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz",
+ "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -121,6 +138,24 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/inquirer": {
+ "version": "9.0.8",
+ "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.8.tgz",
+ "integrity": "sha512-CgPD5kFGWsb8HJ5K7rfWlifao87m4ph8uioU7OTncJevmE/VLIqAAjfQtko578JZg7/f69K4FgqYym3gNr7DeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/through": "*",
+ "rxjs": "^7.2.0"
+ }
+ },
+ "node_modules/@types/mime-types": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
+ "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/node": {
"version": "18.19.86",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.86.tgz",
@@ -131,6 +166,16 @@
"undici-types": "~5.26.4"
}
},
+ "node_modules/@types/through": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz",
+ "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
@@ -157,6 +202,45 @@
"node": ">=0.4.0"
}
},
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
@@ -164,6 +248,148 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
+ "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chardet": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+ "license": "MIT"
+ },
+ "node_modules/cli-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-width": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
+ "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@@ -171,6 +397,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/defaults": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+ "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "clone": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -193,6 +431,158 @@
"url": "https://dotenvx.com"
}
},
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/external-editor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+ "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+ "license": "MIT",
+ "dependencies": {
+ "chardet": "^0.7.0",
+ "iconv-lite": "^0.4.24",
+ "tmp": "^0.0.33"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/inquirer": {
+ "version": "9.3.7",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.7.tgz",
+ "integrity": "sha512-LJKFHCSeIRq9hanN14IlOtPSTe3lNES7TYDTE2xxdAy1LS5rYphajK1qtwvj3YmQXvvk0U2Vbmcni8P9EIQW9w==",
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/figures": "^1.0.3",
+ "ansi-escapes": "^4.3.2",
+ "cli-width": "^4.1.0",
+ "external-editor": "^3.1.0",
+ "mute-stream": "1.0.0",
+ "ora": "^5.4.1",
+ "run-async": "^3.0.0",
+ "rxjs": "^7.8.1",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^6.2.0",
+ "yoctocolors-cjs": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-interactive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
+ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@@ -200,10 +590,248 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/mute-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
+ "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==",
+ "license": "ISC",
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/nylas": {
"resolved": "..",
"link": true
},
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
+ "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.1.0",
+ "chalk": "^4.1.0",
+ "cli-cursor": "^3.1.0",
+ "cli-spinners": "^2.5.0",
+ "is-interactive": "^1.0.0",
+ "is-unicode-supported": "^0.1.0",
+ "log-symbols": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "wcwidth": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/run-async": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz",
+ "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "license": "MIT",
+ "dependencies": {
+ "os-tmpdir": "~1.0.2"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
@@ -248,6 +876,24 @@
}
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@@ -269,6 +915,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -276,6 +928,29 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+ "license": "MIT",
+ "dependencies": {
+ "defaults": "^1.0.3"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
@@ -285,6 +960,18 @@
"engines": {
"node": ">=6"
}
+ },
+ "node_modules/yoctocolors-cjs": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz",
+ "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
}
}
diff --git a/examples/package.json b/examples/package.json
index 64ade60f..de670a7f 100644
--- a/examples/package.json
+++ b/examples/package.json
@@ -9,15 +9,22 @@
"notetakers": "ts-node notetakers/notetaker.ts",
"calendars": "ts-node calendars/event_with_notetaker.ts",
"messages": "ts-node messages/messages.ts",
+ "send-attachments": "ts-node messages/send-attachments-cli.ts",
"folders": "ts-node folders/folders.ts"
},
"dependencies": {
+ "chalk": "^5.3.0",
+ "commander": "^11.1.0",
"dotenv": "^16.0.0",
+ "inquirer": "^9.2.12",
+ "mime-types": "^2.1.35",
"nylas": "file:.."
},
"devDependencies": {
"ts-node": "^10.8.0",
"typescript": "^5.0.0",
- "@types/node": "^18.11.9"
+ "@types/node": "^18.11.9",
+ "@types/inquirer": "^9.0.7",
+ "@types/mime-types": "^2.1.4"
}
}
\ No newline at end of file
diff --git a/jest.config.js b/jest.config.js
index a1544a8f..b3785f22 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -9,10 +9,19 @@ const config = {
],
},
moduleNameMapper: {
- '^[../]+src/([^/]+)$': '/lib/esm/$1.js',
- '^[../]+src/resources/([^/]+)$': '/lib/esm/resources/$1.js',
- '^[../]+src/models/([^/]+)$': '/lib/esm/models/$1.js',
+ '^[../]+src/([^/]+)$': '/src/$1.ts',
+ '^[../]+src/resources/([^/]+)$': '/src/resources/$1.ts',
+ '^[../]+src/models/([^/]+)$': '/src/models/$1.ts',
+ // Handle .js imports in TypeScript files for Jest
+ '^(.+)\\.js$': '$1',
},
+ // Handle ESM modules like node-fetch v3
+ extensionsToTreatAsEsm: ['.ts'],
+ transformIgnorePatterns: [
+ 'node_modules/(?!(node-fetch|data-uri-to-buffer|fetch-blob|formdata-polyfill)/)',
+ ],
+ // Set up jest-fetch-mock
+ setupFilesAfterEnv: ['/tests/setupTests.ts'],
coverageThreshold: {
global: {
functions: 80,
diff --git a/package-lock.json b/package-lock.json
index edeca825..b00ab1e2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,9 +10,10 @@
"license": "MIT",
"dependencies": {
"change-case": "^4.1.2",
- "form-data": "^4.0.0",
+ "form-data-encoder": "^4.1.0",
+ "formdata-node": "^6.0.3",
"mime-types": "^2.1.35",
- "node-fetch": "^2.6.12",
+ "node-fetch": "^3.3.2",
"uuid": "^8.3.2"
},
"devDependencies": {
@@ -20,7 +21,6 @@
"@types/jest": "^29.5.2",
"@types/mime-types": "^2.1.2",
"@types/node": "^22.15.21",
- "@types/node-fetch": "^2.6.4",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^2.25.0",
"@typescript-eslint/parser": "^2.25.0",
@@ -30,6 +30,7 @@
"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",
@@ -1538,30 +1539,6 @@
"undici-types": "~6.21.0"
}
},
- "node_modules/@types/node-fetch": {
- "version": "2.6.4",
- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz",
- "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==",
- "dev": true,
- "dependencies": {
- "@types/node": "*",
- "form-data": "^3.0.0"
- }
- },
- "node_modules/@types/node-fetch/node_modules/form-data": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
- "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
- "dev": true,
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/@types/prettier": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz",
@@ -1911,11 +1888,6 @@
"node": ">=4"
}
},
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
- },
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -2422,17 +2394,6 @@
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2455,6 +2416,37 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"dev": true
},
+ "node_modules/cross-fetch": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
+ "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "node-fetch": "^2.7.0"
+ }
+ },
+ "node_modules/cross-fetch/node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
"node_modules/cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -2480,6 +2472,15 @@
"semver": "bin/semver"
}
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -2534,14 +2535,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "engines": {
- "node": ">=0.4.0"
- }
- },
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -3268,6 +3261,29 @@
"bser": "2.1.1"
}
},
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
"node_modules/figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -3346,17 +3362,34 @@
"is-callable": "^1.1.3"
}
},
- "node_modules/form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "node_modules/form-data-encoder": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz",
+ "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/formdata-node": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz",
+ "integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
"dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
+ "fetch-blob": "^3.1.2"
},
"engines": {
- "node": ">= 6"
+ "node": ">=12.20.0"
}
},
"node_modules/fs.realpath": {
@@ -4742,6 +4775,17 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/jest-fetch-mock": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz",
+ "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-fetch": "^3.0.4",
+ "promise-polyfill": "^8.1.3"
+ }
+ },
"node_modules/jest-get-type": {
"version": "29.4.3",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz",
@@ -6091,23 +6135,42 @@
"tslib": "^2.0.3"
}
},
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
"node_modules/node-fetch": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
- "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "license": "MIT",
"dependencies": {
- "whatwg-url": "^5.0.0"
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
},
"engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
}
},
"node_modules/node-int64": {
@@ -6551,6 +6614,13 @@
"node": ">=0.4.0"
}
},
+ "node_modules/promise-polyfill": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz",
+ "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -7266,7 +7336,9 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/ts-jest": {
"version": "29.1.1",
@@ -7673,15 +7745,28 @@
"makeerror": "1.0.12"
}
},
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true,
+ "license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -9153,29 +9238,6 @@
"undici-types": "~6.21.0"
}
},
- "@types/node-fetch": {
- "version": "2.6.4",
- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz",
- "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==",
- "dev": true,
- "requires": {
- "@types/node": "*",
- "form-data": "^3.0.0"
- },
- "dependencies": {
- "form-data": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
- "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
- "dev": true,
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- }
- }
- }
- },
"@types/prettier": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz",
@@ -9411,11 +9473,6 @@
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
"dev": true
},
- "asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
- },
"available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -9792,14 +9849,6 @@
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
- "combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "requires": {
- "delayed-stream": "~1.0.0"
- }
- },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -9822,6 +9871,26 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"dev": true
},
+ "cross-fetch": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
+ "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==",
+ "dev": true,
+ "requires": {
+ "node-fetch": "^2.7.0"
+ },
+ "dependencies": {
+ "node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dev": true,
+ "requires": {
+ "whatwg-url": "^5.0.0"
+ }
+ }
+ }
+ },
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -9843,6 +9912,11 @@
}
}
},
+ "data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
+ },
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -9880,11 +9954,6 @@
"object-keys": "^1.1.1"
}
},
- "delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
- },
"detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -10450,6 +10519,15 @@
"bser": "2.1.1"
}
},
+ "fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "requires": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ }
+ },
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -10513,14 +10591,22 @@
"is-callable": "^1.1.3"
}
},
- "form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "form-data-encoder": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz",
+ "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw=="
+ },
+ "formdata-node": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz",
+ "integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg=="
+ },
+ "formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
+ "fetch-blob": "^3.1.2"
}
},
"fs.realpath": {
@@ -11506,6 +11592,16 @@
"jest-util": "^29.6.1"
}
},
+ "jest-fetch-mock": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz",
+ "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==",
+ "dev": true,
+ "requires": {
+ "cross-fetch": "^3.0.4",
+ "promise-polyfill": "^8.1.3"
+ }
+ },
"jest-get-type": {
"version": "29.4.3",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz",
@@ -12542,12 +12638,19 @@
"tslib": "^2.0.3"
}
},
+ "node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
+ },
"node-fetch": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
- "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"requires": {
- "whatwg-url": "^5.0.0"
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
}
},
"node-int64": {
@@ -12873,6 +12976,12 @@
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
+ "promise-polyfill": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz",
+ "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==",
+ "dev": true
+ },
"prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -13417,7 +13526,8 @@
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true
},
"ts-jest": {
"version": "29.1.1",
@@ -13694,15 +13804,22 @@
"makeerror": "1.0.12"
}
},
+ "web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="
+ },
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
diff --git a/package.json b/package.json
index 6fa01bb5..bf26a85c 100644
--- a/package.json
+++ b/package.json
@@ -40,9 +40,10 @@
"license": "MIT",
"dependencies": {
"change-case": "^4.1.2",
- "form-data": "^4.0.0",
+ "form-data-encoder": "^4.1.0",
+ "formdata-node": "^6.0.3",
"mime-types": "^2.1.35",
- "node-fetch": "^2.6.12",
+ "node-fetch": "^3.3.2",
"uuid": "^8.3.2"
},
"devDependencies": {
@@ -50,7 +51,6 @@
"@types/jest": "^29.5.2",
"@types/mime-types": "^2.1.2",
"@types/node": "^22.15.21",
- "@types/node-fetch": "^2.6.4",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^2.25.0",
"@typescript-eslint/parser": "^2.25.0",
@@ -60,6 +60,7 @@
"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",
diff --git a/src/apiClient.ts b/src/apiClient.ts
index fb4e964d..17bc7b17 100644
--- a/src/apiClient.ts
+++ b/src/apiClient.ts
@@ -1,5 +1,5 @@
import fetch, { Request, Response } from 'node-fetch';
-import { AbortSignal } from 'node-fetch/externals.js';
+import { Readable as _Readable } from 'node:stream';
import { NylasConfig, OverridableNylasConfig } from './config.js';
import {
NylasApiError,
@@ -8,7 +8,8 @@ import {
} from './models/error.js';
import { objKeysToCamelCase, objKeysToSnakeCase } from './utils.js';
import { SDK_VERSION } from './version.js';
-import FormData from 'form-data';
+import { FormData } from 'formdata-node';
+import { FormDataEncoder as _FormDataEncoder } from 'form-data-encoder';
import { snakeCase } from 'change-case';
/**
@@ -265,10 +266,6 @@ export default class APIClient {
if (optionParams.form) {
requestOptions.body = optionParams.form;
- requestOptions.headers = {
- ...requestOptions.headers,
- ...optionParams.form.getHeaders(),
- };
}
return requestOptions;
@@ -315,6 +312,10 @@ export default class APIClient {
options: RequestOptionsParams
): Promise {
const response = await this.sendRequest(options);
+ // TODO: See if we can fix this in a backwards compatible way
+ if (!response.body) {
+ throw new Error('No response body');
+ }
return response.body;
}
}
diff --git a/src/resources/auth.ts b/src/resources/auth.ts
index c6e0606a..d46f1a80 100644
--- a/src/resources/auth.ts
+++ b/src/resources/auth.ts
@@ -1,5 +1,5 @@
import { v4 as uuid } from 'uuid';
-import { createHash } from 'crypto';
+import { createHash } from 'node:crypto';
import { Resource } from './resource.js';
import {
URLForAdminConsentConfig,
diff --git a/src/resources/drafts.ts b/src/resources/drafts.ts
index 15196295..2a14d14d 100644
--- a/src/resources/drafts.ts
+++ b/src/resources/drafts.ts
@@ -14,7 +14,7 @@ import {
NylasResponse,
} from '../models/response.js';
import {
- encodeAttachmentStreams,
+ encodeAttachmentContent,
calculateTotalPayloadSize,
} from '../utils.js';
import { makePathParams } from '../utils.js';
@@ -138,7 +138,7 @@ export class Drafts extends Resource {
overrides,
});
} else if (requestBody.attachments) {
- const processedAttachments = await encodeAttachmentStreams(
+ const processedAttachments = await encodeAttachmentContent(
requestBody.attachments
);
@@ -183,7 +183,7 @@ export class Drafts extends Resource {
overrides,
});
} else if (requestBody.attachments) {
- const processedAttachments = await encodeAttachmentStreams(
+ const processedAttachments = await encodeAttachmentContent(
requestBody.attachments
);
diff --git a/src/resources/messages.ts b/src/resources/messages.ts
index 33693972..8fe4ed46 100644
--- a/src/resources/messages.ts
+++ b/src/resources/messages.ts
@@ -1,4 +1,4 @@
-import FormData from 'form-data';
+import { Blob, FormData } from 'formdata-node';
import APIClient, { RequestOptionsParams } from '../apiClient.js';
import { Overrides } from '../config.js';
import {
@@ -23,10 +23,11 @@ import {
NylasResponse,
} from '../models/response.js';
import {
- encodeAttachmentStreams,
- objKeysToSnakeCase,
- makePathParams,
+ attachmentStreamToFile,
calculateTotalPayloadSize,
+ encodeAttachmentContent,
+ makePathParams,
+ objKeysToSnakeCase,
} from '../utils.js';
import { AsyncListResponse, Resource } from './resource.js';
import { SmartCompose } from './smartCompose.js';
@@ -248,7 +249,7 @@ export class Messages extends Resource {
requestOptions.form = Messages._buildFormRequest(requestBody);
} else {
if (requestBody.attachments) {
- const processedAttachments = await encodeAttachmentStreams(
+ const processedAttachments = await encodeAttachmentContent(
requestBody.attachments
);
@@ -346,10 +347,7 @@ export class Messages extends Resource {
static _buildFormRequest(
requestBody: CreateDraftRequest | UpdateDraftRequest | SendMessageRequest
): FormData {
- // FormData imports are funky, cjs needs to use .default, es6 doesn't
- const FD = require('form-data');
- const FormDataConstructor = FD.default || FD;
- const form: FormData = new FormDataConstructor();
+ const form = new FormData();
// Split out the message payload from the attachments
const messagePayload = {
@@ -359,13 +357,28 @@ export class Messages extends Resource {
form.append('message', JSON.stringify(objKeysToSnakeCase(messagePayload)));
// Add a separate form field for each attachment
- requestBody.attachments?.forEach((attachment, index) => {
- const contentId = attachment.contentId || `file${index}`;
- form.append(contentId, attachment.content, {
- filename: attachment.filename,
- contentType: attachment.contentType,
+ if (requestBody.attachments && requestBody.attachments.length > 0) {
+ requestBody.attachments.map((attachment, index) => {
+ const contentId = attachment.contentId || `file${index}`;
+ // Handle different content types for formdata-node
+ if (typeof attachment.content === 'string') {
+ // Base64 string - create a Blob
+ const buffer = Buffer.from(attachment.content, 'base64');
+ const blob = new Blob([buffer], { type: attachment.contentType });
+ form.append(contentId, blob, attachment.filename);
+ } else if (Buffer.isBuffer(attachment.content)) {
+ // Buffer - create a Blob
+ const blob = new Blob([attachment.content], {
+ type: attachment.contentType,
+ });
+ form.append(contentId, blob, attachment.filename);
+ } else {
+ // ReadableStream - create a proper file-like object according to formdata-node docs
+ const file = attachmentStreamToFile(attachment);
+ form.append(contentId, file, attachment.filename);
+ }
});
- });
+ }
return form;
}
diff --git a/src/utils.ts b/src/utils.ts
index 5ae41e01..8b2fb29f 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,8 +1,9 @@
import { camelCase, snakeCase } from 'change-case';
-import * as fs from 'fs';
-import * as path from 'path';
+import * as fs from 'node:fs';
+import * as path from 'node:path';
import * as mime from 'mime-types';
-import { Readable } from 'stream';
+import { Readable } from 'node:stream';
+import { File as _File, Blob as _Blob } from 'formdata-node';
import { CreateAttachmentRequest } from './models/attachments.js';
export function createFileRequestBuilder(
@@ -43,24 +44,82 @@ function streamToBase64(stream: NodeJS.ReadableStream): Promise {
}
/**
- * Encodes the content of each attachment stream to base64.
+ * Converts a ReadableStream to a File-like object that can be used with FormData.
+ * @param attachment The attachment containing the stream and metadata.
+ * @param mimeType The MIME type for the file (optional).
+ * @returns A File-like object that properly handles the stream.
+ */
+export function attachmentStreamToFile(
+ attachment: CreateAttachmentRequest,
+ mimeType?: string
+): any {
+ if (mimeType != null && typeof mimeType !== 'string') {
+ throw new Error('Invalid mimetype, expected string.');
+ }
+ const content = attachment.content;
+ if (typeof content === 'string' || Buffer.isBuffer(content)) {
+ throw new Error('Invalid attachment content, expected ReadableStream.');
+ }
+
+ // Create a file-shaped object that FormData can handle properly
+ const fileObject = {
+ type: mimeType || attachment.contentType,
+ name: attachment.filename,
+ [Symbol.toStringTag]: 'File',
+ stream() {
+ return content;
+ },
+ };
+
+ // Add size if available
+ if (attachment.size !== undefined) {
+ (fileObject as any).size = attachment.size;
+ }
+
+ return fileObject;
+}
+
+/**
+ * Encodes the content of each attachment to base64.
+ * Handles ReadableStream, Buffer, and string content types.
* @param attachments The attachments to encode.
* @returns The attachments with their content encoded to base64.
*/
-export async function encodeAttachmentStreams(
+export async function encodeAttachmentContent(
attachments: CreateAttachmentRequest[]
): Promise {
return await Promise.all(
attachments.map(async (attachment) => {
- const base64EncodedContent =
- attachment.content instanceof Readable
- ? await streamToBase64(attachment.content)
- : attachment.content;
- return { ...attachment, content: base64EncodedContent }; // Replace the stream with its base64 string
+ let base64EncodedContent: string;
+
+ if (attachment.content instanceof Readable) {
+ // ReadableStream -> base64
+ base64EncodedContent = await streamToBase64(attachment.content);
+ } else if (Buffer.isBuffer(attachment.content)) {
+ // Buffer -> base64
+ base64EncodedContent = attachment.content.toString('base64');
+ } else {
+ // string (assumed to already be base64)
+ base64EncodedContent = attachment.content as string;
+ }
+
+ return { ...attachment, content: base64EncodedContent };
})
);
}
+/**
+ * @deprecated Use encodeAttachmentContent instead. This alias is provided for backwards compatibility.
+ * Encodes the content of each attachment stream to base64.
+ * @param attachments The attachments to encode.
+ * @returns The attachments with their content encoded to base64.
+ */
+export async function encodeAttachmentStreams(
+ attachments: CreateAttachmentRequest[]
+): Promise {
+ return encodeAttachmentContent(attachments);
+}
+
/**
* The type of function that converts a string to a different casing.
* @ignore Not for public use.
diff --git a/test-file-size.js b/test-file-size.js
new file mode 100644
index 00000000..f1489f83
--- /dev/null
+++ b/test-file-size.js
@@ -0,0 +1,155 @@
+// Quick test to verify file size generation
+function testLargeContentGeneration() {
+ // Create text content dynamically
+ let textContent =
+ 'This is a dynamically created text file!\n\nGenerated at: ' +
+ new Date().toISOString();
+ let jsonContent = JSON.stringify(
+ {
+ message: 'Hello from Nylas SDK!',
+ timestamp: new Date().toISOString(),
+ data: { temperature: 72, humidity: 45 },
+ },
+ null,
+ 2
+ );
+
+ // Generate large content
+ textContent += '\n\n' + '='.repeat(100) + '\n';
+ textContent += 'LARGE FILE SIMULATION - TARGET SIZE: >3MB\n';
+ textContent += '='.repeat(100) + '\n\n';
+
+ // Generate enough content to exceed 3,145,728 bytes (3MB)
+ const loremIpsum =
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ';
+
+ // Repeat Lorem ipsum to generate ~1.5MB of text content
+ const targetTextSize = 1572864; // 1.5MB
+ const repeats = Math.ceil(targetTextSize / loremIpsum.length);
+ textContent += loremIpsum.repeat(repeats);
+
+ // Add some structured data
+ textContent += '\n\n' + '='.repeat(100) + '\n';
+ textContent += 'LARGE DATASET SECTION\n';
+ textContent += '='.repeat(100) + '\n\n';
+
+ for (let i = 0; i < 1000; i++) {
+ textContent += `Record ${i}: This is a detailed record with ID ${i} containing extensive information about item ${i}. `;
+ textContent += `Timestamp: ${new Date().toISOString()}, Category: category-${i % 20}, `;
+ textContent += `Tags: tag-${i}, tag-${i + 1}, tag-${i + 2}, tag-${i + 3}, `;
+ textContent += `Properties: color=${['red', 'blue', 'green', 'yellow', 'purple', 'orange'][i % 6]}, `;
+ textContent += `size=${['small', 'medium', 'large', 'extra-large'][i % 4]}, `;
+ textContent += `active=${i % 2 === 0}, priority=${i % 10}, score=${Math.random().toFixed(6)}.\n`;
+ }
+
+ // Create a very large JSON structure to exceed 3MB total
+ const largeData = Array.from({ length: 2000 }, (_, i) => ({
+ id: i,
+ name: `Item ${i}`,
+ description: `This is item number ${i} with extensive additional data to make the JSON file larger. Here's some detailed information about this item including its history, specifications, and metadata.`,
+ timestamp: new Date().toISOString(),
+ details: {
+ category: `category-${i % 25}`,
+ subcategory: `subcategory-${i % 50}`,
+ tags: [
+ `tag-${i}`,
+ `tag-${i + 1}`,
+ `tag-${i + 2}`,
+ `tag-${i + 3}`,
+ `tag-${i + 4}`,
+ ],
+ properties: {
+ color: [
+ 'red',
+ 'blue',
+ 'green',
+ 'yellow',
+ 'purple',
+ 'orange',
+ 'pink',
+ 'cyan',
+ ][i % 8],
+ size: ['tiny', 'small', 'medium', 'large', 'extra-large', 'huge'][
+ i % 6
+ ],
+ weight: `${(Math.random() * 1000).toFixed(2)}kg`,
+ dimensions: {
+ length: `${(Math.random() * 100).toFixed(2)}cm`,
+ width: `${(Math.random() * 100).toFixed(2)}cm`,
+ height: `${(Math.random() * 100).toFixed(2)}cm`,
+ },
+ active: i % 2 === 0,
+ priority: i % 10,
+ score: Math.random(),
+ ratings: Array.from(
+ { length: 20 },
+ () => Math.floor(Math.random() * 5) + 1
+ ),
+ },
+ history: Array.from({ length: 10 }, (_, j) => ({
+ date: new Date(
+ Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000
+ ).toISOString(),
+ action: [
+ 'created',
+ 'updated',
+ 'viewed',
+ 'edited',
+ 'deleted',
+ 'restored',
+ ][j % 6],
+ user: `user-${j % 100}`,
+ details: `Action ${j} performed on item ${i} with additional context and information.`,
+ })),
+ },
+ }));
+
+ jsonContent = JSON.stringify(
+ {
+ message: 'Hello from Nylas SDK - Large Dataset (>3MB)!',
+ timestamp: new Date().toISOString(),
+ totalItems: largeData.length,
+ estimatedSize: '~2MB JSON content',
+ items: largeData,
+ metadata: {
+ generated: new Date().toISOString(),
+ purpose: 'Large file testing',
+ targetSize: '3145728+ bytes',
+ format: 'JSON',
+ },
+ },
+ null,
+ 2
+ );
+
+ // Calculate sizes
+ const textSize = Buffer.byteLength(textContent, 'utf8');
+ const jsonSize = Buffer.byteLength(jsonContent, 'utf8');
+ const totalSize = textSize + jsonSize;
+ const targetSize = 3145728; // 3MB
+
+ // eslint-disable-next-line no-console
+ console.log(
+ `Text content size: ${textSize.toLocaleString()} bytes (${(textSize / 1024 / 1024).toFixed(2)} MB)`
+ );
+ // eslint-disable-next-line no-console
+ console.log(
+ `JSON content size: ${jsonSize.toLocaleString()} bytes (${(jsonSize / 1024 / 1024).toFixed(2)} MB)`
+ );
+ // eslint-disable-next-line no-console
+ console.log(
+ `Total size: ${totalSize.toLocaleString()} bytes (${(totalSize / 1024 / 1024).toFixed(2)} MB)`
+ );
+ // eslint-disable-next-line no-console
+ console.log(
+ `Target size: ${targetSize.toLocaleString()} bytes (${(targetSize / 1024 / 1024).toFixed(2)} MB)`
+ );
+ // eslint-disable-next-line no-console
+ console.log(`Exceeds target: ${totalSize > targetSize ? 'YES ✅' : 'NO ❌'}`);
+ // eslint-disable-next-line no-console
+ console.log(
+ `Excess: ${totalSize > targetSize ? '+' : ''}${(totalSize - targetSize).toLocaleString()} bytes`
+ );
+}
+
+testLargeContentGeneration();
diff --git a/tests/apiClient.spec.ts b/tests/apiClient.spec.ts
index 5c3a0dc1..17a09acb 100644
--- a/tests/apiClient.spec.ts
+++ b/tests/apiClient.spec.ts
@@ -1,4 +1,3 @@
-import { RequestInfo } from 'node-fetch';
import APIClient, { RequestOptionsParams } from '../src/apiClient';
import {
NylasApiError,
@@ -6,17 +5,15 @@ import {
NylasSdkTimeoutError,
} from '../src/models/error';
import { SDK_VERSION } from '../src/version';
-import { mockedFetch, mockResponse } from './testUtils';
-
-jest.mock('node-fetch', () => {
- const originalModule = jest.requireActual('node-fetch');
- return {
- ...originalModule,
- default: jest.fn(),
- };
-});
+import { mockResponse } from './testUtils';
+
+import fetchMock from 'jest-fetch-mock';
describe('APIClient', () => {
+ beforeEach(() => {
+ fetchMock.resetMocks();
+ });
+
describe('constructor', () => {
it('should initialize all the values', () => {
const client = new APIClient({
@@ -261,7 +258,7 @@ describe('APIClient', () => {
mockResp.headers.set(key, value);
});
- mockedFetch.mockImplementationOnce(() => Promise.resolve(mockResp));
+ fetchMock.mockImplementationOnce(() => Promise.resolve(mockResp));
const response = await client.request({
path: '/test',
@@ -282,7 +279,7 @@ describe('APIClient', () => {
});
it('should throw an error if the response is undefined', async () => {
- mockedFetch.mockImplementationOnce(() =>
+ fetchMock.mockImplementationOnce(() =>
Promise.resolve(undefined as any)
);
@@ -298,7 +295,7 @@ describe('APIClient', () => {
const payload = {
invalid: true,
};
- mockedFetch.mockImplementationOnce(() =>
+ fetchMock.mockImplementationOnce(() =>
Promise.resolve(mockResponse(JSON.stringify(payload), 400))
);
@@ -337,7 +334,7 @@ describe('APIClient', () => {
mockHeaders['x-nylas-api-version']
);
- mockedFetch.mockImplementation(() => Promise.resolve(mockResp));
+ fetchMock.mockImplementation(() => Promise.resolve(mockResp));
try {
await client.request({
@@ -387,7 +384,7 @@ describe('APIClient', () => {
mockHeaders['x-nylas-api-version']
);
- mockedFetch.mockImplementation(() => Promise.resolve(mockResp));
+ fetchMock.mockImplementation(() => Promise.resolve(mockResp));
try {
await client.request({
@@ -405,7 +402,7 @@ describe('APIClient', () => {
it('should respect override timeout when provided in seconds (value < 1000)', async () => {
const overrideTimeout = 2; // 2 second timeout
- mockedFetch.mockImplementationOnce((_url: RequestInfo) => {
+ fetchMock.mockImplementationOnce(() => {
// Immediately throw an AbortError to simulate a timeout
const error = new Error('The operation was aborted');
error.name = 'AbortError';
@@ -434,7 +431,7 @@ describe('APIClient', () => {
try {
const overrideTimeoutMs = 2000; // 2000 milliseconds (2 seconds)
- mockedFetch.mockImplementationOnce((_url: RequestInfo) => {
+ fetchMock.mockImplementationOnce(() => {
// Immediately throw an AbortError to simulate a timeout
const error = new Error('The operation was aborted');
error.name = 'AbortError';
@@ -468,7 +465,7 @@ describe('APIClient', () => {
try {
// Mock fetch to return a successful response so we can verify setTimeout
- mockedFetch.mockImplementationOnce(() =>
+ fetchMock.mockImplementationOnce(() =>
Promise.resolve(mockResponse(JSON.stringify({ data: 'test' })))
);
@@ -503,7 +500,7 @@ describe('APIClient', () => {
try {
// Mock fetch to return a successful response so we can verify setTimeout
- mockedFetch.mockImplementationOnce(() =>
+ fetchMock.mockImplementationOnce(() =>
Promise.resolve(mockResponse(JSON.stringify({ data: 'test' })))
);
@@ -530,7 +527,7 @@ describe('APIClient', () => {
});
it('should use default timeout when no override provided', async () => {
- mockedFetch.mockImplementationOnce((_url: RequestInfo) => {
+ fetchMock.mockImplementationOnce(() => {
// Immediately throw an AbortError to simulate a timeout
const error = new Error('The operation was aborted');
error.name = 'AbortError';
@@ -567,7 +564,7 @@ describe('APIClient', () => {
mockResp.headers.set(key, value);
});
- mockedFetch.mockImplementationOnce(() => Promise.resolve(mockResp));
+ fetchMock.mockImplementationOnce(() => Promise.resolve(mockResp));
const result = await client.request({
path: '/test',
diff --git a/tests/resources/applications.spec.ts b/tests/resources/applications.spec.ts
index ca50b688..5612dfdc 100644
--- a/tests/resources/applications.spec.ts
+++ b/tests/resources/applications.spec.ts
@@ -1,6 +1,6 @@
import APIClient from '../../src/apiClient';
import { Applications } from '../../src/resources/applications';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('Applications', () => {
let apiClient: jest.Mocked;
diff --git a/tests/resources/attachments.spec.ts b/tests/resources/attachments.spec.ts
index 573fc45d..a5d60199 100644
--- a/tests/resources/attachments.spec.ts
+++ b/tests/resources/attachments.spec.ts
@@ -1,7 +1,7 @@
import APIClient from '../../src/apiClient';
import { Attachments } from '../../src/resources/attachments';
import { Readable } from 'stream';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('Attachments', () => {
let apiClient: jest.Mocked;
diff --git a/tests/resources/bookings.spec.ts b/tests/resources/bookings.spec.ts
index a4cd88d9..590565d8 100644
--- a/tests/resources/bookings.spec.ts
+++ b/tests/resources/bookings.spec.ts
@@ -1,6 +1,6 @@
import APIClient from '../../src/apiClient';
import { Bookings } from '../../src/resources/bookings';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('Bookings', () => {
let apiClient: jest.Mocked;
diff --git a/tests/resources/calendars.spec.ts b/tests/resources/calendars.spec.ts
index 578fb74d..b11c2837 100644
--- a/tests/resources/calendars.spec.ts
+++ b/tests/resources/calendars.spec.ts
@@ -1,7 +1,7 @@
import APIClient from '../../src/apiClient';
import { Calendars } from '../../src/resources/calendars';
import { AvailabilityMethod } from '../../src/models/availability';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('Calendars', () => {
let apiClient: jest.Mocked;
diff --git a/tests/resources/configurations.spec.ts b/tests/resources/configurations.spec.ts
index 1a47b349..20b43b3c 100644
--- a/tests/resources/configurations.spec.ts
+++ b/tests/resources/configurations.spec.ts
@@ -1,6 +1,6 @@
import APIClient from '../../src/apiClient';
import { Configurations } from '../../src/resources/configurations';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('Configurations', () => {
let apiClient: jest.Mocked;
diff --git a/tests/resources/contacts.spec.ts b/tests/resources/contacts.spec.ts
index dcbc370d..5296dfa9 100644
--- a/tests/resources/contacts.spec.ts
+++ b/tests/resources/contacts.spec.ts
@@ -1,6 +1,6 @@
import APIClient from '../../src/apiClient';
import { Contacts } from '../../src/resources/contacts';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('Contacts', () => {
let apiClient: jest.Mocked;
diff --git a/tests/resources/credentials.spec.ts b/tests/resources/credentials.spec.ts
index ee53ff68..be803153 100644
--- a/tests/resources/credentials.spec.ts
+++ b/tests/resources/credentials.spec.ts
@@ -1,7 +1,7 @@
import APIClient from '../../src/apiClient';
import { CredentialType } from '../../src/models/credentials';
import { Credentials } from '../../src/resources/credentials';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('Credentials', () => {
let apiClient: jest.Mocked;
diff --git a/tests/resources/drafts.spec.ts b/tests/resources/drafts.spec.ts
index 229611ff..1c522c2d 100644
--- a/tests/resources/drafts.spec.ts
+++ b/tests/resources/drafts.spec.ts
@@ -3,11 +3,11 @@ import { CreateAttachmentRequest } from '../../src/models/attachments';
import { Drafts } from '../../src/resources/drafts';
import { objKeysToCamelCase } from '../../src/utils';
import { createReadableStream, MockedFormData } from '../testUtils';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
// Mock the FormData constructor
-jest.mock('form-data', () => {
- return jest.fn().mockImplementation(function (this: MockedFormData) {
+jest.mock('formdata-node', () => ({
+ FormData: jest.fn().mockImplementation(function (this: MockedFormData) {
const appendedData: Record = {};
this.append = (key: string, value: any): void => {
@@ -15,8 +15,23 @@ jest.mock('form-data', () => {
};
this._getAppendedData = (): Record => appendedData;
- });
-});
+ }),
+ Blob: jest.fn().mockImplementation((parts: any[], options?: any) => ({
+ type: options?.type || '',
+ size: parts.reduce((size, part) => size + (part.length || 0), 0),
+ })),
+ File: jest
+ .fn()
+ .mockImplementation((parts: any[], name: string, options?: any) => ({
+ name,
+ type: options?.type || '',
+ size:
+ options?.size ||
+ parts.reduce((size, part) => size + (part.length || 0), 0),
+ stream: () => parts[0],
+ [Symbol.toStringTag]: 'File',
+ })),
+}));
describe('Drafts', () => {
let apiClient: jest.Mocked;
@@ -296,7 +311,14 @@ describe('Drafts', () => {
capturedRequest.form as any as MockedFormData
)._getAppendedData();
expect(formData.message).toEqual(JSON.stringify(messageJson));
- expect(formData.file0).toEqual(fileStream);
+ // ReadableStream is now wrapped in a file-like object
+ expect(formData.file0).toEqual({
+ type: 'text/plain',
+ name: 'file1.txt',
+ size: 3145728, // 3MB as declared in the attachment
+ stream: expect.any(Function),
+ [Symbol.toStringTag]: 'File',
+ });
expect(capturedRequest.method).toEqual('POST');
expect(capturedRequest.path).toEqual('/v3/grants/id123/drafts');
expect(capturedRequest.overrides).toEqual({
@@ -344,7 +366,14 @@ describe('Drafts', () => {
capturedRequest.form as any as MockedFormData
)._getAppendedData();
expect(formData.message).toEqual(JSON.stringify(messageJson));
- expect(formData.file0).toEqual(fileStream);
+ // ReadableStream is now wrapped in a file-like object
+ expect(formData.file0).toEqual({
+ type: 'text/plain',
+ name: 'small_file.txt',
+ size: 1000, // 1KB as declared in the attachment
+ stream: expect.any(Function),
+ [Symbol.toStringTag]: 'File',
+ });
expect(capturedRequest.method).toEqual('POST');
expect(capturedRequest.path).toEqual('/v3/grants/id123/drafts');
});
@@ -428,7 +457,14 @@ describe('Drafts', () => {
capturedRequest.form as any as MockedFormData
)._getAppendedData();
expect(formData.message).toEqual(JSON.stringify(messageJson));
- expect(formData.file0).toEqual(fileStream);
+ // ReadableStream is now wrapped in a file-like object
+ expect(formData.file0).toEqual({
+ type: 'text/plain',
+ name: 'file1.txt',
+ size: 3145728, // 3MB as declared in the attachment
+ stream: expect.any(Function),
+ [Symbol.toStringTag]: 'File',
+ });
expect(capturedRequest.method).toEqual('PUT');
expect(capturedRequest.path).toEqual('/v3/grants/id123/drafts/draft123');
expect(capturedRequest.overrides).toEqual({
@@ -477,7 +513,13 @@ describe('Drafts', () => {
capturedRequest.form as any as MockedFormData
)._getAppendedData();
expect(formData.message).toEqual(JSON.stringify(messageJson));
- expect(formData.file0).toEqual(fileStream);
+ expect(formData.file0).toEqual({
+ size: 1000, // 1KB as declared in the attachment
+ stream: expect.any(Function),
+ type: 'text/plain',
+ name: 'small_file.txt',
+ [Symbol.toStringTag]: 'File',
+ });
expect(capturedRequest.method).toEqual('PUT');
expect(capturedRequest.path).toEqual('/v3/grants/id123/drafts/draft123');
});
diff --git a/tests/resources/events.spec.ts b/tests/resources/events.spec.ts
index cb91d5cf..55134df9 100644
--- a/tests/resources/events.spec.ts
+++ b/tests/resources/events.spec.ts
@@ -1,6 +1,6 @@
import APIClient from '../../src/apiClient';
import { Events } from '../../src/resources/events';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('Events', () => {
let apiClient: jest.Mocked;
diff --git a/tests/resources/messages.spec.ts b/tests/resources/messages.spec.ts
index 31714834..4ae7315f 100644
--- a/tests/resources/messages.spec.ts
+++ b/tests/resources/messages.spec.ts
@@ -7,11 +7,11 @@ import {
Message,
MessageTrackingOptions,
} from '../../src/models/messages';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
// Mock the FormData constructor
-jest.mock('form-data', () => {
- return jest.fn().mockImplementation(function (this: MockedFormData) {
+jest.mock('formdata-node', () => ({
+ FormData: jest.fn().mockImplementation(function (this: MockedFormData) {
const appendedData: Record = {};
this.append = (key: string, value: any): void => {
@@ -19,8 +19,23 @@ jest.mock('form-data', () => {
};
this._getAppendedData = (): Record => appendedData;
- });
-});
+ }),
+ Blob: jest.fn().mockImplementation((parts: any[], options?: any) => ({
+ type: options?.type || '',
+ size: parts.reduce((size, part) => size + (part.length || 0), 0),
+ })),
+ File: jest
+ .fn()
+ .mockImplementation((parts: any[], name: string, options?: any) => ({
+ name,
+ type: options?.type || '',
+ size:
+ options?.size ||
+ parts.reduce((size, part) => size + (part.length || 0), 0),
+ stream: () => parts[0],
+ [Symbol.toStringTag]: 'File',
+ })),
+}));
describe('Messages', () => {
let apiClient: jest.Mocked;
@@ -391,7 +406,14 @@ describe('Messages', () => {
capturedRequest.form as any as MockedFormData
)._getAppendedData();
expect(formData.message).toEqual(JSON.stringify(messageJson));
- expect(formData.file0).toEqual(fileStream);
+ // ReadableStream is now wrapped in a file-like object
+ expect(formData.file0).toEqual({
+ type: 'text/plain',
+ name: 'file1.txt',
+ size: 3145728, // 3MB as declared in the attachment
+ stream: expect.any(Function),
+ [Symbol.toStringTag]: 'File',
+ });
expect(capturedRequest.method).toEqual('POST');
expect(capturedRequest.path).toEqual('/v3/grants/id123/messages/send');
expect(capturedRequest.overrides).toEqual({
@@ -439,7 +461,14 @@ describe('Messages', () => {
capturedRequest.form as any as MockedFormData
)._getAppendedData();
expect(formData.message).toEqual(JSON.stringify(messageJson));
- expect(formData.file0).toEqual(fileStream);
+ // ReadableStream is now wrapped in a file-like object
+ expect(formData.file0).toEqual({
+ type: 'text/plain',
+ name: 'small_file.txt',
+ size: 1000, // 1KB as declared in the attachment
+ stream: expect.any(Function),
+ [Symbol.toStringTag]: 'File',
+ });
expect(capturedRequest.method).toEqual('POST');
expect(capturedRequest.path).toEqual('/v3/grants/id123/messages/send');
});
diff --git a/tests/resources/redirectUris.spec.ts b/tests/resources/redirectUris.spec.ts
index 180e3267..51b68390 100644
--- a/tests/resources/redirectUris.spec.ts
+++ b/tests/resources/redirectUris.spec.ts
@@ -1,6 +1,6 @@
import APIClient from '../../src/apiClient';
import { RedirectUris } from '../../src/resources/redirectUris';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('RedirectUris', () => {
let apiClient: jest.Mocked;
diff --git a/tests/resources/sessions.spec.ts b/tests/resources/sessions.spec.ts
index d8e876e8..d14a4596 100644
--- a/tests/resources/sessions.spec.ts
+++ b/tests/resources/sessions.spec.ts
@@ -1,6 +1,6 @@
import APIClient from '../../src/apiClient';
import { Sessions } from '../../src/resources/sessions';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('Sessions', () => {
let apiClient: jest.Mocked;
diff --git a/tests/resources/smartCompose.spec.ts b/tests/resources/smartCompose.spec.ts
index 82bb9e98..e922710d 100644
--- a/tests/resources/smartCompose.spec.ts
+++ b/tests/resources/smartCompose.spec.ts
@@ -1,6 +1,6 @@
import APIClient from '../../src/apiClient';
import { SmartCompose } from '../../src/resources/smartCompose';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('SmartCompose', () => {
let apiClient: jest.Mocked;
diff --git a/tests/resources/webhooks.spec.ts b/tests/resources/webhooks.spec.ts
index 37c9bd29..740df236 100644
--- a/tests/resources/webhooks.spec.ts
+++ b/tests/resources/webhooks.spec.ts
@@ -2,7 +2,7 @@ import APIClient from '../../src/apiClient';
import { Webhooks } from '../../src/resources/webhooks';
import { WebhookTriggers } from '../../src/models/webhooks';
-jest.mock('../src/apiClient');
+jest.mock('../../src/apiClient');
describe('Webhooks', () => {
let apiClient: jest.Mocked;
diff --git a/tests/setupTests.ts b/tests/setupTests.ts
new file mode 100644
index 00000000..c4e6c932
--- /dev/null
+++ b/tests/setupTests.ts
@@ -0,0 +1,6 @@
+import { enableFetchMocks } from 'jest-fetch-mock';
+
+enableFetchMocks();
+
+// Export for TypeScript module resolution
+export {};
diff --git a/tests/testUtils.ts b/tests/testUtils.ts
index 2d14c75e..a2f1255e 100644
--- a/tests/testUtils.ts
+++ b/tests/testUtils.ts
@@ -1,5 +1,4 @@
/* istanbul ignore file */
-import fetch from 'node-fetch';
import { Readable } from 'stream';
export interface MockedFormData {
@@ -7,8 +6,6 @@ export interface MockedFormData {
_getAppendedData(): Record;
}
-export const mockedFetch = fetch as jest.MockedFunction;
-
export const mockResponse = (body: string, status = 200): any => {
const headers: Record = {};
@@ -48,7 +45,21 @@ export const createReadableStream = (text: string): NodeJS.ReadableStream => {
return new Readable({
read(): void {
this.push(text);
- this.push(null); // indicates EOF
+ this.push(null);
},
});
};
+
+export class MockFormData implements MockedFormData {
+ private data: Record = {};
+
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
+ append(key: string, value: any) {
+ this.data[key] = value;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
+ _getAppendedData() {
+ return this.data;
+ }
+}
diff --git a/tests/utils.spec.ts b/tests/utils.spec.ts
index f81fa040..4a34da65 100644
--- a/tests/utils.spec.ts
+++ b/tests/utils.spec.ts
@@ -3,9 +3,13 @@ import {
objKeysToCamelCase,
objKeysToSnakeCase,
makePathParams,
+ encodeAttachmentContent,
+ encodeAttachmentStreams,
} from '../src/utils';
+import { Readable } from 'stream';
+import { CreateAttachmentRequest } from '../src/models/attachments';
-jest.mock('fs', () => {
+jest.mock('node:fs', () => {
return {
statSync: jest.fn(),
createReadStream: jest.fn(),
@@ -27,8 +31,8 @@ describe('createFileRequestBuilder', () => {
beforeEach(() => {
jest.resetAllMocks();
- require('fs').statSync.mockReturnValue(mockedStatSync);
- require('fs').createReadStream.mockReturnValue(mockedReadStream);
+ require('node:fs').statSync.mockReturnValue(mockedStatSync);
+ require('node:fs').createReadStream.mockReturnValue(mockedReadStream);
});
it('should return correct file details for a given filePath', () => {
@@ -275,3 +279,211 @@ describe('makePathParams and safePath', () => {
expect(path).toBe('/v3/grants/%2F%40%23%24%25%5E%26*()');
});
});
+
+describe('encodeAttachmentContent', () => {
+ // Helper function to create a readable stream from a string
+ const createReadableStream = (content: string): NodeJS.ReadableStream => {
+ const stream = new Readable();
+ stream.push(content);
+ stream.push(null); // Signal end of stream
+ return stream;
+ };
+
+ it('should encode Buffer content to base64', async () => {
+ const testContent = 'Hello, World!';
+ const buffer = Buffer.from(testContent, 'utf8');
+ const expectedBase64 = buffer.toString('base64');
+
+ const attachments: CreateAttachmentRequest[] = [
+ {
+ filename: 'test.txt',
+ contentType: 'text/plain',
+ content: buffer,
+ size: buffer.length,
+ },
+ ];
+
+ const result = await encodeAttachmentContent(attachments);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].content).toBe(expectedBase64);
+ expect(result[0].filename).toBe('test.txt');
+ expect(result[0].contentType).toBe('text/plain');
+ expect(result[0].size).toBe(buffer.length);
+ });
+
+ it('should encode ReadableStream content to base64', async () => {
+ const testContent = 'Stream content test';
+ const stream = createReadableStream(testContent);
+ const expectedBase64 = Buffer.from(testContent, 'utf8').toString('base64');
+
+ const attachments: CreateAttachmentRequest[] = [
+ {
+ filename: 'stream.txt',
+ contentType: 'text/plain',
+ content: stream,
+ size: testContent.length,
+ },
+ ];
+
+ const result = await encodeAttachmentContent(attachments);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].content).toBe(expectedBase64);
+ expect(result[0].filename).toBe('stream.txt');
+ expect(result[0].contentType).toBe('text/plain');
+ });
+
+ it('should pass through string content unchanged', async () => {
+ const base64Content = 'SGVsbG8sIFdvcmxkIQ=='; // "Hello, World!" in base64
+
+ const attachments: CreateAttachmentRequest[] = [
+ {
+ filename: 'string.txt',
+ contentType: 'text/plain',
+ content: base64Content,
+ size: 13,
+ },
+ ];
+
+ const result = await encodeAttachmentContent(attachments);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].content).toBe(base64Content);
+ expect(result[0].filename).toBe('string.txt');
+ expect(result[0].contentType).toBe('text/plain');
+ });
+
+ it('should handle mixed content types in a single request', async () => {
+ const bufferContent = Buffer.from('Buffer content', 'utf8');
+ const streamContent = createReadableStream('Stream content');
+ const stringContent = 'U3RyaW5nIGNvbnRlbnQ='; // Already base64
+
+ const attachments: CreateAttachmentRequest[] = [
+ {
+ filename: 'buffer.txt',
+ contentType: 'text/plain',
+ content: bufferContent,
+ size: bufferContent.length,
+ },
+ {
+ filename: 'stream.txt',
+ contentType: 'text/plain',
+ content: streamContent,
+ size: 14,
+ },
+ {
+ filename: 'string.txt',
+ contentType: 'text/plain',
+ content: stringContent,
+ size: 13,
+ },
+ ];
+
+ const result = await encodeAttachmentContent(attachments);
+
+ expect(result).toHaveLength(3);
+
+ // Buffer should be converted to base64
+ expect(result[0].content).toBe(bufferContent.toString('base64'));
+ expect(result[0].filename).toBe('buffer.txt');
+
+ // Stream should be converted to base64
+ expect(result[1].content).toBe(
+ Buffer.from('Stream content', 'utf8').toString('base64')
+ );
+ expect(result[1].filename).toBe('stream.txt');
+
+ // String should be passed through
+ expect(result[2].content).toBe(stringContent);
+ expect(result[2].filename).toBe('string.txt');
+ });
+
+ it('should handle binary Buffer content correctly', async () => {
+ // Create a buffer with binary data (some non-text bytes)
+ const binaryData = new Uint8Array([0x00, 0x01, 0x02, 0xff, 0xfe, 0xfd]);
+ const buffer = Buffer.from(binaryData);
+ const expectedBase64 = buffer.toString('base64');
+
+ const attachments: CreateAttachmentRequest[] = [
+ {
+ filename: 'binary.bin',
+ contentType: 'application/octet-stream',
+ content: buffer,
+ size: buffer.length,
+ },
+ ];
+
+ const result = await encodeAttachmentContent(attachments);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].content).toBe(expectedBase64);
+ expect(result[0].filename).toBe('binary.bin');
+ expect(result[0].contentType).toBe('application/octet-stream');
+ });
+
+ it('should handle empty Buffer', async () => {
+ const emptyBuffer = Buffer.alloc(0);
+ const expectedBase64 = emptyBuffer.toString('base64'); // Should be empty string
+
+ const attachments: CreateAttachmentRequest[] = [
+ {
+ filename: 'empty.txt',
+ contentType: 'text/plain',
+ content: emptyBuffer,
+ size: 0,
+ },
+ ];
+
+ const result = await encodeAttachmentContent(attachments);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].content).toBe(expectedBase64);
+ expect(result[0].filename).toBe('empty.txt');
+ });
+
+ it('should handle large Buffer content', async () => {
+ // Create a 1KB buffer with repeating pattern
+ const largeContent = 'A'.repeat(1024);
+ const largeBuffer = Buffer.from(largeContent, 'utf8');
+ const expectedBase64 = largeBuffer.toString('base64');
+
+ const attachments: CreateAttachmentRequest[] = [
+ {
+ filename: 'large.txt',
+ contentType: 'text/plain',
+ content: largeBuffer,
+ size: largeBuffer.length,
+ },
+ ];
+
+ const result = await encodeAttachmentContent(attachments);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].content).toBe(expectedBase64);
+ expect(result[0].filename).toBe('large.txt');
+ expect(result[0].size).toBe(1024);
+ });
+});
+
+describe('encodeAttachmentStreams (backwards compatibility)', () => {
+ it('should work the same as encodeAttachmentContent', async () => {
+ const testContent = 'Backwards compatibility test';
+ const buffer = Buffer.from(testContent, 'utf8');
+
+ const attachments: CreateAttachmentRequest[] = [
+ {
+ filename: 'compat.txt',
+ contentType: 'text/plain',
+ content: buffer,
+ size: buffer.length,
+ },
+ ];
+
+ const newResult = await encodeAttachmentContent(attachments);
+ const oldResult = await encodeAttachmentStreams(attachments);
+
+ expect(oldResult).toEqual(newResult);
+ expect(oldResult[0].content).toBe(buffer.toString('base64'));
+ });
+});