@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.
- package/CHANGELOG.md +174 -0
- package/LICENSE +1 -1
- package/README.md +12 -14
- package/dist/client.d.ts +8 -8
- package/dist/client.js +8 -11
- package/dist/core/http.d.ts +1 -1
- package/dist/core/http.js +18 -11
- package/dist/core/pagination.js +16 -2
- package/dist/core.d.ts +3 -3
- package/dist/core.js +4 -4
- package/dist/error-handling.d.ts +1 -1
- package/dist/error-handling.js +3 -3
- package/dist/error-scenarios.js +4 -4
- package/dist/helpers.js +5 -4
- package/dist/index.d.ts +8 -8
- package/dist/index.js +15 -15
- package/dist/matching/matcher.d.ts +22 -0
- package/dist/matching/matcher.js +260 -27
- package/dist/matching/scoring.d.ts +9 -7
- package/dist/matching/scoring.js +54 -23
- package/dist/matching/strategies.d.ts +1 -0
- package/dist/matching/strategies.js +17 -4
- package/dist/modules/campus.d.ts +5 -5
- package/dist/modules/campus.js +2 -2
- package/dist/modules/contacts.d.ts +1 -1
- package/dist/modules/contacts.js +2 -2
- package/dist/modules/fields.d.ts +4 -4
- package/dist/modules/fields.js +2 -2
- package/dist/modules/forms.d.ts +7 -4
- package/dist/modules/forms.js +5 -2
- package/dist/modules/households.d.ts +2 -2
- package/dist/modules/households.js +2 -2
- package/dist/modules/lists.d.ts +2 -2
- package/dist/modules/lists.js +2 -2
- package/dist/modules/notes.d.ts +2 -2
- package/dist/modules/notes.js +2 -2
- package/dist/modules/people.d.ts +57 -5
- package/dist/modules/people.js +44 -9
- package/dist/modules/reports.d.ts +5 -5
- package/dist/modules/reports.js +2 -2
- package/dist/modules/service-time.d.ts +5 -5
- package/dist/modules/service-time.js +2 -2
- package/dist/modules/workflows.d.ts +2 -2
- package/dist/modules/workflows.js +2 -2
- package/dist/people/contacts.d.ts +1 -1
- package/dist/people/core.d.ts +1 -1
- package/dist/people/fields.d.ts +1 -1
- package/dist/people/fields.js +4 -4
- package/dist/people/households.d.ts +1 -1
- package/dist/people/lists.d.ts +1 -1
- package/dist/people/notes.d.ts +1 -1
- package/dist/people/organization.d.ts +1 -1
- package/dist/people/workflows.d.ts +1 -1
- package/dist/types/client.d.ts +3 -96
- package/dist/types/client.js +2 -0
- package/dist/types/index.d.ts +1 -2
- package/dist/types/index.js +0 -2
- package/package.json +16 -19
- package/dist/api-error.d.ts +0 -10
- package/dist/api-error.js +0 -32
- package/dist/batch.d.ts +0 -47
- package/dist/batch.js +0 -376
- package/dist/modules/base.d.ts +0 -46
- package/dist/modules/base.js +0 -82
- package/dist/monitoring.d.ts +0 -53
- package/dist/monitoring.js +0 -142
- package/dist/rate-limiter.d.ts +0 -79
- package/dist/rate-limiter.js +0 -137
- package/dist/types/batch.d.ts +0 -50
- package/dist/types/batch.js +0 -5
- package/dist/types/events.d.ts +0 -85
- package/dist/types/events.js +0 -5
package/dist/types/client.d.ts
CHANGED
|
@@ -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
|
-
|
|
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';
|
package/dist/types/client.js
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export
|
|
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';
|
package/dist/types/index.js
CHANGED
|
@@ -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.
|
|
4
|
-
"description": "A strictly typed TypeScript client for Planning Center Online People API with
|
|
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=
|
|
17
|
-
"
|
|
18
|
-
"
|
|
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
|
|
38
|
+
"author": "Rachel Allyson",
|
|
46
39
|
"license": "MIT",
|
|
47
40
|
"devDependencies": {
|
|
48
41
|
"@types/jest": "^30.0.0",
|
|
49
|
-
"
|
|
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-
|
|
59
|
+
"url": "https://github.com/rachelallyson/planning-center-monorepo.git"
|
|
63
60
|
},
|
|
64
61
|
"bugs": {
|
|
65
|
-
"url": "https://github.com/rachelallyson/planning-center-
|
|
62
|
+
"url": "https://github.com/rachelallyson/planning-center-monorepo/issues"
|
|
66
63
|
},
|
|
67
|
-
"homepage": "https://github.com/rachelallyson/planning-center-
|
|
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
|
-
"
|
|
72
|
+
"@rachelallyson/planning-center-base-ts": "^1.0.0"
|
|
76
73
|
}
|
|
77
74
|
}
|
package/dist/api-error.d.ts
DELETED
|
@@ -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
|
-
}
|