@rachelallyson/planning-center-people-ts 2.14.1 → 3.1.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 (77) hide show
  1. package/CHANGELOG.md +82 -7
  2. package/README.md +42 -4
  3. package/dist/auth.d.ts +1 -1
  4. package/dist/auth.js +14 -6
  5. package/dist/client.d.ts +33 -8
  6. package/dist/client.js +47 -22
  7. package/dist/core.d.ts +4 -2
  8. package/dist/core.js +3 -2
  9. package/dist/error-handling.d.ts +4 -4
  10. package/dist/error-handling.js +13 -2
  11. package/dist/error-scenarios.d.ts +11 -7
  12. package/dist/error-scenarios.js +26 -10
  13. package/dist/helpers.d.ts +124 -48
  14. package/dist/helpers.js +237 -93
  15. package/dist/index.d.ts +10 -8
  16. package/dist/index.js +31 -72
  17. package/dist/matching/matcher.d.ts +8 -4
  18. package/dist/matching/matcher.js +51 -58
  19. package/dist/matching/scoring.d.ts +9 -6
  20. package/dist/matching/scoring.js +18 -14
  21. package/dist/modules/campus.d.ts +31 -36
  22. package/dist/modules/campus.js +36 -49
  23. package/dist/modules/contacts.d.ts +33 -29
  24. package/dist/modules/contacts.js +36 -12
  25. package/dist/modules/fields.d.ts +39 -55
  26. package/dist/modules/fields.js +65 -105
  27. package/dist/modules/forms.d.ts +35 -24
  28. package/dist/modules/forms.js +41 -23
  29. package/dist/modules/households.d.ts +17 -19
  30. package/dist/modules/households.js +25 -34
  31. package/dist/modules/lists.d.ts +38 -28
  32. package/dist/modules/lists.js +62 -42
  33. package/dist/modules/notes.d.ts +32 -30
  34. package/dist/modules/notes.js +40 -52
  35. package/dist/modules/people.d.ts +83 -71
  36. package/dist/modules/people.js +323 -172
  37. package/dist/modules/reports.d.ts +18 -32
  38. package/dist/modules/reports.js +28 -40
  39. package/dist/modules/service-time.d.ts +19 -24
  40. package/dist/modules/service-time.js +28 -28
  41. package/dist/modules/workflows.d.ts +42 -47
  42. package/dist/modules/workflows.js +52 -53
  43. package/dist/performance.d.ts +14 -10
  44. package/dist/performance.js +61 -25
  45. package/dist/testing/recorder.js +11 -2
  46. package/dist/testing/simple-builders.d.ts +6 -4
  47. package/dist/testing/simple-builders.js +36 -49
  48. package/dist/testing/types.d.ts +4 -0
  49. package/dist/types/api-options.d.ts +380 -0
  50. package/dist/types/api-options.js +6 -0
  51. package/dist/types/client.d.ts +4 -2
  52. package/dist/types/client.js +1 -1
  53. package/dist/types/index.d.ts +1 -1
  54. package/dist/types/people.d.ts +61 -9
  55. package/package.json +7 -7
  56. package/dist/core/http.d.ts +0 -56
  57. package/dist/core/http.js +0 -360
  58. package/dist/core/pagination.d.ts +0 -34
  59. package/dist/core/pagination.js +0 -178
  60. package/dist/people/contacts.d.ts +0 -43
  61. package/dist/people/contacts.js +0 -122
  62. package/dist/people/core.d.ts +0 -28
  63. package/dist/people/core.js +0 -69
  64. package/dist/people/fields.d.ts +0 -68
  65. package/dist/people/fields.js +0 -305
  66. package/dist/people/households.d.ts +0 -15
  67. package/dist/people/households.js +0 -31
  68. package/dist/people/index.d.ts +0 -8
  69. package/dist/people/index.js +0 -25
  70. package/dist/people/lists.d.ts +0 -34
  71. package/dist/people/lists.js +0 -48
  72. package/dist/people/notes.d.ts +0 -30
  73. package/dist/people/notes.js +0 -37
  74. package/dist/people/organization.d.ts +0 -12
  75. package/dist/people/organization.js +0 -15
  76. package/dist/people/workflows.d.ts +0 -37
  77. package/dist/people/workflows.js +0 -75
