@rachelallyson/planning-center-people-ts 1.1.0 → 2.1.0

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/README.md +16 -0
  3. package/dist/batch.d.ts +47 -0
  4. package/dist/batch.js +376 -0
  5. package/dist/client-manager.d.ts +66 -0
  6. package/dist/client-manager.js +156 -0
  7. package/dist/client.d.ts +71 -0
  8. package/dist/client.js +123 -0
  9. package/dist/core/http.d.ts +48 -0
  10. package/dist/core/http.js +265 -0
  11. package/dist/core/pagination.d.ts +34 -0
  12. package/dist/core/pagination.js +164 -0
  13. package/dist/index.d.ts +13 -3
  14. package/dist/index.js +23 -5
  15. package/dist/matching/matcher.d.ts +41 -0
  16. package/dist/matching/matcher.js +161 -0
  17. package/dist/matching/scoring.d.ts +35 -0
  18. package/dist/matching/scoring.js +141 -0
  19. package/dist/matching/strategies.d.ts +35 -0
  20. package/dist/matching/strategies.js +79 -0
  21. package/dist/modules/base.d.ts +46 -0
  22. package/dist/modules/base.js +82 -0
  23. package/dist/modules/contacts.d.ts +103 -0
  24. package/dist/modules/contacts.js +130 -0
  25. package/dist/modules/fields.d.ts +157 -0
  26. package/dist/modules/fields.js +294 -0
  27. package/dist/modules/households.d.ts +42 -0
  28. package/dist/modules/households.js +74 -0
  29. package/dist/modules/lists.d.ts +62 -0
  30. package/dist/modules/lists.js +92 -0
  31. package/dist/modules/notes.d.ts +74 -0
  32. package/dist/modules/notes.js +125 -0
  33. package/dist/modules/people.d.ts +196 -0
  34. package/dist/modules/people.js +221 -0
  35. package/dist/modules/workflows.d.ts +131 -0
  36. package/dist/modules/workflows.js +221 -0
  37. package/dist/monitoring.d.ts +53 -0
  38. package/dist/monitoring.js +142 -0
  39. package/dist/testing/index.d.ts +9 -0
  40. package/dist/testing/index.js +24 -0
  41. package/dist/testing/recorder.d.ts +58 -0
  42. package/dist/testing/recorder.js +195 -0
  43. package/dist/testing/simple-builders.d.ts +33 -0
  44. package/dist/testing/simple-builders.js +124 -0
  45. package/dist/testing/simple-factories.d.ts +91 -0
  46. package/dist/testing/simple-factories.js +288 -0
  47. package/dist/testing/types.d.ts +160 -0
  48. package/dist/testing/types.js +5 -0
  49. package/dist/types/batch.d.ts +50 -0
  50. package/dist/types/batch.js +5 -0
  51. package/dist/types/client.d.ts +89 -0
  52. package/dist/types/client.js +5 -0
  53. package/dist/types/events.d.ts +85 -0
  54. package/dist/types/events.js +5 -0
  55. package/dist/types/people.d.ts +20 -1
  56. package/package.json +9 -3
