@rachelallyson/planning-center-people-ts 2.7.0 → 2.9.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 (72) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/LICENSE +1 -1
  3. package/README.md +12 -14
  4. package/dist/client.d.ts +8 -8
  5. package/dist/client.js +8 -11
  6. package/dist/core/http.d.ts +1 -1
  7. package/dist/core/http.js +18 -11
  8. package/dist/core/pagination.js +16 -2
  9. package/dist/core.d.ts +3 -3
  10. package/dist/core.js +4 -4
  11. package/dist/error-handling.d.ts +1 -1
  12. package/dist/error-handling.js +3 -3
  13. package/dist/error-scenarios.js +4 -4
  14. package/dist/helpers.js +5 -4
  15. package/dist/index.d.ts +8 -8
  16. package/dist/index.js +15 -15
  17. package/dist/matching/matcher.d.ts +22 -0
  18. package/dist/matching/matcher.js +260 -27
  19. package/dist/matching/scoring.d.ts +9 -7
  20. package/dist/matching/scoring.js +54 -23
  21. package/dist/matching/strategies.d.ts +1 -0
  22. package/dist/matching/strategies.js +17 -4
  23. package/dist/modules/campus.d.ts +5 -5
  24. package/dist/modules/campus.js +2 -2
  25. package/dist/modules/contacts.d.ts +1 -1
  26. package/dist/modules/contacts.js +2 -2
  27. package/dist/modules/fields.d.ts +4 -4
  28. package/dist/modules/fields.js +2 -2
  29. package/dist/modules/forms.d.ts +7 -4
  30. package/dist/modules/forms.js +5 -2
  31. package/dist/modules/households.d.ts +2 -2
  32. package/dist/modules/households.js +2 -2
  33. package/dist/modules/lists.d.ts +2 -2
  34. package/dist/modules/lists.js +2 -2
  35. package/dist/modules/notes.d.ts +2 -2
  36. package/dist/modules/notes.js +2 -2
  37. package/dist/modules/people.d.ts +57 -5
  38. package/dist/modules/people.js +44 -9
  39. package/dist/modules/reports.d.ts +5 -5
  40. package/dist/modules/reports.js +2 -2
  41. package/dist/modules/service-time.d.ts +5 -5
  42. package/dist/modules/service-time.js +2 -2
  43. package/dist/modules/workflows.d.ts +2 -2
  44. package/dist/modules/workflows.js +2 -2
  45. package/dist/people/contacts.d.ts +1 -1
  46. package/dist/people/core.d.ts +1 -1
  47. package/dist/people/fields.d.ts +1 -1
  48. package/dist/people/fields.js +4 -4
  49. package/dist/people/households.d.ts +1 -1
  50. package/dist/people/lists.d.ts +1 -1
  51. package/dist/people/notes.d.ts +1 -1
  52. package/dist/people/organization.d.ts +1 -1
  53. package/dist/people/workflows.d.ts +1 -1
  54. package/dist/types/client.d.ts +3 -96
  55. package/dist/types/client.js +2 -0
  56. package/dist/types/index.d.ts +1 -2
  57. package/dist/types/index.js +0 -2
  58. package/package.json +16 -19
  59. package/dist/api-error.d.ts +0 -10
  60. package/dist/api-error.js +0 -32
  61. package/dist/batch.d.ts +0 -47
  62. package/dist/batch.js +0 -376
  63. package/dist/modules/base.d.ts +0 -46
  64. package/dist/modules/base.js +0 -82
  65. package/dist/monitoring.d.ts +0 -53
  66. package/dist/monitoring.js +0 -142
  67. package/dist/rate-limiter.d.ts +0 -79
  68. package/dist/rate-limiter.js +0 -137
  69. package/dist/types/batch.d.ts +0 -50
  70. package/dist/types/batch.js +0 -5
  71. package/dist/types/events.d.ts +0 -85
  72. package/dist/types/events.js +0 -5
@@ -1,99 +1,6 @@
1
1
  /**
2
2
  * v2.0.0 Client Configuration Types
3
+ *
4
+ * Re-exports from base package for convenience
3
5
  */
