@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
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ /**
3
+ * v2.0.0 Client Manager with Caching
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PcoClientManager = void 0;
7
+ const client_1 = require("./client");
8
+ class PcoClientManager {
9
+ constructor() {
10
+ this.clientCache = new Map();
11
+ this.configCache = new Map();
12
+ }
13
+ /**
14
+ * Get the singleton instance
15
+ */
16
+ static getInstance() {
17
+ if (!PcoClientManager.instance) {
18
+ PcoClientManager.instance = new PcoClientManager();
19
+ }
20
+ return PcoClientManager.instance;
21
+ }
22
+ /**
23
+ * Get a client instance with the given configuration
24
+ */
25
+ static getClient(config) {
26
+ return PcoClientManager.getInstance().getClient(config);
27
+ }
28
+ /**
29
+ * Get a client instance for a specific church with config resolution
30
+ */
31
+ static async getClientForChurch(churchId, configResolver) {
32
+ return PcoClientManager.getInstance().getClientForChurch(churchId, configResolver);
33
+ }
34
+ /**
35
+ * Clear the client cache
36
+ */
37
+ static clearCache() {
38
+ PcoClientManager.getInstance().clearCache();
39
+ }
40
+ /**
41
+ * Get a client instance with caching
42
+ */
43
+ getClient(config) {
44
+ const configKey = this.generateConfigKey(config);
45
+ // Check if we have a cached client
46
+ let client = this.clientCache.get(configKey);
47
+ if (!client) {
48
+ // Create new client
49
+ client = new client_1.PcoClient(config);
50
+ this.clientCache.set(configKey, client);
51
+ this.configCache.set(configKey, { ...config });
52
+ }
53
+ else {
54
+ // Check if config has changed
55
+ const cachedConfig = this.configCache.get(configKey);
56
+ if (cachedConfig && this.hasConfigChanged(cachedConfig, config)) {
57
+ // Update client with new config
58
+ client.updateConfig(config);
59
+ this.configCache.set(configKey, { ...config });
60
+ }
61
+ }
62
+ return client;
63
+ }
64
+ /**
65
+ * Get a client instance for a specific church
66
+ */
67
+ async getClientForChurch(churchId, configResolver) {
68
+ const configKey = `church:${churchId}`;
69
+ // Check if we have a cached client
70
+ let client = this.clientCache.get(configKey);
71
+ if (!client) {
72
+ // Resolve configuration
73
+ const config = await configResolver(churchId);
74
+ // Create new client
75
+ client = new client_1.PcoClient(config);
76
+ this.clientCache.set(configKey, client);
77
+ this.configCache.set(configKey, { ...config });
78
+ }
79
+ return client;
80
+ }
81
+ /**
82
+ * Clear the client cache
83
+ */
84
+ clearCache() {
85
+ this.clientCache.clear();
86
+ this.configCache.clear();
87
+ }
88
+ /**
89
+ * Remove a specific client from cache
90
+ */
91
+ removeClient(config) {
92
+ const configKey = this.generateConfigKey(config);
93
+ this.clientCache.delete(configKey);
94
+ this.configCache.delete(configKey);
95
+ }
96
+ /**
97
+ * Remove a church client from cache
98
+ */
99
+ removeChurchClient(churchId) {
100
+ const configKey = `church:${churchId}`;
101
+ this.clientCache.delete(configKey);
102
+ this.configCache.delete(configKey);
103
+ }
104
+ /**
105
+ * Get cache statistics
106
+ */
107
+ getCacheStats() {
108
+ const churchClients = Array.from(this.clientCache.keys()).filter(key => key.startsWith('church:')).length;
109
+ return {
110
+ clientCount: this.clientCache.size,
111
+ configCount: this.configCache.size,
112
+ churchClients,
113
+ };
114
+ }
115
+ /**
116
+ * Generate a cache key for a configuration
117
+ */
118
+ generateConfigKey(config) {
119
+ // Create a hash of the configuration
120
+ const configStr = JSON.stringify({
121
+ authType: config.auth.type,
122
+ hasAccessToken: !!config.auth.accessToken,
123
+ hasRefreshToken: !!config.auth.refreshToken,
124
+ hasPersonalAccessToken: !!config.auth.personalAccessToken,
125
+ baseURL: config.baseURL,
126
+ timeout: config.timeout,
127
+ });
128
+ // Simple hash function
129
+ let hash = 0;
130
+ for (let i = 0; i < configStr.length; i++) {
131
+ const char = configStr.charCodeAt(i);
132
+ hash = ((hash << 5) - hash) + char;
133
+ hash = hash & hash; // Convert to 32-bit integer
134
+ }
135
+ return `config:${Math.abs(hash)}`;
136
+ }
137
+ /**
138
+ * Check if configuration has changed
139
+ */
140
+ hasConfigChanged(oldConfig, newConfig) {
141
+ // Compare key configuration properties
142
+ return (oldConfig.auth.type !== newConfig.auth.type ||
143
+ oldConfig.auth.accessToken !== newConfig.auth.accessToken ||
144
+ oldConfig.auth.refreshToken !== newConfig.auth.refreshToken ||
145
+ oldConfig.auth.personalAccessToken !== newConfig.auth.personalAccessToken ||
146
+ oldConfig.baseURL !== newConfig.baseURL ||
147
+ oldConfig.timeout !== newConfig.timeout);
148
+ }
149
+ }
150
+ exports.PcoClientManager = PcoClientManager;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * v2.0.0 Main PcoClient Class
3
+ */
4
+ import type { PcoClientConfig } from './types/client';
5
+ import type { EventEmitter } from './types/events';
6
+ import { PeopleModule } from './modules/people';
7
+ import { FieldsModule } from './modules/fields';
8
+ import { WorkflowsModule } from './modules/workflows';
9
+ import { ContactsModule } from './modules/contacts';
10
+ import { HouseholdsModule } from './modules/households';
11
+ import { NotesModule } from './modules/notes';
12
+ import { ListsModule } from './modules/lists';
13
+ import { BatchExecutor } from './batch';
14
+ export declare class PcoClient implements EventEmitter {
15
+ people: PeopleModule;
16
+ fields: FieldsModule;
17
+ workflows: WorkflowsModule;
18
+ contacts: ContactsModule;
19
+ households: HouseholdsModule;
20
+ notes: NotesModule;
21
+ lists: ListsModule;
22
+ batch: BatchExecutor;
23
+ private httpClient;
24
+ private paginationHelper;
25
+ private eventEmitter;
26
+ private config;
27
+ constructor(config: PcoClientConfig);
28
+ on<T extends import('./types/events').PcoEvent>(eventType: T['type'], handler: import('./types/events').EventHandler<T>): void;
29
+ off<T extends import('./types/events').PcoEvent>(eventType: T['type'], handler: import('./types/events').EventHandler<T>): void;
30
+ emit<T extends import('./types/events').PcoEvent>(event: T): void;
31
+ /**
32
+ * Get the current configuration
33
+ */
34
+ getConfig(): PcoClientConfig;
35
+ /**
36
+ * Update the configuration
37
+ */
38
+ updateConfig(updates: Partial<PcoClientConfig>): void;
39
+ /**
40
+ * Get performance metrics
41
+ */
42
+ getPerformanceMetrics(): Record<string, {
43
+ count: number;
44
+ averageTime: number;
45
+ minTime: number;
46
+ maxTime: number;
47
+ errorRate: number;
48
+ }>;
49
+ /**
50
+ * Get rate limit information
51
+ */
52
+ getRateLimitInfo(): Record<string, {
53
+ limit: number;
54
+ remaining: number;
55
+ resetTime: number;
56
+ }>;
57
+ /**
58
+ * Clear all event listeners
59
+ */
60
+ removeAllListeners(eventType?: import('./types/events').EventType): void;
61
+ /**
62
+ * Get the number of listeners for an event type
63
+ */
64
+ listenerCount(eventType: import('./types/events').EventType): number;
65
+ /**
66
+ * Get all registered event types
67
+ */
68
+ eventTypes(): import('./types/events').EventType[];
69
+ private setupEventHandlers;
70
+ private updateModules;
71
+ }
package/dist/client.js ADDED
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ /**
3
+ * v2.0.0 Main PcoClient Class
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PcoClient = void 0;
7
+ const monitoring_1 = require("./monitoring");
8
+ const http_1 = require("./core/http");
9
+ const pagination_1 = require("./core/pagination");
10
+ const people_1 = require("./modules/people");
11
+ const fields_1 = require("./modules/fields");
12
+ const workflows_1 = require("./modules/workflows");
13
+ const contacts_1 = require("./modules/contacts");
14
+ const households_1 = require("./modules/households");
15
+ const notes_1 = require("./modules/notes");
16
+ const lists_1 = require("./modules/lists");
17
+ const batch_1 = require("./batch");
18
+ class PcoClient {
19
+ constructor(config) {
20
+ this.config = config;
21
+ this.eventEmitter = new monitoring_1.PcoEventEmitter();
22
+ this.httpClient = new http_1.PcoHttpClient(config, this.eventEmitter);
23
+ this.paginationHelper = new pagination_1.PaginationHelper(this.httpClient);
24
+ // Initialize modules
25
+ this.people = new people_1.PeopleModule(this.httpClient, this.paginationHelper, this.eventEmitter);
26
+ this.fields = new fields_1.FieldsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
27
+ this.workflows = new workflows_1.WorkflowsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
28
+ this.contacts = new contacts_1.ContactsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
29
+ this.households = new households_1.HouseholdsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
30
+ this.notes = new notes_1.NotesModule(this.httpClient, this.paginationHelper, this.eventEmitter);
31
+ this.lists = new lists_1.ListsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
32
+ this.batch = new batch_1.BatchExecutor(this, this.eventEmitter);
33
+ // Set up event handlers from config
34
+ this.setupEventHandlers();
35
+ }
36
+ // EventEmitter implementation
37
+ on(eventType, handler) {
38
+ this.eventEmitter.on(eventType, handler);
39
+ }
40
+ off(eventType, handler) {
41
+ this.eventEmitter.off(eventType, handler);
42
+ }
43
+ emit(event) {
44
+ this.eventEmitter.emit(event);
45
+ }
46
+ /**
47
+ * Get the current configuration
48
+ */
49
+ getConfig() {
50
+ return { ...this.config };
51
+ }
52
+ /**
53
+ * Update the configuration
54
+ */
55
+ updateConfig(updates) {
56
+ this.config = { ...this.config, ...updates };
57
+ // Recreate HTTP client with new config
58
+ this.httpClient = new http_1.PcoHttpClient(this.config, this.eventEmitter);
59
+ this.paginationHelper = new pagination_1.PaginationHelper(this.httpClient);
60
+ // Update modules with new HTTP client
61
+ this.updateModules();
62
+ }
63
+ /**
64
+ * Get performance metrics
65
+ */
66
+ getPerformanceMetrics() {
67
+ return this.httpClient.getPerformanceMetrics();
68
+ }
69
+ /**
70
+ * Get rate limit information
71
+ */
72
+ getRateLimitInfo() {
73
+ return this.httpClient.getRateLimitInfo();
74
+ }
75
+ /**
76
+ * Clear all event listeners
77
+ */
78
+ removeAllListeners(eventType) {
79
+ this.eventEmitter.removeAllListeners(eventType);
80
+ }
81
+ /**
82
+ * Get the number of listeners for an event type
83
+ */
84
+ listenerCount(eventType) {
85
+ return this.eventEmitter.listenerCount(eventType);
86
+ }
87
+ /**
88
+ * Get all registered event types
89
+ */
90
+ eventTypes() {
91
+ return this.eventEmitter.eventTypes();
92
+ }
93
+ setupEventHandlers() {
94
+ // Set up config event handlers
95
+ if (this.config.events?.onError) {
96
+ this.on('error', this.config.events.onError);
97
+ }
98
+ if (this.config.events?.onAuthFailure) {
99
+ this.on('auth:failure', this.config.events.onAuthFailure);
100
+ }
101
+ if (this.config.events?.onRequestStart) {
102
+ this.on('request:start', this.config.events.onRequestStart);
103
+ }
104
+ if (this.config.events?.onRequestComplete) {
105
+ this.on('request:complete', this.config.events.onRequestComplete);
106
+ }
107
+ if (this.config.events?.onRateLimit) {
108
+ this.on('rate:limit', this.config.events.onRateLimit);
109
+ }
110
+ }
111
+ updateModules() {
112
+ // Recreate modules with new HTTP client
113
+ this.people = new people_1.PeopleModule(this.httpClient, this.paginationHelper, this.eventEmitter);
114
+ this.fields = new fields_1.FieldsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
115
+ this.workflows = new workflows_1.WorkflowsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
116
+ this.contacts = new contacts_1.ContactsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
117
+ this.households = new households_1.HouseholdsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
118
+ this.notes = new notes_1.NotesModule(this.httpClient, this.paginationHelper, this.eventEmitter);
119
+ this.lists = new lists_1.ListsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
120
+ this.batch = new batch_1.BatchExecutor(this, this.eventEmitter);
121
+ }
122
+ }
123
+ exports.PcoClient = PcoClient;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * v2.0.0 HTTP Client
3
+ */
4
+ import type { PcoClientConfig } from '../types/client';
5
+ import { PcoEventEmitter } from '../monitoring';
6
+ export interface HttpRequestOptions {
7
+ method: string;
8
+ endpoint: string;
9
+ data?: any;
10
+ params?: Record<string, any>;
11
+ headers?: Record<string, string>;
12
+ timeout?: number;
13
+ }
14
+ export interface HttpResponse<T = any> {
15
+ data: T;
16
+ status: number;
17
+ headers: Record<string, string>;
18
+ requestId: string;
19
+ duration: number;
20
+ }
21
+ export declare class PcoHttpClient {
22
+ private config;
23
+ private eventEmitter;
24
+ private requestIdGenerator;
25
+ private performanceMetrics;
26
+ private rateLimitTracker;
27
+ private rateLimiter;
28
+ constructor(config: PcoClientConfig, eventEmitter: PcoEventEmitter);
29
+ request<T = any>(options: HttpRequestOptions): Promise<HttpResponse<T>>;
30
+ private makeRequest;
31
+ private addAuthentication;
32
+ private getResourceTypeFromEndpoint;
33
+ private extractHeaders;
34
+ private updateRateLimitTracking;
35
+ getPerformanceMetrics(): Record<string, {
36
+ count: number;
37
+ averageTime: number;
38
+ minTime: number;
39
+ maxTime: number;
40
+ errorRate: number;
41
+ }>;
42
+ getRateLimitInfo(): Record<string, {
43
+ limit: number;
44
+ remaining: number;
45
+ resetTime: number;
46
+ }>;
47
+ }
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ /**
3
+ * v2.0.0 HTTP Client
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PcoHttpClient = void 0;
7
+ const monitoring_1 = require("../monitoring");
8
+ const rate_limiter_1 = require("../rate-limiter");
9
+ const api_error_1 = require("../api-error");
10
+ const auth_1 = require("../auth");
11
+ class PcoHttpClient {
12
+ constructor(config, eventEmitter) {
13
+ this.config = config;
14
+ this.eventEmitter = eventEmitter;
15
+ this.requestIdGenerator = new monitoring_1.RequestIdGenerator();
16
+ this.performanceMetrics = new monitoring_1.PerformanceMetrics();
17
+ this.rateLimitTracker = new monitoring_1.RateLimitTracker();
18
+ // Initialize rate limiter
19
+ this.rateLimiter = new rate_limiter_1.PcoRateLimiter(100, 60000); // 100 requests per minute
20
+ }
21
+ async request(options) {
22
+ const requestId = this.requestIdGenerator.generate();
23
+ const startTime = Date.now();
24
+ // Emit request start event
25
+ this.eventEmitter.emit({
26
+ type: 'request:start',
27
+ endpoint: options.endpoint,
28
+ method: options.method,
29
+ requestId,
30
+ timestamp: new Date().toISOString(),
31
+ });
32
+ try {
33
+ // Wait for rate limiter
34
+ await this.rateLimiter.waitForAvailability();
35
+ const response = await this.makeRequest(options, requestId);
36
+ const duration = Date.now() - startTime;
37
+ // Record performance metrics
38
+ this.performanceMetrics.record(`${options.method} ${options.endpoint}`, duration, true);
39
+ // Update rate limit tracking
40
+ this.updateRateLimitTracking(options.endpoint, response.headers);
41
+ // Emit request complete event
42
+ this.eventEmitter.emit({
43
+ type: 'request:complete',
44
+ endpoint: options.endpoint,
45
+ method: options.method,
46
+ status: response.status,
47
+ duration,
48
+ requestId,
49
+ timestamp: new Date().toISOString(),
50
+ });
51
+ return response;
52
+ }
53
+ catch (error) {
54
+ const duration = Date.now() - startTime;
55
+ // Record performance metrics
56
+ this.performanceMetrics.record(`${options.method} ${options.endpoint}`, duration, false);
57
+ // Emit request error event
58
+ this.eventEmitter.emit({
59
+ type: 'request:error',
60
+ endpoint: options.endpoint,
61
+ method: options.method,
62
+ error: error,
63
+ requestId,
64
+ timestamp: new Date().toISOString(),
65
+ });
66
+ throw error;
67
+ }
68
+ }
69
+ async makeRequest(options, requestId) {
70
+ const baseURL = this.config.baseURL || 'https://api.planningcenteronline.com/people/v2';
71
+ let url = options.endpoint.startsWith('http') ? options.endpoint : `${baseURL}${options.endpoint}`;
72
+ // Add query parameters
73
+ if (options.params) {
74
+ const searchParams = new URLSearchParams();
75
+ Object.entries(options.params).forEach(([key, value]) => {
76
+ if (value !== undefined && value !== null) {
77
+ searchParams.append(key, String(value));
78
+ }
79
+ });
80
+ const queryString = searchParams.toString();
81
+ if (queryString) {
82
+ url += url.includes('?') ? `&${queryString}` : `?${queryString}`;
83
+ }
84
+ }
85
+ // Prepare headers
86
+ const headers = {
87
+ 'Accept': 'application/json',
88
+ 'Content-Type': 'application/json',
89
+ ...this.config.headers,
90
+ ...options.headers,
91
+ };
92
+ // Add authentication
93
+ this.addAuthentication(headers);
94
+ // Prepare request options
95
+ const requestOptions = {
96
+ headers,
97
+ method: options.method,
98
+ };
99
+ // Add body for POST/PATCH requests
100
+ if ((options.method === 'POST' || options.method === 'PATCH') && options.data) {
101
+ // Determine resource type from endpoint
102
+ const resourceType = this.getResourceTypeFromEndpoint(options.endpoint);
103
+ // Separate attributes and relationships
104
+ const { relationships, ...attributes } = options.data;
105
+ const jsonApiData = {
106
+ data: {
107
+ type: resourceType,
108
+ attributes
109
+ }
110
+ };
111
+ // Add relationships if present
112
+ if (relationships) {
113
+ jsonApiData.data.relationships = relationships;
114
+ }
115
+ requestOptions.body = JSON.stringify(jsonApiData);
116
+ }
117
+ // Add timeout
118
+ const timeout = options.timeout || this.config.timeout || 30000;
119
+ const controller = new AbortController();
120
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
121
+ requestOptions.signal = controller.signal;
122
+ try {
123
+ const response = await fetch(url, requestOptions);
124
+ clearTimeout(timeoutId);
125
+ // Update rate limiter from headers
126
+ const rateLimitHeaders = {
127
+ 'Retry-After': response.headers.get('retry-after') || undefined,
128
+ 'X-PCO-API-Request-Rate-Count': response.headers.get('x-pco-api-request-rate-count') || undefined,
129
+ 'X-PCO-API-Request-Rate-Limit': response.headers.get('x-pco-api-request-rate-limit') || undefined,
130
+ 'X-PCO-API-Request-Rate-Period': response.headers.get('x-pco-api-request-rate-period') || undefined,
131
+ };
132
+ this.rateLimiter.updateFromHeaders(rateLimitHeaders);
133
+ this.rateLimiter.recordRequest();
134
+ // Handle 429 responses
135
+ if (response.status === 429) {
136
+ await this.rateLimiter.waitForAvailability();
137
+ return this.makeRequest(options, requestId);
138
+ }
139
+ // Handle other errors
140
+ if (!response.ok) {
141
+ // Handle 401 errors with token refresh if available
142
+ // Convert v2.0 config to v1.x format for auth functions
143
+ const v1Config = {
144
+ refreshToken: this.config.auth.refreshToken,
145
+ onTokenRefresh: this.config.auth.onRefresh,
146
+ onTokenRefreshFailure: this.config.auth.onRefreshFailure,
147
+ };
148
+ const clientState = { config: v1Config, rateLimiter: this.rateLimiter };
149
+ if (response.status === 401 && (0, auth_1.hasRefreshTokenCapability)(clientState)) {
150
+ try {
151
+ await (0, auth_1.attemptTokenRefresh)(clientState, () => this.makeRequest(options, requestId));
152
+ return this.makeRequest(options, requestId);
153
+ }
154
+ catch (refreshError) {
155
+ console.warn('Token refresh failed:', refreshError);
156
+ }
157
+ }
158
+ let errorData;
159
+ try {
160
+ errorData = await response.json();
161
+ }
162
+ catch {
163
+ errorData = {};
164
+ }
165
+ throw api_error_1.PcoApiError.fromFetchError(response, errorData);
166
+ }
167
+ // Parse response
168
+ if (options.method === 'DELETE') {
169
+ return {
170
+ data: undefined,
171
+ status: response.status,
172
+ headers: this.extractHeaders(response),
173
+ requestId,
174
+ duration: 0, // Will be set by caller
175
+ };
176
+ }
177
+ const data = await response.json();
178
+ return {
179
+ data,
180
+ status: response.status,
181
+ headers: this.extractHeaders(response),
182
+ requestId,
183
+ duration: 0, // Will be set by caller
184
+ };
185
+ }
186
+ catch (error) {
187
+ clearTimeout(timeoutId);
188
+ throw error;
189
+ }
190
+ }
191
+ addAuthentication(headers) {
192
+ if (this.config.auth.personalAccessToken) {
193
+ // Personal Access Tokens use HTTP Basic Auth format: app_id:secret
194
+ // The personalAccessToken should be in the format "app_id:secret"
195
+ headers.Authorization = `Basic ${Buffer.from(this.config.auth.personalAccessToken).toString('base64')}`;
196
+ }
197
+ else if (this.config.auth.accessToken) {
198
+ headers.Authorization = `Bearer ${this.config.auth.accessToken}`;
199
+ }
200
+ }
201
+ getResourceTypeFromEndpoint(endpoint) {
202
+ // Extract resource type from endpoint
203
+ // /households -> Household
204
+ // /people -> Person
205
+ // /emails -> Email
206
+ // etc.
207
+ const pathParts = endpoint.split('/').filter(part => part.length > 0);
208
+ const resourcePath = pathParts[pathParts.length - 1];
209
+ // Convert kebab-case to PascalCase and make singular
210
+ const pascalCase = resourcePath
211
+ .split('-')
212
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
213
+ .join('');
214
+ // Make singular (remove trailing 's' if it exists and the word is longer than 3 characters)
215
+ if (pascalCase.endsWith('s') && pascalCase.length > 3) {
216
+ return pascalCase.slice(0, -1);
217
+ }
218
+ return pascalCase;
219
+ }
220
+ extractHeaders(response) {
221
+ const headers = {};
222
+ response.headers.forEach((value, key) => {
223
+ headers[key] = value;
224
+ });
225
+ return headers;
226
+ }
227
+ updateRateLimitTracking(endpoint, headers) {
228
+ const limit = headers['x-pco-api-request-rate-limit'];
229
+ const remaining = headers['x-pco-api-request-rate-count'];
230
+ const resetTime = headers['retry-after'];
231
+ if (limit && remaining && resetTime) {
232
+ this.rateLimitTracker.update(endpoint, parseInt(limit), parseInt(remaining), Date.now() + parseInt(resetTime) * 1000);
233
+ }
234
+ }
235
+ getPerformanceMetrics() {
236
+ return this.performanceMetrics.getMetrics();
237
+ }
238
+ getRateLimitInfo() {
239
+ return this.rateLimitTracker.getAllLimits();
240
+ }
241
+ }
242
+ exports.PcoHttpClient = PcoHttpClient;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * v2.0.0 Pagination Utilities
3
+ */
4
+ import type { ResourceObject, Paginated } from '../types';
5
+ import type { PcoHttpClient } from './http';
6
+ export interface PaginationOptions {
7
+ /** Maximum number of pages to fetch */
8
+ maxPages?: number;
9
+ /** Items per page */
10
+ perPage?: number;
11
+ /** Progress callback */
12
+ onProgress?: (current: number, total: number) => void;
13
+ /** Delay between requests in milliseconds */
14
+ delay?: number;
15
+ }
16
+ export interface PaginationResult<T> {
17
+ data: T[];
18
+ totalCount: number;
19
+ pagesFetched: number;
20
+ duration: number;
21
+ }
22
+ export declare class PaginationHelper {
23
+ private httpClient;
24
+ constructor(httpClient: PcoHttpClient);
25
+ getAllPages<T extends ResourceObject<string, any, any>>(endpoint: string, params?: Record<string, any>, options?: PaginationOptions): Promise<PaginationResult<T>>;
26
+ getPage<T extends ResourceObject<string, any, any>>(endpoint: string, page?: number, perPage?: number, params?: Record<string, any>): Promise<Paginated<T>>;
27
+ streamPages<T extends ResourceObject<string, any, any>>(endpoint: string, params?: Record<string, any>, options?: PaginationOptions): AsyncGenerator<T[], void, unknown>;
28
+ /**
29
+ * Get all pages with parallel processing for better performance
30
+ */
31
+ getAllPagesParallel<T extends ResourceObject<string, any, any>>(endpoint: string, params?: Record<string, any>, options?: PaginationOptions & {
32
+ maxConcurrency?: number;
33
+ }): Promise<PaginationResult<T>>;
34
+ }