ms365-mcp-server 1.1.15 → 1.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,106 @@
1
+ import { logger } from './api.js';
2
+ export class BatchPerformanceMonitor {
3
+ constructor() {
4
+ this.metrics = [];
5
+ }
6
+ /**
7
+ * Start tracking a performance operation
8
+ */
9
+ startOperation(operationType, requestCount, method) {
10
+ const operationId = `${operationType}_${method}_${Date.now()}`;
11
+ const startTime = Date.now();
12
+ logger.log(`šŸ“Š PERFORMANCE START: ${operationType} (${method}) - ${requestCount} requests`);
13
+ return operationId;
14
+ }
15
+ /**
16
+ * End tracking a performance operation
17
+ */
18
+ endOperation(operationId, operationType, requestCount, method, startTime, httpCalls, success = true, errorCount = 0) {
19
+ const endTime = Date.now();
20
+ const duration = endTime - startTime;
21
+ const averagePerRequest = duration / requestCount;
22
+ const metrics = {
23
+ operationType,
24
+ requestCount,
25
+ startTime,
26
+ endTime,
27
+ duration,
28
+ averagePerRequest,
29
+ success,
30
+ errorCount,
31
+ httpCalls,
32
+ method
33
+ };
34
+ this.metrics.push(metrics);
35
+ logger.log(`šŸ“Š PERFORMANCE END: ${operationType} (${method})`);
36
+ logger.log(` Duration: ${duration}ms`);
37
+ logger.log(` HTTP Calls: ${httpCalls}`);
38
+ logger.log(` Avg per request: ${averagePerRequest.toFixed(2)}ms`);
39
+ logger.log(` Success: ${success} (${errorCount} errors)`);
40
+ return metrics;
41
+ }
42
+ /**
43
+ * Compare traditional vs batched performance
44
+ */
45
+ comparePerformance(operationType) {
46
+ const traditionalMetrics = this.metrics.find(m => m.operationType === operationType && m.method === 'traditional');
47
+ const batchedMetrics = this.metrics.find(m => m.operationType === operationType && m.method === 'batched');
48
+ if (!traditionalMetrics || !batchedMetrics) {
49
+ return null;
50
+ }
51
+ const durationReduction = ((traditionalMetrics.duration - batchedMetrics.duration) / traditionalMetrics.duration) * 100;
52
+ const httpCallReduction = ((traditionalMetrics.httpCalls - batchedMetrics.httpCalls) / traditionalMetrics.httpCalls) * 100;
53
+ const efficiencyGain = (traditionalMetrics.averagePerRequest - batchedMetrics.averagePerRequest) / traditionalMetrics.averagePerRequest * 100;
54
+ const result = {
55
+ traditional: traditionalMetrics,
56
+ batched: batchedMetrics,
57
+ improvement: {
58
+ durationReduction,
59
+ httpCallReduction,
60
+ efficiencyGain
61
+ }
62
+ };
63
+ logger.log(`šŸ† PERFORMANCE COMPARISON: ${operationType}`);
64
+ logger.log(` Duration: ${traditionalMetrics.duration}ms → ${batchedMetrics.duration}ms (${durationReduction.toFixed(1)}% faster)`);
65
+ logger.log(` HTTP Calls: ${traditionalMetrics.httpCalls} → ${batchedMetrics.httpCalls} (${httpCallReduction.toFixed(1)}% reduction)`);
66
+ logger.log(` Efficiency: ${efficiencyGain.toFixed(1)}% improvement per request`);
67
+ return result;
68
+ }
69
+ /**
70
+ * Get all metrics for a specific operation type
71
+ */
72
+ getMetrics(operationType) {
73
+ if (operationType) {
74
+ return this.metrics.filter(m => m.operationType === operationType);
75
+ }
76
+ return [...this.metrics];
77
+ }
78
+ /**
79
+ * Generate performance report
80
+ */
81
+ generateReport() {
82
+ const operationTypes = [...new Set(this.metrics.map(m => m.operationType))];
83
+ let report = `šŸ“Š BATCH PERFORMANCE REPORT\n\n`;
84
+ operationTypes.forEach(opType => {
85
+ const comparison = this.comparePerformance(opType);
86
+ if (comparison) {
87
+ report += `šŸ” ${opType.toUpperCase()}\n`;
88
+ report += ` Traditional: ${comparison.traditional.duration}ms (${comparison.traditional.httpCalls} HTTP calls)\n`;
89
+ report += ` Batched: ${comparison.batched.duration}ms (${comparison.batched.httpCalls} HTTP calls)\n`;
90
+ report += ` šŸ’” Improvements:\n`;
91
+ report += ` - ${comparison.improvement.durationReduction.toFixed(1)}% faster execution\n`;
92
+ report += ` - ${comparison.improvement.httpCallReduction.toFixed(1)}% fewer HTTP calls\n`;
93
+ report += ` - ${comparison.improvement.efficiencyGain.toFixed(1)}% better efficiency\n\n`;
94
+ }
95
+ });
96
+ return report;
97
+ }
98
+ /**
99
+ * Clear all metrics
100
+ */
101
+ clear() {
102
+ this.metrics = [];
103
+ }
104
+ }
105
+ // Singleton instance
106
+ export const performanceMonitor = new BatchPerformanceMonitor();
@@ -0,0 +1,277 @@
1
+ import { performanceMonitor } from './batch-performance-monitor.js';
2
+ import { logger } from './api.js';
3
+ export class BatchTestRunner {
4
+ constructor(ms365Ops) {
5
+ this.ms365Ops = ms365Ops;
6
+ }
7
+ /**
8
+ * Test Scenario 1: Bulk Email Retrieval
9
+ */
10
+ async testBulkEmailRetrieval(messageIds) {
11
+ logger.log(`🧪 TEST SCENARIO 1: Bulk Email Retrieval (${messageIds.length} emails)`);
12
+ // Traditional approach - sequential calls
13
+ const traditionalStart = Date.now();
14
+ const traditionalEmails = [];
15
+ for (const messageId of messageIds) {
16
+ try {
17
+ const email = await this.ms365Ops.getEmail(messageId, true);
18
+ traditionalEmails.push(email);
19
+ }
20
+ catch (error) {
21
+ logger.log(`āŒ Traditional: Failed to get email ${messageId}: ${error}`);
22
+ }
23
+ }
24
+ const traditionalMetrics = performanceMonitor.endOperation('bulk_email_retrieval', 'bulk_email_retrieval', messageIds.length, 'traditional', traditionalStart, messageIds.length * 2, // email + attachments call per email
25
+ true);
26
+ // Wait a moment to avoid rate limiting
27
+ await new Promise(resolve => setTimeout(resolve, 1000));
28
+ // Batched approach - native JSON batching
29
+ const batchedStart = Date.now();
30
+ const batchedEmails = await this.ms365Ops.getEmailsBatch(messageIds, true);
31
+ const batchedMetrics = performanceMonitor.endOperation('bulk_email_retrieval', 'bulk_email_retrieval', messageIds.length, 'batched', batchedStart, Math.ceil(messageIds.length / 20), // 20 requests per batch
32
+ true);
33
+ const comparison = performanceMonitor.comparePerformance('bulk_email_retrieval');
34
+ return {
35
+ traditional: { emails: traditionalEmails, metrics: traditionalMetrics },
36
+ batched: { emails: batchedEmails, metrics: batchedMetrics },
37
+ comparison
38
+ };
39
+ }
40
+ /**
41
+ * Test Scenario 2: Bulk Email Operations (Mark/Move/Delete)
42
+ */
43
+ async testBulkEmailOperations(messageIds) {
44
+ logger.log(`🧪 TEST SCENARIO 2: Bulk Email Operations (${messageIds.length} operations)`);
45
+ // Create operations for testing
46
+ const operations = messageIds.map((id, index) => ({
47
+ id: `op_${index}`,
48
+ operation: 'mark',
49
+ messageId: id,
50
+ params: { isRead: true }
51
+ }));
52
+ // Traditional approach - sequential operations
53
+ const traditionalStart = Date.now();
54
+ const traditionalResults = [];
55
+ for (const op of operations) {
56
+ try {
57
+ await this.ms365Ops.markEmail(op.messageId, op.params.isRead);
58
+ traditionalResults.push({ id: op.id, success: true });
59
+ }
60
+ catch (error) {
61
+ logger.log(`āŒ Traditional: Failed to mark email ${op.messageId}: ${error}`);
62
+ traditionalResults.push({ id: op.id, success: false, error: String(error) });
63
+ }
64
+ }
65
+ const traditionalMetrics = performanceMonitor.endOperation('bulk_email_operations', 'bulk_email_operations', operations.length, 'traditional', traditionalStart, operations.length, // one call per operation
66
+ true);
67
+ // Wait a moment to avoid rate limiting
68
+ await new Promise(resolve => setTimeout(resolve, 1000));
69
+ // Batched approach - native JSON batching
70
+ const batchedStart = Date.now();
71
+ const batchedResults = await this.ms365Ops.batchEmailOperations(operations);
72
+ const batchedMetrics = performanceMonitor.endOperation('bulk_email_operations', 'bulk_email_operations', operations.length, 'batched', batchedStart, Math.ceil(operations.length / 20), // 20 operations per batch
73
+ true);
74
+ const comparison = performanceMonitor.comparePerformance('bulk_email_operations');
75
+ return {
76
+ traditional: { results: traditionalResults, metrics: traditionalMetrics },
77
+ batched: { results: Array.from(batchedResults.entries()), metrics: batchedMetrics },
78
+ comparison
79
+ };
80
+ }
81
+ /**
82
+ * Test Scenario 3: Folder + Email Parallel Fetching
83
+ */
84
+ async testFolderWithEmails(folderId, emailCount = 10) {
85
+ logger.log(`🧪 TEST SCENARIO 3: Folder + Emails Fetching (${emailCount} emails)`);
86
+ // Traditional approach - sequential calls
87
+ const traditionalStart = Date.now();
88
+ // Get folder info first - using existing folder methods
89
+ const folders = await this.ms365Ops.listFolders();
90
+ const folderInfo = folders.find(f => f.id === folderId || f.displayName.toLowerCase() === folderId.toLowerCase());
91
+ // Then get emails
92
+ const emailList = await this.ms365Ops.listEmails(folderId, emailCount);
93
+ const traditionalResult = {
94
+ folder: folderInfo,
95
+ emails: emailList.messages
96
+ };
97
+ const traditionalMetrics = performanceMonitor.endOperation('folder_with_emails', 'folder_with_emails', emailCount + 1, // folder + emails
98
+ 'traditional', traditionalStart, 2, // folder call + emails call
99
+ true);
100
+ // Wait a moment to avoid rate limiting
101
+ await new Promise(resolve => setTimeout(resolve, 1000));
102
+ // Batched approach - parallel fetching
103
+ const batchedStart = Date.now();
104
+ const batchedResult = await this.ms365Ops.getFolderWithRecentEmails(folderId, emailCount);
105
+ const batchedMetrics = performanceMonitor.endOperation('folder_with_emails', 'folder_with_emails', emailCount + 1, 'batched', batchedStart, 1, // single batch call
106
+ true);
107
+ const comparison = performanceMonitor.comparePerformance('folder_with_emails');
108
+ return {
109
+ traditional: { result: traditionalResult, metrics: traditionalMetrics },
110
+ batched: { result: batchedResult, metrics: batchedMetrics },
111
+ comparison
112
+ };
113
+ }
114
+ /**
115
+ * Test Scenario 4: Mixed Operations (Real-world workflow)
116
+ */
117
+ async testMixedOperations(messageIds) {
118
+ logger.log(`🧪 TEST SCENARIO 4: Mixed Operations Workflow (${messageIds.length} emails)`);
119
+ // Take first few emails for different operations
120
+ const emailsToMark = messageIds.slice(0, Math.ceil(messageIds.length / 3));
121
+ const emailsToMove = messageIds.slice(Math.ceil(messageIds.length / 3), Math.ceil(messageIds.length * 2 / 3));
122
+ const emailsToDelete = messageIds.slice(Math.ceil(messageIds.length * 2 / 3));
123
+ // Traditional approach
124
+ const traditionalStart = Date.now();
125
+ const traditionalResults = {
126
+ marked: 0,
127
+ moved: 0,
128
+ deleted: 0,
129
+ errors: 0
130
+ };
131
+ // Mark emails as read
132
+ for (const id of emailsToMark) {
133
+ try {
134
+ await this.ms365Ops.markEmail(id, true);
135
+ traditionalResults.marked++;
136
+ }
137
+ catch (error) {
138
+ traditionalResults.errors++;
139
+ }
140
+ }
141
+ // Move emails to archive (simulated)
142
+ for (const id of emailsToMove) {
143
+ try {
144
+ await this.ms365Ops.moveEmail(id, 'archive');
145
+ traditionalResults.moved++;
146
+ }
147
+ catch (error) {
148
+ traditionalResults.errors++;
149
+ }
150
+ }
151
+ // Delete emails (simulated - we won't actually delete for testing)
152
+ for (const id of emailsToDelete) {
153
+ try {
154
+ // Simulate delete operation with mark as read instead
155
+ await this.ms365Ops.markEmail(id, true);
156
+ traditionalResults.deleted++;
157
+ }
158
+ catch (error) {
159
+ traditionalResults.errors++;
160
+ }
161
+ }
162
+ const traditionalMetrics = performanceMonitor.endOperation('mixed_operations', 'mixed_operations', messageIds.length, 'traditional', traditionalStart, messageIds.length, // one call per operation
163
+ true);
164
+ // Wait a moment to avoid rate limiting
165
+ await new Promise(resolve => setTimeout(resolve, 1000));
166
+ // Batched approach
167
+ const batchedStart = Date.now();
168
+ const mixedOperations = [
169
+ ...emailsToMark.map((id, index) => ({
170
+ id: `mark_${index}`,
171
+ operation: 'mark',
172
+ messageId: id,
173
+ params: { isRead: true }
174
+ })),
175
+ ...emailsToMove.map((id, index) => ({
176
+ id: `move_${index}`,
177
+ operation: 'move',
178
+ messageId: id,
179
+ params: { destinationFolderId: 'archive' }
180
+ })),
181
+ // Simulate delete with mark for testing
182
+ ...emailsToDelete.map((id, index) => ({
183
+ id: `delete_${index}`,
184
+ operation: 'mark',
185
+ messageId: id,
186
+ params: { isRead: true }
187
+ }))
188
+ ];
189
+ const batchedResults = await this.ms365Ops.batchEmailOperations(mixedOperations);
190
+ const batchedMetrics = performanceMonitor.endOperation('mixed_operations', 'mixed_operations', messageIds.length, 'batched', batchedStart, Math.ceil(mixedOperations.length / 20), // 20 operations per batch
191
+ true);
192
+ const comparison = performanceMonitor.comparePerformance('mixed_operations');
193
+ return {
194
+ traditional: { results: traditionalResults, metrics: traditionalMetrics },
195
+ batched: { results: Array.from(batchedResults.entries()), metrics: batchedMetrics },
196
+ comparison
197
+ };
198
+ }
199
+ /**
200
+ * Run all test scenarios
201
+ */
202
+ async runAllTests(messageIds) {
203
+ logger.log(`šŸš€ STARTING COMPREHENSIVE BATCH PERFORMANCE TESTS`);
204
+ logger.log(`šŸ“§ Using ${messageIds.length} test emails`);
205
+ performanceMonitor.clear();
206
+ const results = {
207
+ bulkRetrieval: null,
208
+ bulkOperations: null,
209
+ folderWithEmails: null,
210
+ mixedOperations: null
211
+ };
212
+ try {
213
+ // Test 1: Bulk Email Retrieval
214
+ if (messageIds.length >= 5) {
215
+ const testIds = messageIds.slice(0, Math.min(10, messageIds.length));
216
+ results.bulkRetrieval = await this.testBulkEmailRetrieval(testIds);
217
+ }
218
+ // Test 2: Bulk Email Operations
219
+ if (messageIds.length >= 5) {
220
+ const testIds = messageIds.slice(0, Math.min(15, messageIds.length));
221
+ results.bulkOperations = await this.testBulkEmailOperations(testIds);
222
+ }
223
+ // Test 3: Folder + Emails
224
+ results.folderWithEmails = await this.testFolderWithEmails('inbox', 10);
225
+ // Test 4: Mixed Operations
226
+ if (messageIds.length >= 9) {
227
+ const testIds = messageIds.slice(0, Math.min(12, messageIds.length));
228
+ results.mixedOperations = await this.testMixedOperations(testIds);
229
+ }
230
+ }
231
+ catch (error) {
232
+ logger.error('Error during batch testing:', error);
233
+ }
234
+ // Generate comprehensive report
235
+ const report = this.generateTestReport(results);
236
+ logger.log(report);
237
+ return report;
238
+ }
239
+ /**
240
+ * Generate comprehensive test report
241
+ */
242
+ generateTestReport(results) {
243
+ let report = `\nšŸ† BATCH PERFORMANCE TEST RESULTS\n`;
244
+ report += `${'='.repeat(50)}\n\n`;
245
+ if (results.bulkRetrieval?.comparison) {
246
+ const comp = results.bulkRetrieval.comparison;
247
+ report += `šŸ“§ BULK EMAIL RETRIEVAL\n`;
248
+ report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
249
+ report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
250
+ report += ` šŸ’” Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
251
+ }
252
+ if (results.bulkOperations?.comparison) {
253
+ const comp = results.bulkOperations.comparison;
254
+ report += `⚔ BULK EMAIL OPERATIONS\n`;
255
+ report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
256
+ report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
257
+ report += ` šŸ’” Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
258
+ }
259
+ if (results.folderWithEmails?.comparison) {
260
+ const comp = results.folderWithEmails.comparison;
261
+ report += `šŸ“ FOLDER + EMAILS FETCHING\n`;
262
+ report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
263
+ report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
264
+ report += ` šŸ’” Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
265
+ }
266
+ if (results.mixedOperations?.comparison) {
267
+ const comp = results.mixedOperations.comparison;
268
+ report += `šŸ”„ MIXED OPERATIONS WORKFLOW\n`;
269
+ report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
270
+ report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
271
+ report += ` šŸ’” Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
272
+ }
273
+ report += `šŸ“Š OVERALL PERFORMANCE INSIGHTS:\n`;
274
+ report += performanceMonitor.generateReport();
275
+ return report;
276
+ }
277
+ }