4
- /** Authentication configuration for Personal Access Token */
5
- export interface PersonalAccessTokenAuth {
6
- type: 'personal_access_token';
7
- personalAccessToken: string;
8
- }
9
- /** Authentication configuration for OAuth 2.0 with required refresh handling */
10
- export interface OAuthAuth {
11
- type: 'oauth';
12
- accessToken: string;
13
- refreshToken: string;
14
- onRefresh: (tokens: {
15
- accessToken: string;
16
- refreshToken: string;
17
- }) => void | Promise<void>;
18
- onRefreshFailure: (error: Error) => void | Promise<void>;
19
- /** Client ID for token refresh (optional, can use environment variable PCO_APP_ID) */
20
- clientId?: string;
21
- /** Client Secret for token refresh (optional, can use environment variable PCO_APP_SECRET) */
22
- clientSecret?: string;
23
- }
24
- /** Authentication configuration for Basic Auth with app credentials */
25
- export interface BasicAuth {
26
- type: 'basic';
27
- appId: string;
28
- appSecret: string;
29
- }
30
- /** Union type for authentication configurations */
31
- export type PcoAuthConfig = PersonalAccessTokenAuth | OAuthAuth | BasicAuth;
32
- export interface PcoClientConfig {
33
- /** Authentication configuration */
34
- auth: PcoAuthConfig;
35
- /** Caching configuration */
36
- caching?: {
37
- fieldDefinitions?: boolean;
38
- ttl?: number;
39
- maxSize?: number;
40
- };
41
- /** Retry configuration */
42
- retry?: {
43
- enabled?: boolean;
44
- maxRetries?: number;
45
- baseDelay?: number;
46
- maxDelay?: number;
47
- backoff?: 'linear' | 'exponential';
48
- };
49
- /** Event handlers */
50
- events?: {
51
- onError?: (event: ErrorEvent) => void | Promise<void>;
52
- onAuthFailure?: (event: AuthFailureEvent) => void | Promise<void>;
53
- onRequestStart?: (event: RequestStartEvent) => void | Promise<void>;
54
- onRequestComplete?: (event: RequestCompleteEvent) => void | Promise<void>;
55
- onRateLimit?: (event: RateLimitEvent) => void | Promise<void>;
56
- };
57
- /** Base URL override */
58
- baseURL?: string;
59
- /** Request timeout in milliseconds */
60
- timeout?: number;
61
- /** Custom headers */
62
- headers?: Record<string, string>;
63
- }
64
- export interface ErrorEvent {
65
- error: Error;
66
- operation: string;
67
- timestamp: string;
68
- context?: Record<string, any>;
69
- }
70
- export interface AuthFailureEvent {
71
- error: Error;
72
- timestamp: string;
73
- authType: 'oauth' | 'basic';
74
- }
75
- export interface RequestStartEvent {
76
- endpoint: string;
77
- method: string;
78
- timestamp: string;
79
- requestId: string;
80
- }
81
- export interface RequestCompleteEvent {
82
- endpoint: string;
83
- method: string;
84
- status: number;
85
- duration: number;
86
- timestamp: string;
87
- requestId: string;
88
- }
89
- export interface RateLimitEvent {
90
- limit: number;
91
- remaining: number;
92
- resetTime: string;
93
- timestamp: string;
94
- }
95
- export interface CacheEvent {
96
- key: string;
97
- operation: 'hit' | 'miss' | 'set' | 'invalidate';
98
- timestamp: string;
99
- }
6
+ export type { PcoClientConfig, PcoAuthConfig, PersonalAccessTokenAuth, OAuthAuth, BasicAuth, ErrorEvent, AuthFailureEvent, RequestStartEvent, RequestCompleteEvent, RateLimitEvent, CacheEvent, } from '@rachelallyson/planning-center-base-ts';
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * v2.0.0 Client Configuration Types
4
+ *
5
+ * Re-exports from base package for convenience
4
6
  */