package/CHANGELOG.md CHANGED
@@ -5,6 +5,187 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.1.0] - 2025-01-17
9
+
10
+ ### 🔒 **SECURITY RELEASE - Required Refresh Token Handling**
11
+
12
+ This release addresses a critical security issue where OAuth 2.0 clients could lose access when tokens expire without proper refresh handling.
13
+
14
+ ### Breaking Changes
15
+
16
+ - **OAuth 2.0 Authentication**: `onRefresh` and `onRefreshFailure` callbacks are now **required** for OAuth configurations
17
+ - **Type Safety**: Enhanced type-safe authentication configuration prevents invalid configurations at compile time
18
+
19
+ ### Security
20
+
21
+ - **CRITICAL**: OAuth 2.0 authentication now requires refresh token handling to prevent token loss
22
+ - **BREAKING**: Type-safe authentication configuration enforces required fields
23
+ - Enhanced token refresh implementation with proper error handling
24
+ - Improved authentication type safety with union types
25
+
26
+ ### Fixed
27
+
28
+ - Fixed person matching to properly handle default fuzzy strategy
29
+ - Fixed mock client to support createWithContacts method
30
+ - Fixed event system tests to work with mock client
31
+ - Fixed phone number builder in mock response builder
32
+
33
+ ### Migration from v2.0.0
34
+
35
+ **Before (v2.0.0):**
36
+
37
+ ```typescript
38
+ const client = new PcoClient({
39
+ auth: {
40
+ type: 'oauth',
41
+ accessToken: 'access-token',
42
+ refreshToken: 'refresh-token'
43
+ // Missing required callbacks - this will now cause TypeScript errors
44
+ }
45
+ });
46
+ ```
47
+
48
+ **After (v2.1.0):**
49
+
50
+ ```typescript
51
+ const client = new PcoClient({
52
+ auth: {
53
+ type: 'oauth',
54
+ accessToken: 'access-token',
55
+ refreshToken: 'refresh-token',
56
+ // REQUIRED: Handle token refresh to prevent token loss
57
+ onRefresh: async (tokens) => {
58
+ await saveTokensToDatabase(userId, tokens);
59
+ },
60
+ // REQUIRED: Handle refresh failures
61
+ onRefreshFailure: async (error) => {
62
+ console.error('Token refresh failed:', error.message);
63
+ await clearUserTokens(userId);
64
+ }
65
+ }
66
+ });
67
+ ```
68
+
69
+ ## [2.0.0] - 2025-01-17
70
+
71
+ ### 🚀 **MAJOR RELEASE - Complete API Redesign**
72
+
73
+ This is a **breaking change** release that completely redesigns the API for better developer experience, type safety, and maintainability.
74
+
75
+ ### Added
76
+
77
+ #### **🏗️ New Class-Based Architecture**
78
+
79
+ - **PcoClient Class**: Main client with modular architecture
80
+ - **PcoClientManager**: Automatic client caching and lifecycle management
81
+ - **Event System**: Comprehensive event emission for monitoring and debugging
82
+ - **Module Architecture**: Organized API interactions into focused modules
83
+
84
+ #### **🔧 Core Utilities**
85
+
86
+ - **Built-in Pagination**: `getAllPages()` method for automatic pagination
87
+ - **Batch Operations**: Execute multiple operations with dependency resolution
88
+ - **Person Matching**: Smart person matching with fuzzy logic and `findOrCreate`
89
+ - **Type-Safe Field Operations**: Enhanced custom field operations with caching
90
+ - **Workflow State Management**: Smart workflow operations with duplicate detection
91
+
92
+ #### **📦 New Modules**
93
+
94
+ - **PeopleModule**: Core person operations with smart matching
95
+ - **FieldsModule**: Type-safe custom field operations with caching
96
+ - **WorkflowsModule**: Complete workflow and workflow card management
97
+ - **ContactsModule**: Email, phone, address, and social profile management
98
+ - **HouseholdsModule**: Household operations and member management
99
+ - **NotesModule**: Note and note category operations
100
+ - **ListsModule**: List and list category operations with rule-based membership
101
+
102
+ #### **🔐 Enhanced Authentication**
103
+
104
+ - **OAuth 2.0 Support**: Full OAuth with automatic token refresh
105
+ - **Personal Access Token**: HTTP Basic Auth support
106
+ - **Token Refresh**: Automatic refresh with callback support
107
+ - **Environment Persistence**: Automatic token persistence in test environments
108
+
109
+ #### **⚡ Performance & Reliability**
110
+
111
+ - **Rate Limiting**: Built-in rate limiting (100 req/min)
112
+ - **Error Handling**: Comprehensive error handling with retry logic
113
+ - **Request Timeouts**: Configurable request timeouts
114
+ - **Event Monitoring**: Real-time request/response monitoring
115
+
116
+ #### **🧪 Testing Infrastructure**
117
+
118
+ - **MockPcoClient**: Complete mock implementation for testing
119
+ - **MockResponseBuilder**: Response building utilities
120
+ - **RequestRecorder**: Request recording for testing
121
+ - **Integration Tests**: 129 comprehensive integration tests
122
+
123
+ ### Changed
124
+
125
+ #### **🔄 Breaking Changes**
126
+
127
+ - **API Design**: Complete redesign from functional to class-based approach
128
+ - **Import Structure**: New import structure with `PcoClient` class
129
+ - **Method Names**: Updated method names for consistency
130
+ - **Type Definitions**: Enhanced type definitions with better type safety
131
+
132
+ #### **📈 Improvements**
133
+
134
+ - **Type Safety**: Enhanced TypeScript support with strict typing
135
+ - **Error Messages**: More descriptive error messages and handling
136
+ - **Documentation**: Comprehensive inline documentation
137
+ - **Performance**: Optimized request handling and caching
138
+
139
+ ### Migration Guide
140
+
141
+ #### **Before (v1.x)**
142
+
143
+ ```typescript
144
+ import { createPcoClient, getPeople, createPerson } from '@rachelallyson/planning-center-people-ts';
145
+
146
+ const client = createPcoClient({
147
+ personalAccessToken: 'your-token',
148
+ appId: 'your-app-id',
149
+ appSecret: 'your-app-secret'
150
+ });
151
+
152
+ const people = await getPeople(client, { per_page: 10 });
153
+ const person = await createPerson(client, { first_name: 'John', last_name: 'Doe' });
154
+ ```
155
+
156
+ #### **After (v2.0.0)**
157
+
158
+ ```typescript
159
+ import { PcoClient } from '@rachelallyson/planning-center-people-ts';
160
+
161
+ const client = new PcoClient({
162
+ auth: {
163
+ type: 'personal_access_token',
164
+ personalAccessToken: 'your-token'
165
+ }
166
+ });
167
+
168
+ const people = await client.people.getAll({ perPage: 10 });
169
+ const person = await client.people.create({ first_name: 'John', last_name: 'Doe' });
170
+ ```
171
+
172
+ ### Removed
173
+
174
+ - **Functional API**: All functional API methods removed in favor of class-based approach
175
+ - **Legacy Types**: Old type definitions replaced with enhanced versions
176
+ - **Deprecated Methods**: All deprecated methods removed
177
+
178
+ ### Fixed
179
+
180
+ - **Type Safety**: Resolved all TypeScript strict mode issues
181
+ - **Error Handling**: Improved error handling and retry logic
182
+ - **Rate Limiting**: Fixed rate limiting edge cases
183
+ - **Authentication**: Resolved token refresh and persistence issues
184
+ - Fixed person matching to properly handle default fuzzy strategy
185
+ - Fixed mock client to support createWithContacts method
186
+ - Fixed event system tests to work with mock client
187
+ - Fixed phone number builder in mock response builder
188
+
8
189
  ## [1.1.0] - 2025-10-08