package/CHANGELOG.md CHANGED
@@ -5,8 +5,55 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.1.0] - 2026-02-05
9
+
10
+ ### Added
11
+
12
+ - **ListsModule.getRules(listId)**: New method to fetch rules for a list (`GET /people/v2/lists/:id/rules`). Returns paginated list of rules with `data`, `meta`, and `links`. Types `ListRuleResource`, `ListRuleAttributes`, and `ListRulesList` are exported.
13
+
14
+ ## [3.0.0] - 2026-01-28
15
+
16
+ ### Added
17
+
18
+ - **Debug logging**: Set `config.debug: true` (or an options object) when creating the client to see detailed logs (rate limiting, retries, each request, and each module call). Helpers `createDebugLogger`, `attachDebugListener`, and `formatDebugEvent` are exported; see `docs/DEBUG_LOGGING.md` if present.
19
+ - **Person create/update with snake_case**: You can pass snake_case attributes (e.g. `first_name`, `last_name`) to `client.people.create()` and `client.people.update()` in addition to camelCase; both are accepted.
20
+ - **Stricter list options**: List and page options (e.g. `PersonListOptions`, `WorkflowPageOptions`) are fully typed per endpoint. Types such as `PersonInclude`, `PersonOrderField`, `PersonWhereClause` (and equivalents for other resources) are exported for better autocomplete and type safety.
21
+ - **Included-data helpers**: `findIncluded`, `resolveIncluded`, and `createIncludedLookup` are exported for working with JSON:API `included` data.
22
+ - **Event types**: When you use `client.on('request:start', ...)` (and other events), TypeScript narrows the handler argument to the correct event type via overloads.
23
+ - **`getPage()`** on all list-capable modules for single-page fetching (e.g. `client.people.getPage({ perPage: 25, page: 1, where: { status: 'active' } })`).
24
+
25
+ ### Changed
26
+
27
+ - **Responses with `include`**: When you request related data (e.g. `getById(id, ['primary_campus'])`), that data now appears at the top level (e.g. `person.primary_campus`) as well as in the response structure.
28
+ - **Contacts create methods** now require the person ID as the first argument (API is person-scoped): `createEmail(personId, data)`, `createPhoneNumber(personId, data)`, `createAddress(personId, data)`, `createSocialProfile(personId, data)`.
29
+ - **Single-page fetching**: `getAllPagesPaginated()` has been removed from all modules. Use `getPage(options)` instead.
30
+ - **Low-level HTTP helpers** are no longer exported: `del`, `getAllPages`, `getList`, `getSingle`, `patch`, `post`. Use module methods or `createPcoClient` / `getRateLimitInfo` from core.
31
+ - **Function-style API** has been removed: standalone functions such as `createPerson`, `getPerson`, `getHouseholds`, `getLists` are no longer exported. Use the client and its modules (e.g. `client.people.create`, `client.contacts.createEmail(personId, data)`, `client.households.getAll()`).
32
+ - **`buildQueryParams`** is no longer exported from this package; use module methods and the exported option types. It remains available from `@rachelallyson/planning-center-base-ts` if needed.
33
+ - **Modules**: All 11 modules now receive a config getter for debug and pass option objects into base for query building. See `MODULE_CHANGES.md` for a per-module summary.
34
+
35
+ ### Breaking changes
36
+
37
+ - **Response shape (flattened data)**: All methods now return **flattened** resources (from base `mapIncludedToRelationships`). Use `resource.first_name` instead of `resource.attributes.first_name`, and `resource.emails` instead of `resource.relationships.emails.data` and `response.included`. Applies to getById, getPage, getAll, create, and update responses.
38
+ - **Contacts**: If you use `client.contacts.createEmail(data)` (or the same for phone/address/social profile), you must switch to `createEmail(personId, data)` (and similarly for `createPhoneNumber`, `createAddress`, `createSocialProfile`).
39
+ - **Function API**: If you import the old function API (`createPerson`, `getPerson`, `getHouseholds`, `getLists`, etc.), replace those calls with the module API on a `PcoClient` instance.
40
+
41
+ ### Dependency
42
+
43
+ - This release depends on `@rachelallyson/planning-center-base-ts` `^1.1.1`. It is installed automatically when you install the people package.
44
+
8
45
  ## [2.14.1] - 2026-01-21
9
46
 
47
+ ### ✨ **New Features**
48
+
49
+ - **Strictly Typed API Options**: Added comprehensive TypeScript types for all endpoint parameters
50
+ - Created `api-options.ts` with strict types for Include, OrderField, WhereClause, and ListOptions
51
+ - Fully typed: Person, FieldDefinition, Workflow, Note, List, Household endpoints
52
+ - Basic structure for: Campus, Form, Report, ServiceTime (can be enhanced with full API doc extraction)
53
+ - All types exported from main index for easy import
54
+ - Replaces `Record<string, any>` with strict interfaces for better type safety and IDE autocomplete
55
+ - Added `order` parameter support to people, workflows, notes, lists, households modules
56
+
10
57
  ### 🔧 **Bug Fixes**
11
58
 
12
59
  - **Complete Tab API Coverage**: Added missing `getTabById()` / `getTab()` method to retrieve a single tab by ID
@@ -14,6 +61,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
14
61
  - Function API: `getTab(client, tabId, params?, context?)`
15
62
  - Completes full CRUD coverage for tabs (get, list, create, update, delete)
16
63
 
