@rachelallyson/planning-center-people-ts 1.1.0 → 2.0.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 +116 -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 +150 -0
  7. package/dist/client.d.ts +71 -0
  8. package/dist/client.js +123 -0
  9. package/dist/core/http.d.ts +47 -0
  10. package/dist/core/http.js +242 -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 +279 -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 +81 -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,122 @@ 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.0.0] - 2025-01-17
9
+
10
+ ### 🚀 **MAJOR RELEASE - Complete API Redesign**
11
+
12
+ This is a **breaking change** release that completely redesigns the API for better developer experience, type safety, and maintainability.
13
+
14
+ ### Added
15
+
16
+ #### **🏗️ New Class-Based Architecture**
17
+
18
+ - **PcoClient Class**: Main client with modular architecture
19
+ - **PcoClientManager**: Automatic client caching and lifecycle management
20
+ - **Event System**: Comprehensive event emission for monitoring and debugging
21
+ - **Module Architecture**: Organized API interactions into focused modules
22
+
23
+ #### **🔧 Core Utilities**
24
+
25
+ - **Built-in Pagination**: `getAllPages()` method for automatic pagination
26
+ - **Batch Operations**: Execute multiple operations with dependency resolution
27
+ - **Person Matching**: Smart person matching with fuzzy logic and `findOrCreate`
28
+ - **Type-Safe Field Operations**: Enhanced custom field operations with caching
29
+ - **Workflow State Management**: Smart workflow operations with duplicate detection
30
+
31
+ #### **📦 New Modules**
32
+
33
+ - **PeopleModule**: Core person operations with smart matching
34
+ - **FieldsModule**: Type-safe custom field operations with caching
35
+ - **WorkflowsModule**: Complete workflow and workflow card management
36
+ - **ContactsModule**: Email, phone, address, and social profile management
37
+ - **HouseholdsModule**: Household operations and member management
38
+ - **NotesModule**: Note and note category operations
39
+ - **ListsModule**: List and list category operations with rule-based membership
40
+
41
+ #### **🔐 Enhanced Authentication**
42
+
43
+ - **OAuth 2.0 Support**: Full OAuth with automatic token refresh
44
+ - **Personal Access Token**: HTTP Basic Auth support
45
+ - **Token Refresh**: Automatic refresh with callback support
46
+ - **Environment Persistence**: Automatic token persistence in test environments
47
+
48
+ #### **⚡ Performance & Reliability**
49
+
50
+ - **Rate Limiting**: Built-in rate limiting (100 req/min)
51
+ - **Error Handling**: Comprehensive error handling with retry logic
52
+ - **Request Timeouts**: Configurable request timeouts
53
+ - **Event Monitoring**: Real-time request/response monitoring
54
+
55
+ #### **🧪 Testing Infrastructure**
56
+
57
+ - **MockPcoClient**: Complete mock implementation for testing
58
+ - **MockResponseBuilder**: Response building utilities
59
+ - **RequestRecorder**: Request recording for testing
60
+ - **Integration Tests**: 129 comprehensive integration tests
61
+
62
+ ### Changed
63
+
64
+ #### **🔄 Breaking Changes**
65
+
66
+ - **API Design**: Complete redesign from functional to class-based approach
67
+ - **Import Structure**: New import structure with `PcoClient` class
68
+ - **Method Names**: Updated method names for consistency
69
+ - **Type Definitions**: Enhanced type definitions with better type safety
70
+
71
+ #### **📈 Improvements**
72
+
73
+ - **Type Safety**: Enhanced TypeScript support with strict typing
74
+ - **Error Messages**: More descriptive error messages and handling
75
+ - **Documentation**: Comprehensive inline documentation
76
+ - **Performance**: Optimized request handling and caching
77
+
78
+ ### Migration Guide
79
+
80
+ #### **Before (v1.x)**
81
+
82
+ ```typescript
83
+ import { createPcoClient, getPeople, createPerson } from '@rachelallyson/planning-center-people-ts';
84
+
85
+ const client = createPcoClient({
86
+ personalAccessToken: 'your-token',
87
+ appId: 'your-app-id',
88
+ appSecret: 'your-app-secret'
89
+ });
90
+
91
+ const people = await getPeople(client, { per_page: 10 });
92
+ const person = await createPerson(client, { first_name: 'John', last_name: 'Doe' });
93
+ ```
94
+
95
+ #### **After (v2.0.0)**
96
+
97
+ ```typescript
98
+ import { PcoClient } from '@rachelallyson/planning-center-people-ts';
99
+
100
+ const client = new PcoClient({
101
+ auth: {
102
+ type: 'personal_access_token',
103
+ personalAccessToken: 'your-token'
104
+ }
105
+ });
106
+
107
+ const people = await client.people.getAll({ perPage: 10 });
108
+ const person = await client.people.create({ first_name: 'John', last_name: 'Doe' });
109
+ ```
110
+
111
+ ### Removed
112
+
113
+ - **Functional API**: All functional API methods removed in favor of class-based approach
114
+ - **Legacy Types**: Old type definitions replaced with enhanced versions
115
+ - **Deprecated Methods**: All deprecated methods removed
116
+
117
+ ### Fixed
118
+
119
+ - **Type Safety**: Resolved all TypeScript strict mode issues
120
+ - **Error Handling**: Improved error handling and retry logic
121
+ - **Rate Limiting**: Fixed rate limiting edge cases
122
+ - **Authentication**: Resolved token refresh and persistence issues
123
+
8
124
  ## [1.1.0] - 2025-10-08