9
190
 
10
191
  ### Added
package/README.md CHANGED
@@ -515,6 +515,22 @@ See [TYPE_VALIDATION_SUMMARY.md](./TYPE_VALIDATION_SUMMARY.md) for detailed docu
515
515
  4. Add tests (both unit and integration)
516
516
  5. Submit a pull request
517
517
 
518
+ ## 📚 Comprehensive Documentation
519
+
520
+ This library includes extensive documentation covering all aspects of usage:
521
+
522
+ - **[📖 Complete Documentation](./docs/README.md)** - Comprehensive guide covering all features
523
+ - **[🚀 Getting Started](./docs/OVERVIEW.md)** - What this library does and why you should use it
524
+ - **[⚙️ Installation Guide](./docs/INSTALLATION.md)** - Complete setup instructions for all environments
525
+ - **[🔐 Authentication Guide](./docs/AUTHENTICATION.md)** - All authentication methods and token management
526
+ - **[📋 API Reference](./docs/API_REFERENCE.md)** - Complete reference for all 40+ functions
527
+ - **[💡 Examples & Patterns](./docs/EXAMPLES.md)** - Real-world examples and common patterns
528
+ - **[🛠️ Error Handling](./docs/ERROR_HANDLING.md)** - Advanced error management and recovery
529
+ - **[⚡ Performance Guide](./docs/PERFORMANCE.md)** - Optimization techniques and bulk operations
530
+ - **[🔧 Troubleshooting](./docs/TROUBLESHOOTING.md)** - Common issues and solutions
531
+ - **[🔄 Migration Guide](./docs/MIGRATION.md)** - Switching from other libraries
532
+ - **[⭐ Best Practices](./docs/BEST_PRACTICES.md)** - Production-ready patterns and security
533
+
518
534
  ## License