64
+ - **Fixed `getAll()` Methods to Actually Get All Pages**: All `getAll()` methods now fetch all pages instead of just one
65
+ - Fixed: `people.getAll()`, `workflows.getAll()`, `notes.getAll()`, `lists.getAll()`, `households.getAll()`, `campus.getAll()`, `forms.getAll()`, `reports.getAll()`, `service-time.getAll()`
66
+ - Previously used `getList()` (one page), now uses `getAllPages()` (all pages)
67
+ - Note: `perPage` and `page` options are ignored when using `getAll()` - it automatically fetches all pages
68
+
69
+ - **Removed `getAllPagesPaginated()` Methods**: Removed redundant `getAllPagesPaginated()` methods from all modules
70
+ - `getAll()` now handles fetching all pages automatically
71
+ - Removed from: `people`, `workflows`, `notes`, `lists`, `households`, `campus`, `forms`, `reports`, `service-time` modules
72
+
73
+ - **Added `getPage()` Methods for Single Page Fetching**: Added `getPage()` methods to all modules for fetching a single page with full pagination control
74
+ - Use `getAll()` when you need all pages automatically
75
+ - Use `getPage()` when you need a specific page, custom per_page, or want to limit results
76
+ - Available on: `people.getPage()`, `workflows.getPage()`, `notes.getPage()`, `lists.getPage()`, `households.getPage()`, `campus.getPage()`, `forms.getPage()`, `reports.getPage()`, `service-time.getPage()`
77
+ - Example: `client.people.getPage({ perPage: 25, page: 1, where: { status: 'active' } })`
78
+
79
+ - **Fixed `getAllFieldDefinitions()` Pagination**: Changed `include: ['tab']` to `include: 'tab'` to properly fetch all pages
80
+ - `getAllPages()` expects query params where `include` is a comma-separated string, not an array
81
+
82
+ - **Made `getAllFieldDefinitions()` Include Parameter Optional**: `getAllFieldDefinitions()` now accepts an optional `include` parameter
83
+ - Defaults to `['tab']` if not provided (maintains backward compatibility)
84
+ - Can pass custom include array: `getAllFieldDefinitions(['tab', 'field_options'])`
85
+ - Can pass empty array to exclude relationships: `getAllFieldDefinitions([])`
86
+
87
+ - **Added `where` Filtering, `order`, and `includeDeleted` Option to `getAllFieldDefinitions()`**: Enhanced `getAllFieldDefinitions()` with full API parameter support
88
+ - Added `where` parameter for filtering: `getAllFieldDefinitions(['tab'], { where: { tab_id: '123', data_type: 'string' } })`
89
+ - Added `order` parameter for sorting: `getAllFieldDefinitions(['tab'], { order: 'sequence' })` or `{ order: '-name' }` for descending
90
+ - Added `includeDeleted` option: `getAllFieldDefinitions(['tab'], { includeDeleted: true })`
91
+ - Valid where keys: `config`, `data_type`, `deleted_at`, `name`, `sequence`, `slug`, `tab_id`
92
+ - Valid order values: `config`, `data_type`, `deleted_at`, `name`, `sequence`, `slug`, `tab_id` (prefix with `-` for descending)
93
+
17
94
  ### 📦 **Exports**
18
95
 
19
96
  - `getTab` - Function API for retrieving a single tab by ID
@@ -21,6 +98,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
21
98
  ### 🧪 **Testing**
22
99
 
23
100
  - Added tests for `getTabById()` / `getTab()` in fields test suites
101
+ - Updated all `getAll()` tests to mock `paginationHelper.getAllPages()` instead of `httpClient.request()`
24
102
 
25
103
  ## [2.14.0] - 2026-01-21
26
104
 
@@ -517,11 +595,11 @@ No breaking changes - this is a type enhancement release:
517
595
  // Existing code continues to work
518
596
  const fields = await client.people.getFieldDefinitions();
519
597
 