5
7
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,4 +1,3 @@
1
- export * from './json-api';
1
+ export type { JsonValue, Meta, LinkObject, Link, Links, PaginationLinks, TopLevelLinks, TopLevelJsonApi, ResourceIdentifier, Relationship, ToOne, ToMany, Attributes, ResourceObject, JsonApiBase, ErrorObject, ErrorDocument, DataDocumentSingle, DataDocumentMany, JsonApiDocument, Paginated, Response, } from '@rachelallyson/planning-center-base-ts';
2
2
  export * from './people';
3
- export type { Paginated, Relationship, ResourceIdentifier, ResourceObject, } from './json-api';
4
3
  export type { AddressAttributes, AddressesList, AddressResource, AddressSingle, EmailAttributes, EmailResource, EmailSingle, EmailsList, FieldDefinitionAttributes, FieldDefinitionResource, FieldDefinitionSingle, FieldDefinitionsList, FieldOptionAttributes, FieldOptionResource, FieldOptionSingle, FieldOptionsList, HouseholdAttributes, HouseholdResource, HouseholdSingle, HouseholdsList, PeopleList, PersonAttributes, PersonRelationships, PersonResource, PersonSingle, PhoneNumberAttributes, PhoneNumberResource, PhoneNumberSingle, PhoneNumbersList, SocialProfileAttributes, SocialProfileResource, SocialProfileSingle, SocialProfilesList, } from './people';
@@ -14,7 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- // Export all JSON:API types
18
- __exportStar(require("./json-api"), exports);
19
17
  // Export all People-specific types
20
18
  __exportStar(require("./people"), exports);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rachelallyson/planning-center-people-ts",
3
- "version": "2.7.0",
4
- "description": "A strictly typed TypeScript client for Planning Center Online People API with smart matching, batch operations, and enhanced developer experience",
3
+ "version": "2.9.0",
4
+ "description": "A strictly typed TypeScript client for Planning Center Online People API with comprehensive functionality and enhanced developer experience",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
@@ -13,11 +13,9 @@
13
13
  "test:watch": "jest --watch --testPathIgnorePatterns=integration.test.ts",
14
14
  "test:coverage": "jest --coverage --testPathIgnorePatterns=integration.test.ts",
15
15
  "test:ci": "jest --ci --coverage --watchAll=false --testPathIgnorePatterns=integration.test.ts",
16
- "test:integration": "dotenv -e .env.test -- jest --testPathPatterns=integration.test.ts --setupFilesAfterEnv='<rootDir>/tests/integration-setup.ts' --testTimeout=30000",
17
- "test:integration:people-all": "dotenv -e .env.test -- jest --testPathPatterns=integration/people-.*.integration.test.ts --setupFilesAfterEnv='<rootDir>/tests/integration-setup.ts' --testTimeout=30000",
18
- "test:integration:v2:all": "dotenv -e .env.test -- jest --testPathPatterns=integration/v2/.*.integration.test.ts --setupFilesAfterEnv='<rootDir>/tests/integration-setup.ts' --testTimeout=30000",
19
- "test:edge-cases": "jest --testPathPatterns=edge-cases.test.ts --testTimeout=30000",
20
- "test:all": "npm run test && npm run test:edge-cases",
16
+ "test:integration": "dotenv -e .env.test -- jest --testPathPatterns=integration/.*.integration.test.ts --setupFilesAfterEnv='<rootDir>/tests/integration-setup.ts' --testTimeout=60000",
17
+ "docs": "typedoc",
18
+ "docs:watch": "typedoc --watch",
21
19
  "prepublishOnly": "npm run build"
22
20
  },
23
21
  "keywords": [
@@ -29,26 +27,25 @@
29
27
  "json-api",
30
28
  "people",
31
29
  "church",
32
- "crm",
33
30
  "client",
34
31
  "sdk",
35
32
  "rate-limiting",
36
33
  "error-handling",
37
- "performance",
38
- "caching",
39
34
  "batch-operations",
40
- "smart-matching",
41
35
  "fluent-api",
42
- "event-system",
43
- "client-manager"
36
+ "event-system"
44
37
  ],