519
535
 
520
536
  MIT
@@ -0,0 +1,47 @@
1
+ /**
2
+ * v2.0.0 Batch Operations Executor
3
+ */
4
+ import type { PcoClient } from './client';
5
+ import type { PcoEventEmitter } from './monitoring';
6
+ import type { BatchOperation, BatchOptions, BatchSummary } from './types/batch';
7
+ export declare class BatchExecutor {
8
+ private client;
9
+ private eventEmitter;
10
+ constructor(client: PcoClient, eventEmitter: PcoEventEmitter);
11
+ /**
12
+ * Execute a batch of operations
13
+ */
14
+ execute<T = any>(operations: BatchOperation[], options?: BatchOptions): Promise<BatchSummary>;
15
+ /**
16
+ * Resolve operation dependencies and references
17
+ */
18
+ private resolveOperations;
19
+ /**
20
+ * Resolve references in operation data
21
+ */
22
+ private resolveReferences;
23
+ /**
24
+ * Resolve string references like "$0.id" or "$1.data.attributes.name"
25
+ */
26
+ private resolveStringReferences;
27
+ /**
28
+ * Get nested value from object using dot notation
29
+ */
30
+ private getNestedValue;
31
+ /**
32
+ * Find dependencies for an operation
33
+ */
34
+ private findDependencies;
35
+ /**
36
+ * Execute a single operation
37
+ */
38
+ private executeOperation;
39
+ /**
40
+ * Rollback successful operations
41
+ */
42
+ private rollbackOperations;
43
+ /**
44
+ * Rollback a single operation
45
+ */
46
+ private rollbackOperation;
47
+ }
package/dist/batch.js ADDED
@@ -0,0 +1,376 @@
1
+ "use strict";
2
+ /**
3
+ * v2.0.0 Batch Operations Executor
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BatchExecutor = void 0;
7
+ class BatchExecutor {
8
+ constructor(client, eventEmitter) {
9
+ this.client = client;
10
+ this.eventEmitter = eventEmitter;
11
+ }
12
+ /**
13
+ * Execute a batch of operations
14
+ */
15
+ async execute(operations, options = {}) {
16
+ const { continueOnError = true, maxConcurrency = 5, enableRollback = false, onOperationComplete, onBatchComplete, } = options;
17
+ const startTime = Date.now();
18
+ const results = [];
19
+ const successfulOperations = [];
20
+ try {
21
+ // Resolve operation dependencies and references
22
+ const resolvedOperations = await this.resolveOperations(operations);
23
+ // Execute operations with dependency resolution
24
+ const semaphore = new Semaphore(maxConcurrency);
25
+ const operationResults = new Map();
26
+ const executeOperationWithDependencies = async (operation, index) => {
27
+ // Wait for dependencies to complete
28
+ if (operation.dependencies && operation.dependencies.length > 0) {
29
+ const dependencyPromises = operation.dependencies.map(depId => {
30
+ // Handle index-based dependencies
31
+ if (depId.startsWith('$index_')) {
32
+ const depIndex = parseInt(depId.substring(7));
33
+ const depOperation = resolvedOperations[depIndex];
34
+ if (!depOperation) {
35
+ throw new Error(`Dependency at index ${depIndex} not found`);
36
+ }
37
+ const depResult = operationResults.get(depOperation.id);
38
+ if (!depResult) {
39
+ throw new Error(`Dependency '${depOperation.id}' not found in operations list`);
40
+ }
41
+ return depResult;
42
+ }
43
+ else {
44
+ // Handle operation ID-based dependencies
45
+ const depResult = operationResults.get(depId);
46
+ if (!depResult) {
47
+ throw new Error(`Dependency '${depId}' not found in operations list`);
48
+ }
49
+ return depResult;
50
+ }
51
+ });
52
+ await Promise.all(dependencyPromises);
53
+ }
54
+ await semaphore.acquire();
55
+ try {
56
+ // Create a function to get current results for reference resolution
57
+ const getCurrentResults = () => results;
58
+ const result = await this.executeOperation(operation, getCurrentResults);
59
+ const batchResult = {
60
+ index,
61
+ operation,
62
+ success: true,
63
+ data: result,
64
+ };
65
+ results.push(batchResult);
66
+ successfulOperations.push(operation);
67
+ onOperationComplete?.(batchResult);
68
+ return batchResult;
69
+ }
70
+ catch (error) {
71
+ const batchResult = {
72
+ index,
73
+ operation,
74
+ success: false,
75
+ error: error,
76
+ };
77
+ results.push(batchResult);
78
+ if (!continueOnError) {
79
+ throw error;
80
+ }
81
+ onOperationComplete?.(batchResult);
82
+ return batchResult;
83
+ }
84
+ finally {
85
+ semaphore.release();
86
+ }
87
+ };
88
+ // Create promises for all operations
89
+ const operationPromises = resolvedOperations.map((operation, index) => {
90
+ const promise = executeOperationWithDependencies(operation, index);
91
+ operationResults.set(operation.id, promise);
92
+ return promise;
93
+ });
94
+ await Promise.all(operationPromises);
95
+ const summary = {
96
+ total: operations.length,
97
+ successful: results.filter(r => r.success).length,
98
+ failed: results.filter(r => !r.success).length,
99
+ successRate: results.length > 0 ? results.filter(r => r.success).length / results.length : 0,
100
+ duration: Date.now() - startTime,
101
+ results,
102
+ };
103
+ onBatchComplete?.(results);
104
+ return summary;
105
+ }
106
+ catch (error) {
107
+ // Rollback successful operations if enabled
108
+ if (enableRollback && successfulOperations.length > 0) {
109
+ await this.rollbackOperations(successfulOperations);
110
+ }
111
+ throw error;
112
+ }
113
+ }
114
+ /**
115
+ * Resolve operation dependencies and references
116
+ */
117
+ async resolveOperations(operations) {
118
+ const resolved = [];
119
+ for (let i = 0; i < operations.length; i++) {
120
+ const operation = operations[i];
121
+ const resolvedOperation = { ...operation };
122
+ // Resolve references in data
123
+ if (operation.data) {
124
+ resolvedOperation.resolvedData = await this.resolveReferences(operation.data, resolved);
125
+ }
126
+ // Determine dependencies
127
+ resolvedOperation.dependencies = this.findDependencies(operation, i);
128
+ resolved.push(resolvedOperation);
129
+ }
130
+ return resolved;
131
+ }
132
+ /**
133
+ * Resolve references in operation data
134
+ */
135
+ async resolveReferences(data, previousResults) {
136
+ if (typeof data === 'string') {
137
+ // Convert ResolvedBatchOperation[] to BatchResult[] for resolveStringReferences
138
+ const batchResults = previousResults.map((op, index) => ({
139
+ index,
140
+ operation: op,
141
+ success: true,
142
+ data: op.resolvedData || op.data,
143
+ }));
144
+ return this.resolveStringReferences(data, batchResults);
145
+ }
146
+ if (Array.isArray(data)) {
147
+ return Promise.all(data.map(item => this.resolveReferences(item, previousResults)));
148
+ }
149
+ if (data && typeof data === 'object') {
150
+ const resolved = {};
151
+ for (const [key, value] of Object.entries(data)) {
152
+ resolved[key] = await this.resolveReferences(value, previousResults);
153
+ }
154
+ return resolved;
155
+ }
156
+ return data;
157
+ }
158
+ /**
159
+ * Resolve string references like "$0.id" or "$1.data.attributes.name"
160
+ */
161
+ resolveStringReferences(str, previousResults) {
162
+ return str.replace(/\$(\d+)\.([\w.]+)/g, (match, indexStr, path) => {
163
+ const index = parseInt(indexStr);
164
+ if (index < previousResults.length) {
165
+ const result = previousResults[index];
166
+ // For simple references like $0.id, look in result.data.id
167
+ if (path === 'id' && result.data?.id) {
168
+ return String(result.data.id);
169
+ }
170
+ // For other paths, look in result.data
171
+ const value = this.getNestedValue(result.data, path);
172
+ return value !== undefined ? String(value) : match;
173
+ }
174
+ return match;
175
+ });
176
+ }
177
+ /**
178
+ * Get nested value from object using dot notation
179
+ */
180
+ getNestedValue(obj, path) {
181
+ return path.split('.').reduce((current, key) => {
182
+ return current && current[key] !== undefined ? current[key] : undefined;
183
+ }, obj);
184
+ }
185
+ /**
186
+ * Find dependencies for an operation
187
+ */
188
+ findDependencies(operation, currentIndex) {
189
+ const dependencies = [];
190
+ // If operation has explicit dependencies, use those
191
+ if (operation.dependencies && Array.isArray(operation.dependencies)) {
192
+ dependencies.push(...operation.dependencies);
193
+ }
194
+ // Also find references to previous operations by index
195
+ const operationStr = JSON.stringify(operation);
196
+ const referenceMatches = operationStr.match(/\$(\d+)/g);
197
+ if (referenceMatches) {
198
+ for (const match of referenceMatches) {
199
+ const index = parseInt(match.substring(1));
200
+ if (index < currentIndex) {
201
+ // Convert index to operation ID reference
202
+ const depRef = `$index_${index}`;
203
+ if (!dependencies.includes(depRef)) {
204
+ dependencies.push(depRef);
205
+ }
206
+ }
207
+ }
208
+ }
209
+ return dependencies;
210
+ }
211
+ /**
212
+ * Execute a single operation
213
+ */
214
+ async executeOperation(operation, getPreviousResults) {
215
+ const { type, resolvedData, data, endpoint, resourceType } = operation;
216
+ const operationData = resolvedData || data;
217
+ // Check if type is module.method format
218
+ if (type.includes('.')) {
219
+ // Existing module.method approach
220
+ const [module, method] = type.split('.');
221
+ if (!module || !method) {
222
+ throw new Error(`Invalid operation type: ${type}`);
223
+ }
224
+ // Get the appropriate module
225
+ const moduleInstance = this.client[module];
226
+ if (!moduleInstance) {
227
+ throw new Error(`Unknown module: ${module}`);
228
+ }
229
+ // Execute the method
230
+ const methodFunc = moduleInstance[method];
231
+ if (typeof methodFunc !== 'function') {
232
+ throw new Error(`Unknown method: ${module}.${method}`);
233
+ }
234
+ return methodFunc.call(moduleInstance, operationData);
235
+ }
236
+ else {
237
+ // New endpoint-based approach
238
+ const methodMap = {
239
+ 'create': 'POST',
240
+ 'update': 'PATCH',
241
+ 'delete': 'DELETE'
242
+ };
243
+ const httpMethod = methodMap[type];
244
+ if (!httpMethod || !endpoint) {
245
+ throw new Error(`Invalid operation: ${type} requires endpoint`);
246
+ }
247
+ // Resolve endpoint references
248
+ const previousResults = getPreviousResults();
249
+ const resolvedEndpoint = this.resolveStringReferences(endpoint, previousResults);
250
+ // Map endpoint to module and method
251
+ if (resolvedEndpoint === '/people') {
252
+ // /people endpoint
253
+ if (type === 'create') {
254
+ return this.client.people.create(operationData);
255
+ }
256
+ }
257
+ else if (resolvedEndpoint.startsWith('/people/') && resolvedEndpoint.includes('/emails')) {
258
+ // /people/{id}/emails endpoint
259
+ if (type === 'create') {
260
+ const personId = resolvedEndpoint.split('/')[2];
261
+ return this.client.people.addEmail(personId, operationData);
262
+ }
263
+ else if (type === 'update') {
264
+ const parts = resolvedEndpoint.split('/');
265
+ const personId = parts[2];
266
+ const emailId = parts[4];
267
+ return this.client.people.updateEmail(personId, emailId, operationData);
268
+ }
269
+ else if (type === 'delete') {
270
+ const parts = resolvedEndpoint.split('/');
271
+ const personId = parts[2];
272
+ const emailId = parts[4];
273
+ return this.client.people.deleteEmail(personId, emailId);
274
+ }
275
+ }
276
+ else if (resolvedEndpoint.startsWith('/people/') && resolvedEndpoint.includes('/phone_numbers')) {
277
+ // /people/{id}/phone_numbers endpoint
278
+ if (type === 'create') {
279
+ const personId = resolvedEndpoint.split('/')[2];
280
+ return this.client.people.addPhoneNumber(personId, operationData);
281
+ }
282
+ else if (type === 'update') {
283
+ const parts = resolvedEndpoint.split('/');
284
+ const personId = parts[2];
285
+ const phoneId = parts[4];
286
+ return this.client.people.updatePhoneNumber(personId, phoneId, operationData);
287
+ }
288
+ else if (type === 'delete') {
289
+ const parts = resolvedEndpoint.split('/');
290
+ const personId = parts[2];
291
+ const phoneId = parts[4];
292
+ return this.client.people.deletePhoneNumber(personId, phoneId);
293
+ }
294
+ }
295
+ else if (resolvedEndpoint.startsWith('/people/') && !resolvedEndpoint.includes('/emails') && !resolvedEndpoint.includes('/phone_numbers')) {
296
+ // /people/{id} endpoint
297
+ if (type === 'update') {
298
+ const personId = resolvedEndpoint.split('/')[2];
299
+ return this.client.people.update(personId, operationData);
300
+ }
301
+ else if (type === 'delete') {
302
+ const personId = resolvedEndpoint.split('/')[2];
303
+ return this.client.people.delete(personId);
304
+ }
305
+ }
306
+ throw new Error(`Unsupported endpoint for batch operation: ${resolvedEndpoint}`);
307
+ }
308
+ }
309
+ /**
310
+ * Rollback successful operations
311
+ */
312
+ async rollbackOperations(operations) {
313
+ // Reverse the order for rollback
314
+ const rollbackOps = [...operations].reverse();
315
+ for (const operation of rollbackOps) {
316
+ try {
317
+ await this.rollbackOperation(operation);
318
+ }
319
+ catch (error) {
320
+ console.error(`Failed to rollback operation ${operation.type}:`, error);
321
+ }
322
+ }
323
+ }
324
+ /**
325
+ * Rollback a single operation
326
+ */
327
+ async rollbackOperation(operation) {
328
+ const { type } = operation;
329
+ const [module, method] = type.split('.');
330
+ // Determine rollback method based on operation type
331
+ let rollbackMethod;
332
+ if (method.startsWith('create')) {
333
+ rollbackMethod = method.replace('create', 'delete');
334
+ }
335
+ else if (method.startsWith('add')) {
336
+ rollbackMethod = method.replace('add', 'delete');
337
+ }
338
+ else {
339
+ // For other operations, we might need to implement specific rollback logic
340
+ return;
341
+ }
342
+ const moduleInstance = this.client[module];
343
+ if (moduleInstance && typeof moduleInstance[rollbackMethod] === 'function') {
344
+ // This is a simplified rollback - in practice, you'd need to store
345
+ // the created resource IDs to properly rollback
346
+ console.warn(`Rollback not fully implemented for ${type}`);
347
+ }
348
+ }
349
+ }
350
+ exports.BatchExecutor = BatchExecutor;
351
+ /**
352
+ * Semaphore for controlling concurrency
353
+ */
354
+ class Semaphore {
355
+ constructor(permits) {
356
+ this.waiting = [];
357
+ this.permits = permits;
358
+ }
359
+ async acquire() {
360
+ if (this.permits > 0) {
361
+ this.permits--;
362
+ return;
363
+ }
364
+ return new Promise(resolve => {
365
+ this.waiting.push(resolve);
366
+ });
367
+ }
368
+ release() {
369
+ this.permits++;
370
+ if (this.waiting.length > 0) {
371
+ const resolve = this.waiting.shift();
372
+ this.permits--;
373
+ resolve();
374
+ }
375
+ }
376
+ }