@rachelallyson/planning-center-people-ts 1.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.
- package/CHANGELOG.md +104 -0
- package/LICENSE +21 -0
- package/README.md +508 -0
- package/dist/api-error.d.ts +10 -0
- package/dist/api-error.js +32 -0
- package/dist/core.d.ts +70 -0
- package/dist/core.js +258 -0
- package/dist/error-handling.d.ts +56 -0
- package/dist/error-handling.js +270 -0
- package/dist/error-scenarios.d.ts +135 -0
- package/dist/error-scenarios.js +352 -0
- package/dist/helpers.d.ts +144 -0
- package/dist/helpers.js +359 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +123 -0
- package/dist/people.d.ts +205 -0
- package/dist/people.js +598 -0
- package/dist/performance.d.ts +111 -0
- package/dist/performance.js +332 -0
- package/dist/rate-limiter.d.ts +70 -0
- package/dist/rate-limiter.js +114 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +20 -0
- package/dist/types/json-api.d.ts +111 -0
- package/dist/types/json-api.js +5 -0
- package/dist/types/people.d.ts +385 -0
- package/dist/types/people.js +6 -0
- package/package.json +67 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2024-01-XX
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Complete PCO People API Client**: Full TypeScript client for Planning Center Online People API
|
|
13
|
+
- **22 Resource Types**: Complete type definitions for all PCO People API resources
|
|
14
|
+
- **Runtime Type Validation**: Comprehensive validation against real API responses
|
|
15
|
+
- **Advanced Error Handling**: 7 different error handling strategies with automatic recovery
|
|
16
|
+
- **Performance Optimization**: Caching, streaming, batch processing, and memory management
|
|
17
|
+
- **Helper Functions**: 15+ helper functions for common operations
|
|
18
|
+
- **Comprehensive Testing**: 125 tests covering unit, integration, and edge cases
|
|
19
|
+
- **Production Features**: Circuit breakers, retry logic, rate limiting, and monitoring
|
|
20
|
+
|
|
21
|
+
### Core Features
|
|
22
|
+
|
|
23
|
+
- **TypeScript Support**: 100% TypeScript with strict type checking
|
|
24
|
+
- **JSON:API 1.0 Compliance**: Follows JSON:API specification exactly
|
|
25
|
+
- **Rate Limiting**: Built-in rate limiting with PCO's 100 req/min policy
|
|
26
|
+
- **Authentication**: Supports Personal Access Tokens and OAuth 2.0
|
|
27
|
+
- **Modern HTTP**: Uses native fetch API (no external dependencies)
|
|
28
|
+
- **Functional Approach**: Clean, composable functions instead of classes
|
|
29
|
+
|
|
30
|
+
### API Coverage
|
|
31
|
+
|
|
32
|
+
- **Person Management**: Create, read, update, delete people
|
|
33
|
+
- **Contact Information**: Manage emails, phone numbers, addresses
|
|
34
|
+
- **Households**: Family and household management
|
|
35
|
+
- **Field Definitions**: Custom fields and field data
|
|
36
|
+
- **Workflows**: Workflow cards and notes
|
|
37
|
+
- **Lists**: People lists and categories
|
|
38
|
+
- **Notes**: Person notes and categories
|
|
39
|
+
- **Organization**: Organization information and statistics
|
|
40
|
+
|
|
41
|
+
### Error Handling
|
|
42
|
+
|
|
43
|
+
- **Exponential Backoff**: Configurable retry with jitter
|
|
44
|
+
- **Circuit Breaker**: Fault tolerance pattern
|
|
45
|
+
- **Bulk Operations**: Individual error handling for bulk operations
|
|
46
|
+
- **Timeout Handling**: Configurable operation timeouts
|
|
47
|
+
- **Error Classification**: Intelligent error categorization
|
|
48
|
+
- **Error Recovery**: Automatic recovery strategies
|
|
49
|
+
- **Error Reporting**: Detailed error analysis and reporting
|
|
50
|
+
|
|
51
|
+
### Performance Features
|
|
52
|
+
|
|
53
|
+
- **Caching**: In-memory cache with TTL support
|
|
54
|
+
- **Streaming**: Memory-efficient processing of large datasets
|
|
55
|
+
- **Batch Processing**: Efficient API call batching
|
|
56
|
+
- **Pagination**: Automatic pagination with progress tracking
|
|
57
|
+
- **Memory Management**: Large dataset processing without memory issues
|
|
58
|
+
- **Performance Monitoring**: Built-in performance metrics
|
|
59
|
+
- **Concurrency Control**: Semaphore-based rate limiting
|
|
60
|
+
- **Adaptive Rate Limiting**: Dynamic rate adjustment
|
|
61
|
+
|
|
62
|
+
### Helper Functions
|
|
63
|
+
|
|
64
|
+
- **Person Management**: Complete person profiles, contact creation, search
|
|
65
|
+
- **Workflow Management**: Workflow cards with notes, bulk operations
|
|
66
|
+
- **Data Export**: Export all people data with filtering
|
|
67
|
+
- **Validation**: Data validation before API calls
|
|
68
|
+
- **Formatting**: Name formatting, date formatting, age calculation
|
|
69
|
+
- **Contact Management**: Primary contact extraction, validation
|
|
70
|
+
|
|
71
|
+
### Testing
|
|
72
|
+
|
|
73
|
+
- **Unit Tests**: 95 tests covering all core functionality
|
|
74
|
+
- **Integration Tests**: 23 tests against real PCO API
|
|
75
|
+
- **Edge Case Tests**: 7 tests for error scenarios and edge cases
|
|
76
|
+
- **Type Validation**: Runtime validation of all 22 resource types
|
|
77
|
+
- **100% Test Success Rate**: All 125 tests passing
|
|
78
|
+
|
|
79
|
+
### Documentation
|
|
80
|
+
|
|
81
|
+
- **API Usage Guide**: Comprehensive 9-section usage guide
|
|
82
|
+
- **Type Safety**: Complete TypeScript definitions with examples
|
|
83
|
+
- **Best Practices**: Performance optimization and error handling patterns
|
|
84
|
+
- **Troubleshooting**: Common issues and solutions
|
|
85
|
+
- **Examples**: Basic, advanced, and functional usage examples
|
|
86
|
+
|
|
87
|
+
### Production Readiness
|
|
88
|
+
|
|
89
|
+
- **Enterprise Features**: Circuit breakers, monitoring, error recovery
|
|
90
|
+
- **Scalability**: Memory-efficient processing for large datasets
|
|
91
|
+
- **Reliability**: Comprehensive error handling and retry logic
|
|
92
|
+
- **Performance**: Optimized for high-volume operations
|
|
93
|
+
- **Monitoring**: Built-in performance and error metrics
|
|
94
|
+
- **Type Safety**: Runtime validation ensures API compatibility
|
|
95
|
+
|
|
96
|
+
## [0.1.0] - 2024-01-XX
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
|
|
100
|
+
- Initial package structure
|
|
101
|
+
- Basic PCO People API client
|
|
102
|
+
- Core type definitions
|
|
103
|
+
- Basic error handling
|
|
104
|
+
- Rate limiting implementation
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Rachel Higley
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
# @planning-center-people-ts
|
|
2
|
+
|
|
3
|
+
A strictly typed TypeScript client for Planning Center Online People API, built with modern functional programming principles and comprehensive error handling.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Strict TypeScript**: Full type safety with no `any` types
|
|
8
|
+
- ✅ **JSON:API 1.0 Compliant**: Follows the JSON:API specification exactly
|
|
9
|
+
- ✅ **Functional Approach**: Clean, composable functions instead of classes
|
|
10
|
+
- ✅ **Rate Limiting**: Built-in rate limiting with PCO's 100 req/min policy
|
|
11
|
+
- ✅ **Modern HTTP**: Uses native fetch API (no external dependencies)
|
|
12
|
+
- ✅ **Authentication**: Supports both Personal Access Tokens and OAuth 2.0
|
|
13
|
+
- ✅ **Enhanced Error Handling**: Comprehensive error handling with categories, severity, and retry logic
|
|
14
|
+
- ✅ **Automatic Retries**: Configurable exponential backoff with smart retry logic
|
|
15
|
+
- ✅ **Request Timeouts**: Configurable request timeouts
|
|
16
|
+
- ✅ **Pagination**: Automatic pagination support
|
|
17
|
+
- ✅ **No Index Signatures**: Clean type definitions without index signatures
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @planning-center-people-ts
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**No external dependencies required!** This package uses the native fetch API available in all modern environments.
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import {
|
|
31
|
+
createPcoClient,
|
|
32
|
+
getPeople,
|
|
33
|
+
getPerson,
|
|
34
|
+
createPerson,
|
|
35
|
+
updatePerson,
|
|
36
|
+
deletePerson,
|
|
37
|
+
} from '@planning-center-people-ts';
|
|
38
|
+
|
|
39
|
+
// Create a client
|
|
40
|
+
const client = createPcoClient({
|
|
41
|
+
personalAccessToken: 'your-token-here',
|
|
42
|
+
appId: 'your-app-id',
|
|
43
|
+
appSecret: 'your-app-secret',
|
|
44
|
+
// Or use OAuth 2.0:
|
|
45
|
+
// accessToken: 'your-oauth-token',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Get all people
|
|
49
|
+
const people = await getPeople(client, {
|
|
50
|
+
per_page: 10,
|
|
51
|
+
include: ['emails', 'phone_numbers'],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Get a specific person
|
|
55
|
+
const person = await getPerson(client, 'person-id', ['emails']);
|
|
56
|
+
|
|
57
|
+
// Create a new person
|
|
58
|
+
const newPerson = await createPerson(client, {
|
|
59
|
+
first_name: 'John',
|
|
60
|
+
last_name: 'Doe',
|
|
61
|
+
email: 'john.doe@example.com',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Update a person
|
|
65
|
+
const updatedPerson = await updatePerson(client, 'person-id', {
|
|
66
|
+
first_name: 'Jane',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Delete a person
|
|
70
|
+
await deletePerson(client, 'person-id');
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
### Authentication
|
|
76
|
+
|
|
77
|
+
The client supports two authentication methods:
|
|
78
|
+
|
|
79
|
+
#### Personal Access Token (Recommended for single-user apps)
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const client = createPcoClient({
|
|
83
|
+
personalAccessToken: 'your-token-here',
|
|
84
|
+
appId: 'your-app-id',
|
|
85
|
+
appSecret: 'your-app-secret',
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### OAuth 2.0 (For multi-user apps)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const client = createPcoClient({
|
|
93
|
+
accessToken: 'your-oauth-token',
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Rate Limiting
|
|
98
|
+
|
|
99
|
+
Rate limiting is automatically handled, but you can customize it:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const client = createPcoClient({
|
|
103
|
+
accessToken: 'your-token',
|
|
104
|
+
rateLimit: {
|
|
105
|
+
maxRequests: 100, // Default: 100
|
|
106
|
+
perMilliseconds: 60000, // Default: 60000 (1 minute)
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Request Timeouts
|
|
112
|
+
|
|
113
|
+
Configure request timeouts to prevent hanging requests:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const client = createPcoClient({
|
|
117
|
+
accessToken: 'your-token',
|
|
118
|
+
timeout: 30000, // 30 seconds
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Retry Configuration
|
|
123
|
+
|
|
124
|
+
Configure automatic retry behavior:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const client = createPcoClient({
|
|
128
|
+
accessToken: 'your-token',
|
|
129
|
+
retry: {
|
|
130
|
+
maxRetries: 3, // Default: 3
|
|
131
|
+
baseDelay: 1000, // Default: 1000ms
|
|
132
|
+
maxDelay: 30000, // Default: 30000ms
|
|
133
|
+
onRetry: (error, attempt) => {
|
|
134
|
+
console.log(`Retry attempt ${attempt} for ${error.context.endpoint}`);
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Custom Headers
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const client = createPcoClient({
|
|
144
|
+
accessToken: 'your-token',
|
|
145
|
+
headers: {
|
|
146
|
+
'X-Custom-Header': 'value',
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Custom Base URL
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const client = createPcoClient({
|
|
155
|
+
accessToken: 'your-token',
|
|
156
|
+
baseURL: 'https://api.planningcenteronline.com/people/v2',
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## API Reference
|
|
161
|
+
|
|
162
|
+
### Core Functions
|
|
163
|
+
|
|
164
|
+
#### `createPcoClient(config: PcoClientConfig): PcoClientState`
|
|
165
|
+
|
|
166
|
+
Creates a new PCO client instance.
|
|
167
|
+
|
|
168
|
+
#### `getSingle<T>(client, endpoint, params?, context?): Promise<JsonApiResponse<T>>`
|
|
169
|
+
|
|
170
|
+
Makes a GET request for a single resource.
|
|
171
|
+
|
|
172
|
+
#### `getList<T>(client, endpoint, params?, context?): Promise<Paginated<T>>`
|
|
173
|
+
|
|
174
|
+
Makes a GET request for a list of resources.
|
|
175
|
+
|
|
176
|
+
#### `post<T>(client, endpoint, data, params?, context?): Promise<JsonApiResponse<T>>`
|
|
177
|
+
|
|
178
|
+
Makes a POST request to create a resource.
|
|
179
|
+
|
|
180
|
+
#### `patch<T>(client, endpoint, data, params?, context?): Promise<JsonApiResponse<T>>`
|
|
181
|
+
|
|
182
|
+
Makes a PATCH request to update a resource.
|
|
183
|
+
|
|
184
|
+
#### `del(client, endpoint, params?, context?): Promise<void>`
|
|
185
|
+
|
|
186
|
+
Makes a DELETE request to remove a resource.
|
|
187
|
+
|
|
188
|
+
#### `getAllPages<T>(client, endpoint, params?, context?): Promise<T[]>`
|
|
189
|
+
|
|
190
|
+
Automatically fetches all pages of a paginated resource.
|
|
191
|
+
|
|
192
|
+
### People API Functions
|
|
193
|
+
|
|
194
|
+
#### People
|
|
195
|
+
|
|
196
|
+
- `getPeople(client, params?, context?)` - Get all people
|
|
197
|
+
- `getPerson(client, id, include?, context?)` - Get a single person
|
|
198
|
+
- `createPerson(client, data, context?)` - Create a new person
|
|
199
|
+
- `updatePerson(client, id, data, context?)` - Update a person
|
|
200
|
+
- `deletePerson(client, id, context?)` - Delete a person
|
|
201
|
+
|
|
202
|
+
#### Emails
|
|
203
|
+
|
|
204
|
+
- `getPersonEmails(client, personId, context?)` - Get all emails for a person
|
|
205
|
+
- `createPersonEmail(client, personId, data, context?)` - Create an email for a person
|
|
206
|
+
|
|
207
|
+
#### Phone Numbers
|
|
208
|
+
|
|
209
|
+
- `getPersonPhoneNumbers(client, personId, context?)` - Get all phone numbers for a person
|
|
210
|
+
- `createPersonPhoneNumber(client, personId, data, context?)` - Create a phone number for a person
|
|
211
|
+
|
|
212
|
+
#### Addresses
|
|
213
|
+
|
|
214
|
+
- `getPersonAddresses(client, personId, context?)` - Get all addresses for a person
|
|
215
|
+
- `createPersonAddress(client, personId, data, context?)` - Create an address for a person
|
|
216
|
+
|
|
217
|
+
#### Households
|
|
218
|
+
|
|
219
|
+
- `getHouseholds(client, params?, context?)` - Get all households
|
|
220
|
+
- `getHousehold(client, id, include?, context?)` - Get a single household
|
|
221
|
+
|
|
222
|
+
#### Field Definitions
|
|
223
|
+
|
|
224
|
+
- `getFieldDefinitions(client, params?, context?)` - Get all field definitions
|
|
225
|
+
- `getFieldOptions(client, fieldDefinitionId, context?)` - Get options for a field definition
|
|
226
|
+
- `createFieldOption(client, fieldDefinitionId, data, context?)` - Create a field option
|
|
227
|
+
|
|
228
|
+
#### Social Profiles
|
|
229
|
+
|
|
230
|
+
- `getPersonSocialProfiles(client, personId, context?)` - Get social profiles for a person
|
|
231
|
+
- `createPersonSocialProfile(client, personId, data, context?)` - Create a social profile for a person
|
|
232
|
+
|
|
233
|
+
## Enhanced Error Handling
|
|
234
|
+
|
|
235
|
+
The library provides comprehensive error handling with categorized errors, severity levels, and smart retry logic.
|
|
236
|
+
|
|
237
|
+
### Error Categories
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { ErrorCategory, ErrorSeverity } from '@planning-center-people-ts';
|
|
241
|
+
|
|
242
|
+
// Error categories for monitoring and handling
|
|
243
|
+
ErrorCategory.AUTHENTICATION // 401 errors
|
|
244
|
+
ErrorCategory.AUTHORIZATION // 403 errors
|
|
245
|
+
ErrorCategory.RATE_LIMIT // 429 errors
|
|
246
|
+
ErrorCategory.VALIDATION // 400/422 errors
|
|
247
|
+
ErrorCategory.NETWORK // Connection/timeout errors
|
|
248
|
+
ErrorCategory.EXTERNAL_API // 5xx server errors
|
|
249
|
+
ErrorCategory.TIMEOUT // Request timeout errors
|
|
250
|
+
ErrorCategory.UNKNOWN // Unknown errors
|
|
251
|
+
|
|
252
|
+
// Severity levels for prioritization
|
|
253
|
+
ErrorSeverity.LOW // Validation errors, etc.
|
|
254
|
+
ErrorSeverity.MEDIUM // Rate limits, network issues
|
|
255
|
+
ErrorSeverity.HIGH // Auth errors, server errors
|
|
256
|
+
ErrorSeverity.CRITICAL // Critical system failures
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Basic Error Handling
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { PcoError, ErrorCategory } from '@planning-center-people-ts';
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const people = await getPeople(client);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (error instanceof PcoError) {
|
|
268
|
+
console.error('PCO Error:', {
|
|
269
|
+
message: error.message,
|
|
270
|
+
status: error.status,
|
|
271
|
+
category: error.category,
|
|
272
|
+
severity: error.severity,
|
|
273
|
+
retryable: error.retryable,
|
|
274
|
+
context: error.context,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Handle different error categories
|
|
278
|
+
switch (error.category) {
|
|
279
|
+
case ErrorCategory.AUTHENTICATION:
|
|
280
|
+
console.error('Authentication failed - check your token');
|
|
281
|
+
break;
|
|
282
|
+
case ErrorCategory.RATE_LIMIT:
|
|
283
|
+
console.error('Rate limited - retry after:', error.getRetryDelay(), 'ms');
|
|
284
|
+
break;
|
|
285
|
+
case ErrorCategory.VALIDATION:
|
|
286
|
+
console.error('Validation error - check your request data');
|
|
287
|
+
break;
|
|
288
|
+
case ErrorCategory.NETWORK:
|
|
289
|
+
console.error('Network error - check your connection');
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Custom Retry Logic
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import { retryWithBackoff } from '@planning-center-people-ts';
|
|
300
|
+
|
|
301
|
+
const result = await retryWithBackoff(
|
|
302
|
+
() => getPerson(client, 'person-id'),
|
|
303
|
+
{
|
|
304
|
+
maxRetries: 3,
|
|
305
|
+
baseDelay: 1000,
|
|
306
|
+
maxDelay: 10000,
|
|
307
|
+
context: {
|
|
308
|
+
endpoint: '/people/person-id',
|
|
309
|
+
method: 'GET',
|
|
310
|
+
metadata: { custom_retry: true },
|
|
311
|
+
},
|
|
312
|
+
onRetry: (error, attempt) => {
|
|
313
|
+
console.log(`Retry attempt ${attempt} for ${error.context.endpoint}`);
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Error Boundary Wrapper
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import { withErrorBoundary } from '@planning-center-people-ts';
|
|
323
|
+
|
|
324
|
+
const result = await withErrorBoundary(
|
|
325
|
+
() => createPerson(client, personData),
|
|
326
|
+
{
|
|
327
|
+
endpoint: '/people',
|
|
328
|
+
method: 'POST',
|
|
329
|
+
metadata: { operation: 'create_person' },
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Error Context
|
|
335
|
+
|
|
336
|
+
All API functions accept an optional `context` parameter for better error tracking:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
const people = await getPeople(client, { per_page: 10 }, {
|
|
340
|
+
metadata: {
|
|
341
|
+
operation: 'fetch_people_list',
|
|
342
|
+
user_id: 'user123',
|
|
343
|
+
batch_id: 'batch456',
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Type Safety
|
|
349
|
+
|
|
350
|
+
All functions are fully typed with TypeScript:
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// TypeScript knows exactly what properties are available
|
|
354
|
+
const person = await getPerson(client, 'person-id');
|
|
355
|
+
console.log(person.data?.attributes?.first_name); // ✅ TypeScript knows this exists
|
|
356
|
+
console.log(person.data?.attributes?.invalid_prop); // ❌ TypeScript error
|
|
357
|
+
|
|
358
|
+
// Creating resources is type-safe
|
|
359
|
+
const newPerson = await createPerson(client, {
|
|
360
|
+
first_name: 'John', // ✅ Valid property
|
|
361
|
+
invalid_prop: 'value', // ❌ TypeScript error
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Rate Limiting
|
|
366
|
+
|
|
367
|
+
Rate limiting is handled automatically:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// Check current rate limit status
|
|
371
|
+
const rateLimitInfo = getRateLimitInfo(client);
|
|
372
|
+
console.log('Requests used:', rateLimitInfo.requestsUsed);
|
|
373
|
+
console.log('Requests remaining:', rateLimitInfo.requestsRemaining);
|
|
374
|
+
console.log('Window resets in:', rateLimitInfo.windowResetsIn);
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Pagination
|
|
378
|
+
|
|
379
|
+
Handle pagination manually or automatically:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
// Manual pagination
|
|
383
|
+
const people = await getPeople(client, { per_page: 10 });
|
|
384
|
+
if (people.links?.next) {
|
|
385
|
+
const nextPage = await getPeople(client, { page: 2 });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Automatic pagination (gets all pages)
|
|
389
|
+
const allPeople = await getAllPages(client, '/people');
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Environment Support
|
|
393
|
+
|
|
394
|
+
This package uses the native fetch API, which is available in:
|
|
395
|
+
|
|
396
|
+
- **Node.js 18+** (built-in)
|
|
397
|
+
- **All modern browsers** (built-in)
|
|
398
|
+
- **Deno** (built-in)
|
|
399
|
+
|
|
400
|
+
For older Node.js versions, you can use a fetch polyfill:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
npm install node-fetch
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import fetch from 'node-fetch';
|
|
408
|
+
global.fetch = fetch;
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Migration from Class-based Approach
|
|
412
|
+
|
|
413
|
+
If you were using the previous class-based approach:
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// Old way (class-based)
|
|
417
|
+
const client = new PcoClient(config);
|
|
418
|
+
const people = await client.getPeople();
|
|
419
|
+
|
|
420
|
+
// New way (functional)
|
|
421
|
+
const client = createPcoClient(config);
|
|
422
|
+
const people = await getPeople(client);
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Testing
|
|
426
|
+
|
|
427
|
+
### Unit Tests
|
|
428
|
+
|
|
429
|
+
Run the comprehensive unit test suite:
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
npm test # Run all unit tests
|
|
433
|
+
npm run test:watch # Run tests in watch mode
|
|
434
|
+
npm run test:coverage # Run tests with coverage report
|
|
435
|
+
npm run test:ci # Run tests for CI/CD
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
The unit tests cover:
|
|
439
|
+
|
|
440
|
+
- Rate limiting functionality
|
|
441
|
+
- Error handling and retry logic
|
|
442
|
+
- Core client operations
|
|
443
|
+
- All People API functions
|
|
444
|
+
- Type safety and edge cases
|
|
445
|
+
|
|
446
|
+
### Integration Tests
|
|
447
|
+
|
|
448
|
+
Test against the real PCO API to validate functionality:
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
npm run test:integration
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**Prerequisites:**
|
|
455
|
+
|
|
456
|
+
1. Copy `.env.test.example` to `.env.test`
|
|
457
|
+
2. Fill in your PCO credentials:
|
|
458
|
+
|
|
459
|
+
```env
|
|
460
|
+
PCO_APP_ID=your_app_id_here
|
|
461
|
+
PCO_APP_SECRET=your_app_secret_here
|
|
462
|
+
# OR use OAuth:
|
|
463
|
+
# PCO_ACCESS_TOKEN=your_token_here
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
3. Ensure your PCO app has People API permissions
|
|
467
|
+
|
|
468
|
+
**What Integration Tests Cover:**
|
|
469
|
+
|
|
470
|
+
- Authentication and configuration
|
|
471
|
+
- Read operations (people, households, field definitions)
|
|
472
|
+
- Write operations with automatic cleanup
|
|
473
|
+
- Rate limiting and performance
|
|
474
|
+
- Error handling with real API responses
|
|
475
|
+
- Concurrent request handling
|
|
476
|
+
- **Runtime type validation** - All 11 core resource types validated against real API responses
|
|
477
|
+
|
|
478
|
+
**Type Validation:**
|
|
479
|
+
|
|
480
|
+
The integration tests include comprehensive runtime validation to ensure TypeScript types match actual PCO API responses:
|
|
481
|
+
|
|
482
|
+
- ✅ **11 Resource Types Validated**: Person, Email, PhoneNumber, Address, Household, SocialProfile, FieldDefinition, FieldOption, FieldDatum, WorkflowCard, WorkflowCardNote
|
|
483
|
+
- ✅ **Attribute Type Checking**: Validates all attributes match expected types (string, number, boolean, date, null)
|
|
484
|
+
- ✅ **Relationship Validation**: Ensures relationship structures conform to JSON:API specification
|
|
485
|
+
- ✅ **Pagination Validation**: Validates links and metadata structures
|
|
486
|
+
- ✅ **API Change Detection**: Catches breaking changes in PCO API immediately
|
|
487
|
+
|
|
488
|
+
See [TYPE_VALIDATION_SUMMARY.md](./TYPE_VALIDATION_SUMMARY.md) for detailed documentation on type validation coverage and approaches for the remaining 11 resource types.
|
|
489
|
+
|
|
490
|
+
**Safety Features:**
|
|
491
|
+
|
|
492
|
+
- All test data is automatically cleaned up
|
|
493
|
+
- Uses descriptive test names (e.g., "TEST_INTEGRATION_2025")
|
|
494
|
+
- Respects PCO rate limits (90 requests per 20 seconds)
|
|
495
|
+
- 30-second timeout per test
|
|
496
|
+
- Comprehensive error handling
|
|
497
|
+
|
|
498
|
+
## Contributing
|
|
499
|
+
|
|
500
|
+
1. Fork the repository
|
|
501
|
+
2. Create a feature branch
|
|
502
|
+
3. Make your changes
|
|
503
|
+
4. Add tests (both unit and integration)
|
|
504
|
+
5. Submit a pull request
|
|
505
|
+
|
|
506
|
+
## License
|
|
507
|
+
|
|
508
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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;
|