9
125
 
10
126
  ### 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
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * v2.0.0 Client Manager with Caching
3
+ */
4
+ import type { PcoClientConfig } from './types/client';
5
+ import { PcoClient } from './client';
6
+ export interface ClientConfigResolver {
7
+ (churchId: string): Promise<PcoClientConfig> | PcoClientConfig;
8
+ }
9
+ export declare class PcoClientManager {
10
+ private static instance;
11
+ private clientCache;
12
+ private configCache;
13
+ private constructor();
14
+ /**
15
+ * Get the singleton instance
16
+ */
17
+ static getInstance(): PcoClientManager;
18
+ /**
19
+ * Get a client instance with the given configuration
20
+ */
21
+ static getClient(config: PcoClientConfig): PcoClient;
22
+ /**
23
+ * Get a client instance for a specific church with config resolution
24
+ */
25
+ static getClientForChurch(churchId: string, configResolver: ClientConfigResolver): Promise<PcoClient>;
26
+ /**
27
+ * Clear the client cache
28
+ */
29
+ static clearCache(): void;
30
+ /**
31
+ * Get a client instance with caching
32
+ */
33
+ getClient(config: PcoClientConfig): PcoClient;
34
+ /**
35
+ * Get a client instance for a specific church
36
+ */
37
+ getClientForChurch(churchId: string, configResolver: ClientConfigResolver): Promise<PcoClient>;
38
+ /**
39
+ * Clear the client cache
40
+ */
41
+ clearCache(): void;
42
+ /**
43
+ * Remove a specific client from cache
44
+ */
45
+ removeClient(config: PcoClientConfig): void;
46
+ /**
47
+ * Remove a church client from cache
48
+ */
49
+ removeChurchClient(churchId: string): void;
50
+ /**
51
+ * Get cache statistics
52
+ */
53
+ getCacheStats(): {
54
+ clientCount: number;
55
+ configCount: number;
56
+ churchClients: number;
57
+ };
58
+ /**
59
+ * Generate a cache key for a configuration
60
+ */
61
+ private generateConfigKey;
62
+ /**
63
+ * Check if configuration has changed
64
+ */
65
+ private hasConfigChanged;
66
+ }