Skip to content

Commit 1b51d3d

Browse files
authored
feat: add support for singleLevel query parameter in folders API (#647)
* feat: add support for singleLevel query parameter in folders API - Add singleLevel parameter to ListFolderQueryParams interface - Support for Microsoft accounts to control folder hierarchy traversal - singleLevel: true returns only direct children (single-level) - singleLevel: false returns all descendants (multi-level, default) - Add comprehensive tests for new parameter functionality - Add folders example demonstrating singleLevel usage - Update CHANGELOG.md with new feature documentation - Maintain backwards compatibility with existing API usage * style: fix prettier formatting - remove trailing whitespace in folder model comment
1 parent 7548028 commit 1b51d3d

File tree

7 files changed

+387
-1
lines changed

7 files changed

+387
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Support for `trackingOptions` property in Message responses when using `fields=include_tracking_options`
1313
- Support for `rawMime` property in Message responses when using `fields=raw_mime`
1414
- `MessageTrackingOptions` interface for tracking message opens, thread replies, link clicks, and custom labels
15+
- Support for `singleLevel` query parameter in `ListFolderQueryParams` for Microsoft accounts to control folder hierarchy traversal
1516

1617
### Fixed
1718
- Fixed 3MB payload size limit to consider total request size (message body + attachments) instead of just attachment size when determining whether to use multipart/form-data encoding

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This directory contains examples of how to use the Nylas Node.js SDK to interact
66

77
- [Notetakers](./notetakers/README.md) - Examples of how to use the Nylas Notetakers API to invite a Notetaker bot to meetings, get recordings and transcripts, and more.
88
- [Messages](./messages/README.md) - Examples of how to use the Nylas Messages API to list, find, send, update messages, and work with new features like tracking options and raw MIME data.
9+
- [Folders](./folders/README.md) - Examples of how to use the Nylas Folders API, including the new `singleLevel` parameter for Microsoft accounts.
910

1011
## Running the Examples
1112

examples/folders/README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Nylas Folders API Examples
2+
3+
This directory contains examples of how to use the Nylas Folders API with the Nylas Node.js SDK, including the new `singleLevel` query parameter.
4+
5+
## What is the singleLevel Parameter?
6+
7+
The `singleLevel` parameter is a new query parameter for the "list all folders" endpoint that controls folder hierarchy traversal:
8+
9+
- **`singleLevel: true`** - Retrieves folders from a single-level hierarchy only (direct children)
10+
- **`singleLevel: false`** - Retrieves folders across a multi-level hierarchy (all descendants, default behavior)
11+
- **Microsoft accounts only** - This parameter is ignored for other providers
12+
13+
## Examples
14+
15+
- [folders.ts](./folders.ts) - A comprehensive example showing how to use the `singleLevel` parameter in various scenarios
16+
17+
## Running the Examples
18+
19+
To run these examples, you'll need to:
20+
21+
1. Install dependencies from the examples directory:
22+
```bash
23+
cd examples
24+
npm install
25+
```
26+
27+
2. Copy the `.env.example` file to `.env` if you haven't already and add your credentials:
28+
```bash
29+
cp .env.example .env
30+
# Edit .env with your editor
31+
```
32+
33+
3. Edit the `.env` file to include:
34+
- `NYLAS_API_KEY` - Your Nylas API key
35+
- `NYLAS_API_URI` (optional) - The Nylas API server URI (defaults to "https://api.us.nylas.com")
36+
- `NYLAS_GRANT_ID` - The Grant ID for a Microsoft account to see the `singleLevel` parameter in action
37+
38+
4. Run the example:
39+
```bash
40+
# From the examples directory
41+
npx ts-node folders/folders.ts
42+
43+
# Or if you add it to package.json scripts
44+
npm run folders
45+
```
46+
47+
## Understanding the Example
48+
49+
This example demonstrates:
50+
51+
1. **Default Behavior**: Listing all folders with multi-level hierarchy (default)
52+
2. **Single-Level Listing**: Using `singleLevel: true` to get only direct children
53+
3. **Combined Parameters**: Using `singleLevel` with `parentId` to control the starting point
54+
4. **Comparison**: Side-by-side comparison of single-level vs multi-level results
55+
5. **Parameter Variations**: All possible combinations of the `singleLevel` parameter
56+
57+
## Use Cases
58+
59+
The `singleLevel` parameter is particularly useful when:
60+
61+
- **Building UI Navigation**: You want to show only immediate child folders in a tree view
62+
- **Performance Optimization**: Reducing the amount of data returned when you only need direct children
63+
- **Hierarchical Processing**: Processing folder structures level by level rather than all at once
64+
- **Microsoft-Specific Features**: Taking advantage of Microsoft's folder hierarchy capabilities
65+
66+
## API Reference
67+
68+
### ListFolderQueryParams Interface
69+
70+
```typescript
71+
interface ListFolderQueryParams extends ListQueryParams {
72+
/**
73+
* (Microsoft and EWS only.) Use the ID of a folder to find all child folders it contains.
74+
*/
75+
parentId?: string;
76+
77+
/**
78+
* (Microsoft only) If true, retrieves folders from a single-level hierarchy only.
79+
* If false, retrieves folders across a multi-level hierarchy.
80+
* @default false
81+
*/
82+
singleLevel?: boolean;
83+
}
84+
```
85+
86+
### Example Usage
87+
88+
```typescript
89+
import Nylas from 'nylas';
90+
91+
const nylas = new Nylas({ apiKey: 'your-api-key' });
92+
93+
// Get only direct children of a specific folder
94+
const singleLevelFolders = await nylas.folders.list({
95+
identifier: 'grant-id',
96+
queryParams: {
97+
parentId: 'parent-folder-id',
98+
singleLevel: true
99+
}
100+
});
101+
102+
// Get all descendants (default behavior)
103+
const allFolders = await nylas.folders.list({
104+
identifier: 'grant-id',
105+
queryParams: {
106+
parentId: 'parent-folder-id',
107+
singleLevel: false // or omit this parameter
108+
}
109+
});
110+
```
111+
112+
## Documentation
113+
114+
For more information, see the [Nylas API Documentation](https://developer.nylas.com/).

examples/folders/folders.ts

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import dotenv from 'dotenv';
2+
import path from 'path';
3+
import * as process from 'process';
4+
import Nylas, {
5+
Folder,
6+
NylasResponse,
7+
NylasListResponse,
8+
NylasApiError,
9+
ListFolderQueryParams
10+
} from 'nylas';
11+
12+
// Load environment variables from .env file
13+
dotenv.config({ path: path.resolve(__dirname, '../.env') });
14+
15+
const NYLAS_API_KEY = process.env.NYLAS_API_KEY;
16+
const NYLAS_GRANT_ID = process.env.NYLAS_GRANT_ID;
17+
18+
if (!NYLAS_API_KEY) {
19+
console.error('NYLAS_API_KEY is required. Please add it to your .env file.');
20+
process.exit(1);
21+
}
22+
23+
if (!NYLAS_GRANT_ID) {
24+
console.error('NYLAS_GRANT_ID is required. Please add it to your .env file.');
25+
process.exit(1);
26+
}
27+
28+
async function listFoldersExample() {
29+
try {
30+
// Initialize Nylas client
31+
const nylas = new Nylas({
32+
apiKey: NYLAS_API_KEY!,
33+
apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com',
34+
});
35+
36+
console.log('=== Nylas Folders API Demo ===\n');
37+
38+
// 1. List all folders (default behavior - multi-level hierarchy)
39+
console.log('1. Listing all folders (multi-level hierarchy):');
40+
const allFolders: NylasListResponse<Folder> = await nylas.folders.list({
41+
identifier: NYLAS_GRANT_ID!,
42+
queryParams: {}
43+
});
44+
45+
console.log(`Found ${allFolders.data.length} folders total:`);
46+
allFolders.data.forEach((folder, index) => {
47+
console.log(` ${index + 1}. ${folder.name} (ID: ${folder.id})`);
48+
if (folder.parentId) {
49+
console.log(` └─ Parent ID: ${folder.parentId}`);
50+
}
51+
if (folder.childCount !== undefined) {
52+
console.log(` └─ Child Count: ${folder.childCount}`);
53+
}
54+
});
55+
console.log();
56+
57+
// 2. List folders with single-level hierarchy (Microsoft only)
58+
console.log('2. Listing folders with single-level hierarchy (Microsoft only):');
59+
const singleLevelFolders: NylasListResponse<Folder> = await nylas.folders.list({
60+
identifier: NYLAS_GRANT_ID!,
61+
queryParams: {
62+
singleLevel: true
63+
} as any
64+
});
65+
66+
console.log(`Found ${singleLevelFolders.data.length} folders at single level:`);
67+
singleLevelFolders.data.forEach((folder, index) => {
68+
console.log(` ${index + 1}. ${folder.name} (ID: ${folder.id})`);
69+
if (folder.parentId) {
70+
console.log(` └─ Parent ID: ${folder.parentId}`);
71+
}
72+
});
73+
console.log();
74+
75+
// 3. List folders with both singleLevel and parentId parameters
76+
const rootFolders = allFolders.data.filter(folder => !folder.parentId);
77+
if (rootFolders.length > 0) {
78+
const rootFolder = rootFolders[0];
79+
console.log(`3. Listing child folders of "${rootFolder.name}" with single-level hierarchy:`);
80+
81+
const childFolders: NylasListResponse<Folder> = await nylas.folders.list({
82+
identifier: NYLAS_GRANT_ID!,
83+
queryParams: {
84+
parentId: rootFolder.id,
85+
singleLevel: true
86+
} as any
87+
});
88+
89+
console.log(`Found ${childFolders.data.length} direct child folders:`);
90+
childFolders.data.forEach((folder, index) => {
91+
console.log(` ${index + 1}. ${folder.name} (ID: ${folder.id})`);
92+
});
93+
} else {
94+
console.log('3. No root folders found to demonstrate parentId + singleLevel combination.');
95+
}
96+
console.log();
97+
98+
// 4. Compare single-level vs multi-level for the same parent
99+
if (rootFolders.length > 0) {
100+
const rootFolder = rootFolders[0];
101+
console.log('4. Comparing single-level vs multi-level hierarchy:');
102+
103+
// Multi-level (default)
104+
const multiLevelChildren: NylasListResponse<Folder> = await nylas.folders.list({
105+
identifier: NYLAS_GRANT_ID!,
106+
queryParams: {
107+
parentId: rootFolder.id,
108+
singleLevel: false // explicit false
109+
} as any
110+
});
111+
112+
// Single-level
113+
const singleLevelChildren: NylasListResponse<Folder> = await nylas.folders.list({
114+
identifier: NYLAS_GRANT_ID!,
115+
queryParams: {
116+
parentId: rootFolder.id,
117+
singleLevel: true
118+
} as any
119+
});
120+
121+
console.log(`Multi-level hierarchy: ${multiLevelChildren.data.length} folders`);
122+
console.log(`Single-level hierarchy: ${singleLevelChildren.data.length} folders`);
123+
124+
if (multiLevelChildren.data.length !== singleLevelChildren.data.length) {
125+
console.log('📝 Note: Different folder counts indicate the singleLevel parameter is working correctly.');
126+
console.log(' Multi-level includes nested folders, single-level shows only direct children.');
127+
}
128+
}
129+
130+
} catch (error) {
131+
if (error instanceof NylasApiError) {
132+
console.error('Nylas API Error:', error.message);
133+
console.error('Status Code:', error.statusCode);
134+
console.error('Error Type:', error.type);
135+
} else {
136+
console.error('Unexpected error:', error);
137+
}
138+
}
139+
}
140+
141+
// Enhanced demonstration with detailed explanations
142+
async function detailedFoldersDemo() {
143+
try {
144+
const nylas = new Nylas({
145+
apiKey: NYLAS_API_KEY!,
146+
apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com',
147+
});
148+
149+
console.log('\n=== Detailed singleLevel Parameter Demo ===\n');
150+
151+
console.log('The singleLevel parameter controls folder hierarchy traversal:');
152+
console.log('• singleLevel: true → Returns only direct children (single level)');
153+
console.log('• singleLevel: false → Returns all descendants (multi-level, default)');
154+
console.log('• Microsoft accounts only - ignored for other providers\n');
155+
156+
// Show all query parameter combinations
157+
const queryVariations: Array<{name: string, params: any}> = [
158+
{
159+
name: 'Default (multi-level)',
160+
params: {}
161+
},
162+
{
163+
name: 'Explicit multi-level',
164+
params: { singleLevel: false }
165+
},
166+
{
167+
name: 'Single-level only',
168+
params: { singleLevel: true }
169+
}
170+
];
171+
172+
for (const variation of queryVariations) {
173+
console.log(`--- ${variation.name} ---`);
174+
console.log(`Query params: ${JSON.stringify(variation.params)}`);
175+
176+
try {
177+
const folders: NylasListResponse<Folder> = await nylas.folders.list({
178+
identifier: NYLAS_GRANT_ID!,
179+
queryParams: variation.params
180+
});
181+
182+
console.log(`Result: ${folders.data.length} folders found`);
183+
184+
// Show folder hierarchy structure
185+
const rootFolders = folders.data.filter(f => !f.parentId);
186+
const childFolders = folders.data.filter(f => f.parentId);
187+
188+
console.log(`├─ Root folders: ${rootFolders.length}`);
189+
console.log(`└─ Child folders: ${childFolders.length}`);
190+
} catch (error) {
191+
console.log(`Error: ${error instanceof NylasApiError ? error.message : 'Unknown error'}`);
192+
}
193+
console.log();
194+
}
195+
196+
} catch (error) {
197+
console.error('Demo error:', error);
198+
}
199+
}
200+
201+
// Run the examples
202+
async function main() {
203+
await listFoldersExample();
204+
await detailedFoldersDemo();
205+
206+
console.log('=== Folders API Demo Complete ===');
207+
console.log('\nKey takeaways:');
208+
console.log('1. The singleLevel parameter is Microsoft-specific');
209+
console.log('2. Use singleLevel: true to get only direct children');
210+
console.log('3. Use singleLevel: false (or omit) for full hierarchy');
211+
console.log('4. Combine with parentId to control which folder to start from');
212+
}
213+
214+
if (require.main === module) {
215+
main().catch(console.error);
216+
}
217+
218+
export default main;

examples/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"build": "tsc",
99
"notetakers": "ts-node notetakers/notetaker.ts",
1010
"calendars": "ts-node calendars/event_with_notetaker.ts",
11-
"messages": "ts-node messages/messages.ts"
11+
"messages": "ts-node messages/messages.ts",
12+
"folders": "ts-node folders/folders.ts"
1213
},
1314
"dependencies": {
1415
"dotenv": "^16.0.0",

src/models/folders.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ export interface ListFolderQueryParams extends ListQueryParams {
101101
* (Microsoft and EWS only.) Use the ID of a folder to find all child folders it contains.
102102
*/
103103
parentId?: string;
104+
105+
/**
106+
* (Microsoft only) If true, retrieves folders from a single-level hierarchy only.
107+
* If false, retrieves folders across a multi-level hierarchy.
108+
* @default false
109+
*/
110+
singleLevel?: boolean;
104111
}
105112

106113
export type UpdateFolderRequest = Partial<CreateFolderRequest>;

0 commit comments

Comments
 (0)