Skip to Content
Core Concepts

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:

  1. Base Package (@rachelallyson/planning-center-base-ts) - Shared infrastructure
  2. People Package (@rachelallyson/planning-center-people-ts) - People API client (depends on base)
  3. 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 included array
  • 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: onRefresh and onRefreshFailure are 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 appId and appSecret
  • 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-Limit and X-PCO-API-Request-Rate-Period
  • Emits Events: Fires rate:limit events 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.next is present if more pages exist
  • links.self is always present
  • meta.total_count may be present (optional)

5. Error Handling Model

All errors extend PcoError or PcoApiError with:

  • Category: ErrorCategory enum (AUTHENTICATION, RATE_LIMIT, VALIDATION, etc.)
  • Severity: ErrorSeverity enum (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.id to 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 0

Patterns

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 include parameter to fetch related resources

Lifecycle

Client Lifecycle

  1. Instantiation: new PcoClient(config) creates client
  2. Module Initialization: All modules initialized automatically
  3. HTTP Client: Internal HTTP client handles all requests
  4. Rate Limiting: Rate limiter initialized (100 req/20s)
  5. Event System: Event emitter initialized for monitoring

Request Lifecycle

  1. Rate Limit Check: Wait if at limit
  2. Request Start Event: Emit request:start event
  3. HTTP Request: Make authenticated request
  4. Response Handling: Parse JSON:API response
  5. Error Handling: Wrap errors in PcoApiError or PcoError
  6. Request Complete Event: Emit request:complete or request:error event
  7. Rate Limit Update: Update rate limit tracking

Token Refresh Lifecycle (OAuth)

  1. 401 Response: Detect expired token
  2. Refresh Attempt: Call refresh endpoint with refresh token
  3. Success: Call onRefresh callback with new tokens
  4. Retry Request: Retry original request with new token
  5. Failure: Call onRefreshFailure callback

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.

Last updated on