520
- // New type safety benefits
598
+ // New type safety benefits (flattened resources: attributes at top level)
521
599
  fields.data.forEach(field => {
522
- if (field.attributes.data_type === 'select') {
600
+ if (field.data_type === 'select') {
523
601
  // TypeScript knows this is a select field
524
- console.log('Select field:', field.attributes.name);
602
+ console.log('Select field:', field.name);
525
603
  }
526
604
  });
527
605
  ```
@@ -555,7 +633,7 @@ No breaking changes - this is a bug fix release:
555
633
  const person = await client.people.findOrCreate({
556
634
  firstName: 'John',
557
635
  lastName: 'Doe',
558
- email: 'john@example.com',
636
+ email: 'john@gmail.com
559
637
  phone: '555-1234',
560
638
  campusId: 'campus-123' // NEW: Campus assignment support
561
639
  });
@@ -885,7 +963,6 @@ This release adds three high-priority modules to extend the Planning Center Peop
885
963
  - `client.serviceTime.create(campusId, data)` - Create new service time
886
964
  - `client.serviceTime.update(campusId, id, data)` - Update existing service time
887
965
  - `client.serviceTime.delete(campusId, id)` - Delete service time
888
- - `client.serviceTime.getAllPagesPaginated(campusId, params?)` - Get all service times with pagination
889
966
 
890
967
  **ServiceTime Resource Structure:**
891
968
 
@@ -935,7 +1012,6 @@ This release adds three high-priority modules to extend the Planning Center Peop
935
1012
  - `client.reports.delete(id)` - Delete report
936
1013
  - `client.reports.getCreatedBy(reportId)` - Get report creator
937
1014
  - `client.reports.getUpdatedBy(reportId)` - Get report updater
938
- - `client.reports.getAllPagesPaginated(params?)` - Get all reports with pagination
939
1015
 
940
1016
  **Reports Resource Structure:**
941
1017
 
@@ -1014,7 +1090,6 @@ This release adds comprehensive Campus management functionality to the Planning
1014
1090
  - `client.campus.delete(id)` - Delete campus
1015
1091
  - `client.campus.getLists(campusId)` - Get lists for a specific campus
1016
1092
  - `client.campus.getServiceTimes(campusId)` - Get service times for a specific campus
1017
- - `client.campus.getAllPagesPaginated()` - Get all campuses with automatic pagination
1018
1093
 
1019
1094
  #### **🏗️ Campus Resource Structure**
1020
1095
 
package/README.md CHANGED
@@ -61,7 +61,7 @@ const person = await getPerson(client, 'person-id', ['emails']);
61
61
  const newPerson = await createPerson(client, {
62
62
  first_name: 'John',
63
63
  last_name: 'Doe',
64
- email: 'john.doe@example.com',
64
+ email: 'john.doe@gmail.com',
65
65
  });
66
66
 
67
67
  // Update a person
@@ -86,6 +86,44 @@ await createPersonFieldData(
86
86
 
87
87
  ## Configuration
88
88
 
89
+ ### Debug logging
90
+
91
+ Turn logs on or off to see everything that happens inside the package (requests, auth, rate limit, cache, errors). Debug is provided by the base package and shared across all PCO clients. Enable at creation or at runtime:
92
+
93
+ ```typescript
94
+ import { PcoClient } from '@rachelallyson/planning-center-people-ts';
95
+
96
+ // Enable at creation
97
+ const client = new PcoClient({
98
+ auth: { type: 'personal_access_token', personalAccessToken: '...' },
99
+ debug: true,
100
+ });
101
+
102
+ // Or with options
103
+ const client = new PcoClient({
104
+ auth: { ... },
105
+ debug: {
106
+ prefix: '[MyApp]',
107
+ includePayloads: false, // set true to log request/response bodies (avoid in production)
108
+ onLog: (message, data) => myLogger.info(message, data),
109
+ },
110
+ });
111
+
112
+ // Toggle at runtime
113
+ client.updateConfig({ debug: true });
114
+ client.updateConfig({ debug: false });
115
+ ```
116
+
117
+ With `debug: true`, every event is logged with a clear prefix (default `[PCO People]`), including:
118
+
119
+ - `→` request start (method, endpoint, requestId)
120
+ - `←` request complete (status, duration)
121
+ - `✗` request error
122
+ - auth success/failure/refresh
123
+ - rate limit and rate available
124
+ - cache hit/miss/set/invalidate
125
+ - generic errors
126
+
89
127
  ### Authentication
90
128
 
91
129
  The client supports two authentication methods:
@@ -364,10 +402,10 @@ const people = await getPeople(client, { per_page: 10 }, {
364
402
  All functions are fully typed with TypeScript:
365
403
 
366
404
  ```typescript
367
- // TypeScript knows exactly what properties are available
405
+ // TypeScript knows exactly what properties are available (flattened: attributes at top level)
368
406
  const person = await getPerson(client, 'person-id');
369
- console.log(person.data?.attributes?.first_name); // ✅ TypeScript knows this exists
370
- console.log(person.data?.attributes?.invalid_prop); // ❌ TypeScript error
407
+ console.log(person.data?.first_name); // ✅ TypeScript knows this exists
408
+ console.log(person.data?.invalid_prop); // ❌ TypeScript error
371
409
 
372
410
  // Creating resources is type-safe
373
411
  const newPerson = await createPerson(client, {
package/dist/auth.d.ts CHANGED
@@ -43,7 +43,7 @@ export interface PcoClientConfigWithRefresh {
43
43
  maxRetries?: number;
44
44
  baseDelay?: number;
45
45
  maxDelay?: number;
46
- onRetry?: (error: any, attempt: number) => void;
46
+ onRetry?: (error: unknown, attempt: number) => void;
47
47
  };
48
48
  }
49
49
  /**
package/dist/auth.js CHANGED
@@ -4,6 +4,7 @@ exports.refreshAccessToken = refreshAccessToken;
4
4
  exports.updateClientTokens = updateClientTokens;
5
5
  exports.hasRefreshTokenCapability = hasRefreshTokenCapability;
6
6
  exports.attemptTokenRefresh = attemptTokenRefresh;
7
+ const planning_center_base_ts_1 = require("@rachelallyson/planning-center-base-ts");
7
8
  /**
8
9
  * Refresh an OAuth access token using the refresh token
9
10
  */
@@ -59,25 +60,31 @@ async function attemptTokenRefresh(client, originalRequest) {
59
60
  if (!hasRefreshTokenCapability(client)) {
60
61
  throw new Error('No refresh token or callback configured');
61
62
  }
63
+ const logger = (0, planning_center_base_ts_1.createDebugLogger)(client.config);
64
+ if (logger.enabled)
65
+ logger.log('auth attemptTokenRefresh start', {});
62
66
  try {
63
- // Attempt to refresh the token
64
67
  const newTokens = await refreshAccessToken(client, client.config.refreshToken);
65
- // Update the client with new tokens
66
68
  updateClientTokens(client, newTokens);
69
+ if (logger.enabled)
70
+ logger.log('auth attemptTokenRefresh success', {});
67
71
  // Call the token refresh callback if provided
68
72
  if (client.config.onTokenRefresh) {
69
73
  try {
70
74
  await client.config.onTokenRefresh(newTokens);
71
75
  }
72
76
  catch (callbackError) {
73
- // Log callback error but don't fail the token refresh
74
- console.warn('Token refresh callback failed:', callbackError);
77
+ const logger = (0, planning_center_base_ts_1.createDebugLogger)(client.config);
78
+ if (logger.enabled)
79
+ logger.log('auth token refresh callback failed', { error: String(callbackError) });
75
80
  }
76
81
  }
77
82
  // Retry the original request with the new token
78
83
  return await originalRequest();
79
84
  }
80
85
  catch (error) {
86
+ if (logger.enabled)
87
+ logger.log('auth attemptTokenRefresh failed', { error: String(error) });
81
88
  const refreshError = new Error(`Token refresh failed: ${error instanceof Error ? error.message : String(error)}`);
82
89
  // Call the failure callback if provided
83
90
  if (client.config.onTokenRefreshFailure) {
@@ -89,8 +96,9 @@ async function attemptTokenRefresh(client, originalRequest) {
89
96
  });
90
97
  }
91
98
  catch (callbackError) {
92
- // Log callback error but don't fail the refresh error
93
- console.warn('Token refresh failure callback failed:', callbackError);
99
+ const logger = (0, planning_center_base_ts_1.createDebugLogger)(client.config);
100
+ if (logger.enabled)
101
+ logger.log('auth token refresh failure callback failed', { error: String(callbackError) });
94
102
  }
95
103
  }
96
104
  throw refreshError;
package/dist/client.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * v2.0.0 Main PcoClient Class
3
3
  */
4
- import type { PcoClientConfig } from './types/client';
5
- import type { EventEmitter, PcoEvent, EventHandler, EventType } from '@rachelallyson/planning-center-base-ts';
4
+ import type { PeopleClientConfig } from './types/client';
5
+ import type { EventEmitter as BaseEventEmitter, PcoEvent, EventHandler, EventType, EventRequestStartEvent, EventRequestCompleteEvent, RequestErrorEvent, AuthSuccessEvent, EventAuthFailureEvent, AuthRefreshEvent, EventRateLimitEvent, RateAvailableEvent, CacheHitEvent, CacheMissEvent, CacheSetEvent, CacheInvalidateEvent, EventErrorEvent } from '@rachelallyson/planning-center-base-ts';
6
6
  import { BatchExecutor } from '@rachelallyson/planning-center-base-ts';
7
7
  import { PeopleModule } from './modules/people';
8
8
  import { FieldsModule } from './modules/fields';
@@ -15,7 +15,7 @@ import { CampusModule } from './modules/campus';
15
15
  import { ServiceTimeModule } from './modules/service-time';
16
16
  import { FormsModule } from './modules/forms';
17
17
  import { ReportsModule } from './modules/reports';
18
- export declare class PcoClient implements EventEmitter {
18
+ export declare class PcoClient implements BaseEventEmitter {
19
19
  people: PeopleModule;
20
20
  fields: FieldsModule;
21
21
  workflows: WorkflowsModule;
@@ -32,18 +32,43 @@ export declare class PcoClient implements EventEmitter {
32
32
  private paginationHelper;
33
33
  private eventEmitter;
34
34
  private config;
35
- constructor(config: PcoClientConfig);
36
- on<T extends PcoEvent>(eventType: T['type'], handler: EventHandler<T>): void;
37
- off<T extends PcoEvent>(eventType: T['type'], handler: EventHandler<T>): void;
35
+ private debugUnsubscribe;
36
+ constructor(config: PeopleClientConfig);
37
+ on(eventType: 'request:start', handler: EventHandler<EventRequestStartEvent>): void;
38
+ on(eventType: 'request:complete', handler: EventHandler<EventRequestCompleteEvent>): void;
39
+ on(eventType: 'request:error', handler: EventHandler<RequestErrorEvent>): void;
40
+ on(eventType: 'auth:success', handler: EventHandler<AuthSuccessEvent>): void;
41
+ on(eventType: 'auth:failure', handler: EventHandler<EventAuthFailureEvent>): void;
42
+ on(eventType: 'auth:refresh', handler: EventHandler<AuthRefreshEvent>): void;
43
+ on(eventType: 'rate:limit', handler: EventHandler<EventRateLimitEvent>): void;
44
+ on(eventType: 'rate:available', handler: EventHandler<RateAvailableEvent>): void;
45
+ on(eventType: 'cache:hit', handler: EventHandler<CacheHitEvent>): void;
46
+ on(eventType: 'cache:miss', handler: EventHandler<CacheMissEvent>): void;
47
+ on(eventType: 'cache:set', handler: EventHandler<CacheSetEvent>): void;
48
+ on(eventType: 'cache:invalidate', handler: EventHandler<CacheInvalidateEvent>): void;
49
+ on(eventType: 'error', handler: EventHandler<EventErrorEvent>): void;
50
+ off(eventType: 'request:start', handler: EventHandler<EventRequestStartEvent>): void;
51
+ off(eventType: 'request:complete', handler: EventHandler<EventRequestCompleteEvent>): void;
52
+ off(eventType: 'request:error', handler: EventHandler<RequestErrorEvent>): void;
53
+ off(eventType: 'auth:success', handler: EventHandler<AuthSuccessEvent>): void;
54
+ off(eventType: 'auth:failure', handler: EventHandler<EventAuthFailureEvent>): void;
55
+ off(eventType: 'auth:refresh', handler: EventHandler<AuthRefreshEvent>): void;
56
+ off(eventType: 'rate:limit', handler: EventHandler<EventRateLimitEvent>): void;
57
+ off(eventType: 'rate:available', handler: EventHandler<RateAvailableEvent>): void;
58
+ off(eventType: 'cache:hit', handler: EventHandler<CacheHitEvent>): void;
59
+ off(eventType: 'cache:miss', handler: EventHandler<CacheMissEvent>): void;
60
+ off(eventType: 'cache:set', handler: EventHandler<CacheSetEvent>): void;
61
+ off(eventType: 'cache:invalidate', handler: EventHandler<CacheInvalidateEvent>): void;
62
+ off(eventType: 'error', handler: EventHandler<EventErrorEvent>): void;
38
63
  emit<T extends PcoEvent>(event: T): void;
39
64
  /**
40
65
  * Get the current configuration
41
66
  */
42
- getConfig(): PcoClientConfig;
67
+ getConfig(): PeopleClientConfig;
43
68
  /**
44
69
  * Update the configuration
45
70
  */
46
- updateConfig(updates: Partial<PcoClientConfig>): void;
71
+ updateConfig(updates: Partial<PeopleClientConfig>): void;
47
72
  /**
48
73
  * Get performance metrics
49
74
  */
package/dist/client.js CHANGED
@@ -18,30 +18,38 @@ const forms_1 = require("./modules/forms");
18
18
  const reports_1 = require("./modules/reports");
19
19
  class PcoClient {
20
20
  constructor(config) {
21
+ this.debugUnsubscribe = null;
21
22
  this.config = config;
22
23
  this.eventEmitter = new planning_center_base_ts_1.PcoEventEmitter();
23
24
  this.httpClient = new planning_center_base_ts_1.PcoHttpClient(config, this.eventEmitter);
24
- this.paginationHelper = new planning_center_base_ts_1.PaginationHelper(this.httpClient);
25
+ this.paginationHelper = new planning_center_base_ts_1.PaginationHelper(this.httpClient, () => this.getConfig());
25
26
  // Initialize modules
26
- this.people = new people_1.PeopleModule(this.httpClient, this.paginationHelper, this.eventEmitter);
27
- this.fields = new fields_1.FieldsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
28
- this.workflows = new workflows_1.WorkflowsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
29
- this.contacts = new contacts_1.ContactsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
30
- this.households = new households_1.HouseholdsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
31
- this.notes = new notes_1.NotesModule(this.httpClient, this.paginationHelper, this.eventEmitter);
32
- this.lists = new lists_1.ListsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
33
- this.campus = new campus_1.CampusModule(this.httpClient, this.paginationHelper, this.eventEmitter);
34
- this.serviceTime = new service_time_1.ServiceTimeModule(this.httpClient, this.paginationHelper, this.eventEmitter);
35
- this.forms = new forms_1.FormsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
36
- this.reports = new reports_1.ReportsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
27
+ this.people = new people_1.PeopleModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
28
+ this.fields = new fields_1.FieldsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
29
+ this.workflows = new workflows_1.WorkflowsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
30
+ this.contacts = new contacts_1.ContactsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
31
+ this.households = new households_1.HouseholdsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
32
+ this.notes = new notes_1.NotesModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
33
+ this.lists = new lists_1.ListsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
34
+ this.campus = new campus_1.CampusModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
35
+ this.serviceTime = new service_time_1.ServiceTimeModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
36
+ this.forms = new forms_1.FormsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
37
+ this.reports = new reports_1.ReportsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
37
38
  this.batch = new planning_center_base_ts_1.BatchExecutor(this, this.eventEmitter);
38
- // Set up event handlers from config
39
+ // Debug: subscribe to all events when debug is enabled (listener checks config each time so runtime toggle works)
40
+ if (config.debug) {
41
+ this.debugUnsubscribe = (0, planning_center_base_ts_1.attachDebugListener)(this, () => this.config);
42
+ (0, planning_center_base_ts_1.createDebugLogger)(config).log('client debug enabled', { eventListener: true });
43
+ }
39
44
  }
40
- // EventEmitter implementation
41
45
  on(eventType, handler) {
46
+ // TypeScript can't narrow T['type'] to specific literal types, but the runtime types match
47
+ // The overloads above handle the specific cases, this is for dynamic usage
42
48
  this.eventEmitter.on(eventType, handler);
43
49
  }
44
50
  off(eventType, handler) {
51
+ // TypeScript can't narrow T['type'] to specific literal types, but the runtime types match
52
+ // The overloads above handle the specific cases, this is for dynamic usage
45
53
  this.eventEmitter.off(eventType, handler);
46
54
  }
47
55
  emit(event) {
@@ -57,10 +65,23 @@ class PcoClient {
57
65
  * Update the configuration
58
66
  */
59
67
  updateConfig(updates) {
68
+ const hadDebug = Boolean(this.config.debug);
60
69
  this.config = { ...this.config, ...updates };
70
+ const hasDebug = Boolean(this.config.debug);
71
+ const logger = (0, planning_center_base_ts_1.createDebugLogger)(this.config);
72
+ if (logger.enabled)
73
+ logger.log('client.updateConfig', { updates });
74
+ // Attach or detach debug listener when debug is toggled
75
+ if (hadDebug && !hasDebug && this.debugUnsubscribe) {
76
+ this.debugUnsubscribe();
77
+ this.debugUnsubscribe = null;
78
+ }
79
+ else if (!hadDebug && hasDebug) {
80
+ this.debugUnsubscribe = (0, planning_center_base_ts_1.attachDebugListener)(this, () => this.config);
81
+ }
61
82
  // Recreate HTTP client with new config
62
83
  this.httpClient = new planning_center_base_ts_1.PcoHttpClient(this.config, this.eventEmitter);
63
- this.paginationHelper = new planning_center_base_ts_1.PaginationHelper(this.httpClient);
84
+ this.paginationHelper = new planning_center_base_ts_1.PaginationHelper(this.httpClient, () => this.getConfig());
64
85
  // Update modules with new HTTP client
65
86
  this.updateModules();
66
87
  }
@@ -96,13 +117,17 @@ class PcoClient {
96
117
  }
97
118
  updateModules() {
98
119
  // Recreate modules with new HTTP client
99
- this.people = new people_1.PeopleModule(this.httpClient, this.paginationHelper, this.eventEmitter);
100
- this.fields = new fields_1.FieldsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
101
- this.workflows = new workflows_1.WorkflowsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
102
- this.contacts = new contacts_1.ContactsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
103
- this.households = new households_1.HouseholdsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
104
- this.notes = new notes_1.NotesModule(this.httpClient, this.paginationHelper, this.eventEmitter);
105
- this.lists = new lists_1.ListsModule(this.httpClient, this.paginationHelper, this.eventEmitter);
120
+ this.people = new people_1.PeopleModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
121
+ this.fields = new fields_1.FieldsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
122
+ this.workflows = new workflows_1.WorkflowsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
123
+ this.contacts = new contacts_1.ContactsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
124
+ this.households = new households_1.HouseholdsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
125
+ this.notes = new notes_1.NotesModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
126
+ this.lists = new lists_1.ListsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
127
+ this.campus = new campus_1.CampusModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
128
+ this.serviceTime = new service_time_1.ServiceTimeModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
129
+ this.forms = new forms_1.FormsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
130
+ this.reports = new reports_1.ReportsModule(this.httpClient, this.paginationHelper, this.eventEmitter, () => this.getConfig());
106
131
  this.batch = new planning_center_base_ts_1.BatchExecutor(this, this.eventEmitter);
107
132
  }
108
133
  }
package/dist/core.d.ts CHANGED
@@ -37,6 +37,8 @@ export interface PcoClientConfig {
37
37
  maxDelay?: number;
38
38
  onRetry?: (error: PcoError, attempt: number) => void;
39
39
  };
40
+ /** Enable debug logging (same as base package; optional for v1 createPcoClient flow) */
41
+ debug?: boolean | import('@rachelallyson/planning-center-base-ts').PcoDebugOptions;
40
42
  }
41
43
  export { PcoApiError } from '@rachelallyson/planning-center-base-ts';
42
44
  export interface PcoClientState {
@@ -66,11 +68,11 @@ export declare function patch<TRes extends ResourceObject<string, any, any>, TIn
66
68
  /**
67
69
  * Make a DELETE request to the PCO API
68
70
  */
69
- export declare function del(client: PcoClientState, endpoint: string, params?: Record<string, any>, context?: Partial<ErrorContext>): Promise<void>;
71
+ export declare function del(client: PcoClientState, endpoint: string, params?: Record<string, string | number | boolean | undefined>, context?: Partial<ErrorContext>): Promise<void>;
70
72
  /**
71
73
  * Get all pages of a paginated resource
72
74
  */
73
- export declare function getAllPages<T extends ResourceObject<string, any, any>>(client: PcoClientState, endpoint: string, params?: Record<string, any>, context?: Partial<ErrorContext>): Promise<T[]>;
75
+ export declare function getAllPages<T extends ResourceObject<string, any, any>>(client: PcoClientState, endpoint: string, params?: Record<string, string | number | boolean | undefined>, context?: Partial<ErrorContext>): Promise<T[]>;
74
76
  /**
75
77
  * Get rate limit information
76
78
  */
package/dist/core.js CHANGED
@@ -219,8 +219,9 @@ async function makeFetchRequest(client, method, endpoint, data, params, context)
219
219
  return await (0, auth_1.attemptTokenRefresh)(client, () => makeFetchRequest(client, method, endpoint, data, params, context));
220
220
  }
221
221
  catch (refreshError) {
222
- // If token refresh fails, fall through to normal error handling
223
- console.warn('Token refresh failed:', refreshError);
222
+ const logger = (0, planning_center_base_ts_1.createDebugLogger)(client.config);
223
+ if (logger.enabled)
224
+ logger.log('core token refresh failed', { error: String(refreshError) });
224
225
  }
225
226
  }
226
227
  let errorData;
@@ -26,7 +26,7 @@ export interface ErrorContext {
26
26
  category?: ErrorCategory;
27
27
  severity?: ErrorSeverity;
28
28
  retryCount?: number;
29
- metadata?: Record<string, any>;
29
+ metadata?: Record<string, unknown>;
30
30
  }
31
31
  export declare class PcoError extends PcoApiError {
32
32
  readonly category: ErrorCategory;
@@ -35,14 +35,14 @@ export declare class PcoError extends PcoApiError {
35
35
  readonly retryable: boolean;
36
36
  constructor(message: string, status: number, statusText: string, errors: JsonApiError[], rateLimitHeaders?: Record<string, string | undefined>, context?: Partial<ErrorContext>);
37
37
  private categorizeError;
38
- static fromFetchError(response: Response, data?: any, context?: Partial<ErrorContext>): PcoError;
38
+ static fromFetchError(response: Response, data?: unknown, context?: Partial<ErrorContext>): PcoError;
39
39
  static fromNetworkError(error: Error, context?: Partial<ErrorContext>): PcoError;
40
40
  static fromTimeoutError(timeoutMs: number, context?: Partial<ErrorContext>): PcoError;
41
41
  getRetryDelay(): number;
42
42
  shouldRetry(): boolean;
43
- getErrorSummary(): Record<string, any>;
43
+ getErrorSummary(): Record<string, unknown>;
44
44
  }
45
- export declare function shouldNotRetry(error: any): boolean;
45
+ export declare function shouldNotRetry(error: unknown): boolean;
46
46
  export declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: {
47
47
  maxRetries?: number;
48
48
  baseDelay?: number;
@@ -106,7 +106,9 @@ class PcoError extends planning_center_base_ts_1.PcoApiError {
106
106
  static fromFetchError(response, data, context = {}) {
107
107
  const status = response.status;
108
108
  const statusText = response.statusText;
109
- const errors = data?.errors || [];
109
+ const errors = (data && typeof data === 'object' && data !== null && 'errors' in data)
110
+ ? (data.errors || [])
111
+ : [];
110
112
  const rateLimitHeaders = {
111
113
  'Retry-After': response.headers.get('retry-after') || undefined,
112
114
  'X-PCO-API-Request-Rate-Count': response.headers.get('x-pco-api-request-rate-count') || undefined,
@@ -115,7 +117,16 @@ class PcoError extends planning_center_base_ts_1.PcoApiError {
115
117
  };
116
118
  const message = errors.length > 0
117
119
  ? errors
118
- .map((e) => e.detail || e.title || 'Unknown error')
120
+ .map((e) => {
121
+ if (e && typeof e === 'object' && 'detail' in e) {
122
+ return e.detail;
123
+ }
124
+ if (e && typeof e === 'object' && 'title' in e) {
125
+ return e.title;
126
+ }
127
+ return 'Unknown error';
128
+ })
129
+ .map((msg) => typeof msg === 'string' ? msg : 'Unknown error')
119
130
  .join('; ')
120
131
  : statusText;
121
132
  return new PcoError(message, status, statusText, errors, rateLimitHeaders, context);
@@ -41,15 +41,15 @@ export declare class CircuitBreaker {
41
41
  /**
42
42
  * Result of a bulk operation with individual item results
43
43
  */
44
- export interface BulkOperationResult<T> {
44
+ export interface BulkOperationResult<R> {
45
45
  successful: {
46
46
  index: number;
47
- data: T;
47
+ data: R;
48
48
  }[];
49
49
  failed: {
50
50
  index: number;
51
51
  error: Error;
52
- data?: any;
52
+ data?: unknown;
53
53
  }[];
54
54
  totalProcessed: number;
55
55
  successRate: number;
@@ -80,7 +80,7 @@ export declare const TIMEOUT_CONFIG: {
80
80
  /**
81
81
  * Classify errors for appropriate handling
82
82
  */
83
- export declare function classifyError(error: any): {
83
+ export declare function classifyError(error: unknown): {
84
84
  category: 'network' | 'authentication' | 'authorization' | 'validation' | 'rate_limit' | 'server' | 'unknown';
85
85
  severity: 'low' | 'medium' | 'high' | 'critical';
86
86
  retryable: boolean;
@@ -89,7 +89,7 @@ export declare function classifyError(error: any): {
89
89
  /**
90
90
  * Attempt to recover from common error scenarios
91
91
  */
92
- export declare function attemptRecovery<T>(operation: () => Promise<T>, error: any, context: {
92
+ export declare function attemptRecovery<T>(operation: () => Promise<T>, error: unknown, context: {
93
93
  client: PcoClientState;
94
94
  operation: string;
95
95
  maxRetries?: number;
@@ -105,7 +105,11 @@ export interface ErrorReport {
105
105
  message: string;
106
106
  stack?: string;
107
107
  status?: number;
108
- errors?: any[];
108
+ errors?: Array<{
109
+ detail?: string;
110
+ title?: string;
111
+ [key: string]: unknown;
112
+ }>;
109
113
  };
110
114
  context: {
111
115
  clientConfig: {
@@ -124,7 +128,7 @@ export interface ErrorReport {
124
128
  /**
125
129
  * Create detailed error report
126
130
  */
127
- export declare function createErrorReport(error: any, context: {
131
+ export declare function createErrorReport(error: unknown, context: {
128
132
  operation: string;
129
133
  client: PcoClientState;
130
134
  requestInfo?: {