45
- "author": "Rachel Higley",
38
+ "author": "Rachel Allyson",
46
39
  "license": "MIT",
47
40
  "devDependencies": {
48
41
  "@types/jest": "^30.0.0",
49
- "dotenv": "^16.4.5",
42
+ "@types/node": "^20.0.0",
43
+ "dotenv": "^16.6.1",
50
44
  "jest": "^30.2.0",
51
45
  "ts-jest": "^29.4.4",
46
+ "tslib": "^2.8.1",
47
+ "typedoc": "^0.28.14",
48
+ "typedoc-plugin-markdown": "^4.9.0",
52
49
  "typescript": "^5.9.3"
53
50
  },
54
51
  "files": [
@@ -59,12 +56,12 @@
59
56
  ],
60
57
  "repository": {
61
58
  "type": "git",
62
- "url": "https://github.com/rachelallyson/planning-center-people-ts.git"
59
+ "url": "https://github.com/rachelallyson/planning-center-monorepo.git"
63
60
  },
64
61
  "bugs": {
65
- "url": "https://github.com/rachelallyson/planning-center-people-ts/issues"
62
+ "url": "https://github.com/rachelallyson/planning-center-monorepo/issues"
66
63
  },
67
- "homepage": "https://github.com/rachelallyson/planning-center-people-ts#readme",
64
+ "homepage": "https://github.com/rachelallyson/planning-center-monorepo#readme",
68
65
  "engines": {
69
66
  "node": ">=16.0.0"
70
67
  },
@@ -72,6 +69,6 @@
72
69
  "access": "public"
73
70
  },
74
71
  "dependencies": {
75
- "form-data": "^4.0.4"
72
+ "@rachelallyson/planning-center-base-ts": "^1.0.0"
76
73
  }
77
74
  }
@@ -1,10 +0,0 @@
1
- import type { RateLimitHeaders } from './rate-limiter';
2
- import type { ErrorObject as JsonApiError } from './types';
3
- export declare class PcoApiError extends Error {
4
- readonly status: number;
5
- readonly statusText: string;
6
- readonly errors: JsonApiError[];
7
- readonly rateLimitHeaders?: RateLimitHeaders;
8
- constructor(message: string, status: number, statusText: string, errors: JsonApiError[], rateLimitHeaders?: RateLimitHeaders);
9
- static fromFetchError(response: Response, data?: unknown): PcoApiError;
10
- }
package/dist/api-error.js DELETED
@@ -1,32 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PcoApiError = void 0;
4
- // ===== PCO API Error =====
5
- class PcoApiError extends Error {
6
- constructor(message, status, statusText, errors, rateLimitHeaders) {
7
- super(message);
8
- this.name = 'PcoApiError';
9
- this.status = status;
10
- this.statusText = statusText;
11
- this.errors = errors;
12
- this.rateLimitHeaders = rateLimitHeaders;
13
- }
14
- static fromFetchError(response, data) {
15
- const status = response.status;
16
- const statusText = response.statusText;
17
- const apiErrors = Array.isArray(data?.errors)
18
- ? data.errors || []
19
- : [];
20
- const rateLimitHeaders = {
21
- 'Retry-After': response.headers.get('retry-after') ?? undefined,
22
- 'X-PCO-API-Request-Rate-Count': response.headers.get('x-pco-api-request-rate-count') ?? undefined,
23
- 'X-PCO-API-Request-Rate-Limit': response.headers.get('x-pco-api-request-rate-limit') ?? undefined,
24
- 'X-PCO-API-Request-Rate-Period': response.headers.get('x-pco-api-request-rate-period') ?? undefined,
25
- };
26
- const message = apiErrors.length > 0
27
- ? apiErrors.map(e => e.detail ?? e.title ?? 'Unknown error').join('; ')
28
- : statusText;
29
- return new PcoApiError(message, status, statusText, apiErrors, rateLimitHeaders);
30
- }
31
- }
32
- exports.PcoApiError = PcoApiError;
package/dist/batch.d.ts DELETED
@@ -1,47 +0,0 @@
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 DELETED
@@ -1,376 +0,0 @@
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
- }