Core Concepts
This document explains the core mental models, invariants, and architectural patterns used in this library.
Architecture Overview
Monorepo Structure
The monorepo uses npm workspaces with three packages:
- Base Package (
@rachelallyson/planning-center-base-ts) - Shared infrastructure - People Package (
@rachelallyson/planning-center-people-ts) - People API client (depends on base) - Check-Ins Package (
@rachelallyson/planning-center-check-ins-ts) - Check-Ins API client (depends on base)
All packages follow TypeScript strict mode and share common dependencies at the root level.
Base Package
The base package provides:
- HTTP Client (
PcoHttpClient) - Handles all API requests with auth, rate limiting, retries - Rate Limiter (
PcoRateLimiter) - Enforces 100 requests per 20 seconds - Pagination Helper (
PaginationHelper) - Utilities for paginated responses - Base Module (
BaseModule) - Abstract base class for API modules - Error Handling (
PcoApiError,PcoError) - Typed error classes with categories - Batch Executor (
BatchExecutor) - Execute multiple operations with dependency resolution - Event System (
PcoEventEmitter) - Monitor requests, errors, rate limits - JSON:API Types - Complete TypeScript types for JSON:API 1.0 specification
People Package
The People package extends the base with:
- PcoClient - Main client class with 11 specialized modules
- People Module - Person CRUD, matching, relationships
- Fields Module - Custom field definitions and data
- Workflows Module - Workflow cards and state management
- Contacts Module - Emails, phones, addresses, social profiles
- Households Module - Household management
- Notes Module - Person notes and categories
- Lists Module - People lists and categories
- Campus Module - Campus management
- ServiceTime Module - Service time tracking
- Forms Module - Forms and submissions
- Reports Module - Report management
Check-Ins Package
The Check-Ins package extends the base with:
- PcoCheckInsClient - Main client class with 16 specialized modules
- Events Module - Event management with consolidated functionality:
- Event creation, listing, updates, and deletion
- Event periods and times (consolidated from separate modules)
- Person events and check-in times
- Locations Module - Location management
- Check-Ins Module - Check-in operations and tracking
- Stations Module - Station management
- Labels Module - Label operations for events and locations
- Event Times Module - Event time management
- Check-In Groups Module - Group check-in operations
- Passes Module - Pass management
- Headcounts Module - Attendance headcount tracking
- Attendance Types Module - Attendance type management
- Organizations Module - Organization management
- Themes Module - Theme management
- Pre-Checks Module - Pre-check operations
- Integration Links Module - Integration link management
- Roster List Persons Module - Roster list person operations
Mental Models
1. JSON:API Compliance
All API responses follow the JSON:API 1.0 specification :
- Resource Objects:
{ type, id, attributes, relationships } - Resource Collections:
{ data: ResourceObject[], links, meta } - Included Resources: Related resources in
includedarray - Links: Pagination and relationship links
// Single resource response
{
data: {
type: 'Person',
id: '123',
attributes: { first_name: 'John', last_name: 'Doe' },
relationships: { emails: { data: [{ type: 'Email', id: '456' }] } }
},
included: [
{ type: 'Email', id: '456', attributes: { address: 'john@example.com' } }
]
}
// Paginated list response
{
data: [{ type: 'Person', id: '123', attributes: {...} }],
links: {
self: '/people?page=1',
next: '/people?page=2',
last: '/people?page=10'
},
meta: { total_count: 245 }
}2. Authentication Lifecycle
Three authentication methods with different lifecycles:
Personal Access Token (PAT)
- Stable: Never expires (unless revoked)
- Use Case: Single-user applications, scripts
- No Refresh: Direct authentication
- Setup: Requires both Client ID and Client Secret
Configuration Options:
Option 1: Environment Variables (Recommended for production)
// Set environment variables
process.env.PCO_PERSONAL_ACCESS_TOKEN = 'your_client_id';
process.env.PCO_PERSONAL_ACCESS_SECRET = 'your_client_secret';
// Client configuration
const client = new PcoClient({
auth: {
type: 'personal_access_token',
personalAccessToken: process.env.PCO_PERSONAL_ACCESS_TOKEN!
}
});Option 2: Direct Configuration (Convenient for development)
const client = new PcoClient({
auth: {
type: 'personal_access_token',
personalAccessToken: 'your_client_id',
personalAccessTokenSecret: 'your_client_secret'
}
});OAuth 2.0
- Expires: Access tokens expire (typically 1 hour)
- Refresh Required: Must handle refresh via callbacks
- Invariant:
onRefreshandonRefreshFailureare required (v2.0+) - Use Case: Multi-user applications, web apps
// OAuth requires refresh handling
const client = new PcoClient({
auth: {
type: 'oauth',
accessToken: 'token',
refreshToken: 'refresh-token',
onRefresh: async (tokens) => {
// REQUIRED: Save new tokens
await saveTokens(tokens);
},
onRefreshFailure: async (error) => {
// REQUIRED: Handle refresh failure
await handleAuthFailure(error);
}
}
});Basic Auth
- App Credentials: Uses
appIdandappSecret - Server-to-Server: For server-side integrations
3. Rate Limiting
Hard Limit: 100 requests per 20 seconds (enforced by Planning Center API)
The library automatically:
- Tracks: Request count and window timing
- Waits: Blocks requests when limit reached
- Respects Headers: Uses
X-PCO-API-Request-Rate-LimitandX-PCO-API-Request-Rate-Period - Emits Events: Fires
rate:limitevents when approaching limit
// Library handles automatically
await client.people.getAll(); // Automatically waits if at limit
// Monitor rate limit status
const info = client.getRateLimitInfo();
console.log(`${info.remaining} requests remaining`);4. Pagination Model
Two pagination patterns:
Automatic Pagination
// Fetches all pages automatically
const allPeople = await client.people.getAllPages({ perPage: 25 });
// Returns: { data: PersonResource[], meta: {...} }Manual Pagination
// Fetch one page at a time
const page = await client.people.getAll({ perPage: 25 });
// Check for next page
if (page.links?.next) {
const nextPage = await client.people.getAll({ perPage: 25, page: 2 });
}Invariants:
links.nextis present if more pages existlinks.selfis always presentmeta.total_countmay be present (optional)
5. Error Handling Model
All errors extend PcoError or PcoApiError with:
- Category:
ErrorCategoryenum (AUTHENTICATION, RATE_LIMIT, VALIDATION, etc.) - Severity:
ErrorSeverityenum (LOW, MEDIUM, HIGH, CRITICAL) - Retryable: Boolean indicating if operation should be retried
- Context: Request context (endpoint, method, metadata)
try {
await client.people.create({ first_name: 'John' });
} catch (error) {
if (error instanceof PcoError) {
console.log(error.category); // ErrorCategory.VALIDATION
console.log(error.severity); // ErrorSeverity.LOW
console.log(error.retryable); // false
}
}6. Batch Operations Model
Batch operations execute multiple operations with dependency resolution:
- Ordered Execution: Operations execute in order
- Reference Resolution: Use
$N.idto reference previous operation results - Partial Success: Some operations can succeed while others fail
- Dependency Tracking: Automatic dependency resolution
const results = await client.batch.execute([
{ type: 'people.create', data: { firstName: 'John' } },
{ type: 'people.addEmail', personId: '$0.id', data: { address: 'john@example.com' } }
]);
// $0.id references the person created in step 0Patterns
Module Pattern
All modules follow this consistent pattern:
// List operations
getAll(params?: QueryParams): Promise<Paginated<Resource>>
getAllPages(params?: QueryParams): Promise<Paginated<Resource>>
// Single resource operations
getById(id: string, include?: string[]): Promise<ResourceResponse<Resource>>
// Mutations
create(data: CreateData): Promise<ResourceResponse<Resource>>
update(id: string, data: UpdateData): Promise<ResourceResponse<Resource>>
delete(id: string): Promise<void>Query Parameters Pattern
Query parameters use consistent naming:
perPage- Items per page (default: 25, max: 100)page- Page number (1-indexed)include- Related resources to include (array of strings)where- Filter conditions (object with field conditions)order- Sort order (string like'first_name'or'-created_at')
// Example query
const people = await client.people.getAll({
perPage: 50,
include: ['emails', 'phone_numbers'],
where: { status: 'active' },
order: 'first_name'
});Data Invariants
Timestamps
- Format: UTC ISO 8601 strings (
YYYY-MM-DDTHH:mm:ss.sssZ) - Examples:
'2025-01-11T12:00:00.000Z' - Timezone: Always UTC (no timezone conversion)
IDs
- Format: String (no numeric IDs)
- Examples:
'person-123','email-456' - Uniqueness: Guaranteed per resource type
Relationships
- Optional: All relationships are optional (can be
null) - Structure: Follow JSON:API relationship structure
- Included: Use
includeparameter to fetch related resources
Lifecycle
Client Lifecycle
- Instantiation:
new PcoClient(config)creates client - Module Initialization: All modules initialized automatically
- HTTP Client: Internal HTTP client handles all requests
- Rate Limiting: Rate limiter initialized (100 req/20s)
- Event System: Event emitter initialized for monitoring
Request Lifecycle
- Rate Limit Check: Wait if at limit
- Request Start Event: Emit
request:startevent - HTTP Request: Make authenticated request
- Response Handling: Parse JSON:API response
- Error Handling: Wrap errors in
PcoApiErrororPcoError - Request Complete Event: Emit
request:completeorrequest:errorevent - Rate Limit Update: Update rate limit tracking
Token Refresh Lifecycle (OAuth)
- 401 Response: Detect expired token
- Refresh Attempt: Call refresh endpoint with refresh token
- Success: Call
onRefreshcallback with new tokens - Retry Request: Retry original request with new token
- Failure: Call
onRefreshFailurecallback
Performance Considerations
Caching
Field definitions can be cached:
- TTL: Configurable (default: 5 minutes)
- Max Size: Configurable (default: 1000 entries)
- Scope: Field definitions only (not person data)
Batch Operations
Use batch operations for multiple mutations:
- Efficiency: Fewer HTTP requests
- Dependency Resolution: Automatic reference resolution
- Error Isolation: Individual operation errors don’t fail entire batch
Pagination
- Automatic: Use
getAllPages()for convenience - Manual: Use
getAll()for control over pagination - Large Datasets: Use streaming for very large datasets
Security Considerations
Token Storage
- Never Commit: Never commit tokens to version control
- Environment Variables: Use environment variables or secure storage
- Refresh Handling: Always persist refreshed tokens
Rate Limiting
- Respect Limits: Library handles automatically, but don’t bypass
- Monitor: Use event system to monitor rate limit status
- Backoff: Library uses exponential backoff for retries
Error Exposure
- Sanitize: Don’t expose internal error details to end users
- Logging: Log errors with context for debugging
- Monitoring: Use event system for error monitoring
Next: See Quick Start Guide for practical examples.