Overview
The CongregationKit client provides a simplified interface to Salesforce’s APIs specifically designed for church management systems. It handles authentication and provides type-safe access to member data without conflicting with server framework authentication systems.
Designed to work seamlessly with Vapor 4 and Hummingbird server frameworks.
Initialization
init(httpClient:credentials:)
Creates a new CongregationKit client and authenticates with Salesforce.
The AsyncHTTPClient instance to use for making requests
credentials
SalesforceCredentials
required
The Salesforce credentials for authentication Show SalesforceCredentials properties
OAuth client ID from your Salesforce connected app
OAuth client secret from your Salesforce connected app
Salesforce username for authentication
Salesforce password for authentication
Throws if authentication fails during initialization
import AsyncHTTPClient
import CongregationKit
let httpClient = HTTPClient ( eventLoopGroupProvider : . shared )
let credentials = SalesforceCredentials (
clientId : "your_client_id" ,
clientSecret : "your_client_secret" ,
username : "your_username" ,
password : "your_password"
)
let congregation = try await CongregationKit (
httpClient : httpClient,
credentials : credentials
)
init(httpClient:authResponse:)
Creates a new CongregationKit client using pre-authenticated data.
The AsyncHTTPClient instance to use for making requests
authResponse
SalesforceAuthResponse
required
The pre-authenticated Salesforce response Show SalesforceAuthResponse properties
Access token for API authentication
Salesforce instance URL for API calls
Identity URL for user identification
Token type (typically “Bearer”)
When the signature was created (Unix epoch seconds)
Base64-encoded HMAC-SHA256 signature
// Use with pre-authenticated response
let authResponse = SalesforceAuthResponse (
accessToken : "00D..." ,
instanceUrl : "https://your-instance.salesforce.com" ,
id : "https://login.salesforce.com/id/00D.../005..." ,
tokenType : "Bearer" ,
issuedAt : "1234567890" ,
signature : "base64-encoded-signature"
)
let congregation = CongregationKit (
httpClient : httpClient,
authResponse : authResponse
)
Properties
members
Access member-related operations.
The members handler for member operations
// Fetch all members with pagination
let response = try await congregation. members . fetchAll (
pageNumber : 1 ,
pageSize : 50
)
for member in response.members {
print ( " \( member. memberName ?? "Unknown" ) - \( member. phone ?? "No phone" ) " )
}
// Fetch a specific member by ID
let memberId = try MemberID ( validating : "TKT123456" )
let member = try await congregation. members . fetch ( id : memberId)
print ( "Member: \( member. memberName ?? "Unknown" ) " )
// Fetch with expanded fields
let memberWithDetails = try await congregation. members . fetch (
id : memberId,
expanded : [. contactInformation , . employmentInformation ]
)
seekers
Access seeker-related operations.
The seekers handler for seeker operations
// Fetch seekers with filters
let response = try await congregation. seekers . fetchAll (
pageNumber : 1 ,
pageSize : 50 ,
seekerId : nil ,
name : nil ,
campus : . eastCampus ,
leadStatus : . followUp ,
email : nil ,
leadId : nil ,
contactNumber : nil
)
for seeker in response.seekers {
print ( " \( seeker. fullName ?? "Unknown" ) - \( seeker. lead ? . status ? . displayName ?? "No status" ) " )
}
// Fetch a specific seeker by identifier
let seeker = try await congregation. seekers . fetch ( identifier : "LEAD001" )
print ( "Seeker: \( seeker. fullName ?? "Unknown" ) " )
files
Access file download operations.
The files handler for file operations
// Download a file by record ID
let fileResponse = try await congregation. files . download ( recordId : "a0x2w000002jxqn" )
print ( "Downloaded: \( fileResponse. fullFilename ) " )
print ( "Size: \( fileResponse. fileSize ) bytes" )
print ( "Type: \( fileResponse. contentType ) " )
// Save to disk
try fileResponse. data . write ( to : URL ( fileURLWithPath : "/path/to/save/ \( fileResponse. fullFilename ) " ))
MembersHandler Methods
fetchAll(pageNumber:pageSize:)
Fetches all members with pagination support.
The page number to fetch (optional, default 1)
The page size to fetch (optional, default 50)
MemberResponse containing members and pagination info Show MemberResponse structure
Pagination metadata including page, total, per, nextPageToken, previousPageToken
Throws if operation fails
let response = try await congregation. members . fetchAll (
pageNumber : 1 ,
pageSize : 100
)
print ( "Total members: \( response. metadata ? . total ?? 0 ) " )
print ( "Current page: \( response. metadata ? . page ?? 0 ) " )
for member in response.members {
print (member. memberName ?? "Unknown" )
}
fetchAll(pageNumber:pageSize:nextPageToken:)
Fetches all members with cursor-based pagination support.
The page number to fetch (optional, default 1)
The page size to fetch (optional, default 50)
The next page token for cursor-based pagination (optional)
MemberResponse containing members and pagination info
// First page
var response = try await congregation. members . fetchAll (
pageNumber : 1 ,
pageSize : 50 ,
nextPageToken : nil
)
// Get next page using token
if let nextToken = response.metadata ? .nextPageToken {
response = try await congregation. members . fetchAll (
pageNumber : nil ,
pageSize : 50 ,
nextPageToken : nextToken
)
}
fetchAll(pageNumber:pageSize:expanded:)
Fetches all members with optional expanded fields.
The page number to fetch (optional, default 1)
The page size to fetch (optional, default 50)
Array specifying which related information to expand Options: .contactInformation, .employmentInformation, .martialInformation, .discipleshipInformation
MemberResponse containing members with expanded fields
let response = try await congregation. members . fetchAll (
pageNumber : 1 ,
pageSize : 50 ,
expanded : [. contactInformation , . employmentInformation ]
)
for member in response.members {
if let contact = member.contactInformation {
print ( "Email: \( contact. email ?? "None" ) " )
print ( "Address: \( contact. address ?? "None" ) " )
}
if let employment = member.employmentInformation {
print ( "Status: \( employment. employmentStatus ? . rawValue ?? "Unknown" ) " )
}
}
fetch(id:)
Fetches a specific member by ID (type-safe, normalized).
The member ID to fetch (must start with TKT, case-insensitive normalization handled automatically)
Throws if operation fails or memberId is invalid
let memberId = try MemberID ( validating : "TKT123456" )
let member = try await congregation. members . fetch ( id : memberId)
print ( "Name: \( member. memberName ?? "Unknown" ) " )
print ( "Phone: \( member. phone ?? "No phone" ) " )
print ( "Campus: \( member. campus ? . rawValue ?? "Unknown" ) " )
fetch(id:expanded:)
Fetches a specific member by ID with optional expanded information.
Array specifying which related information to expand
The member if found with expanded fields
let memberId = try MemberID ( validating : "TKT123456" )
let member = try await congregation. members . fetch (
id : memberId,
expanded : [. contactInformation , . discipleshipInformation ]
)
if let discipleship = member.discipleshipInformation {
print ( "Baptized: \( discipleship. waterBaptism ? . received ?? false ) " )
print ( "Serving: \( discipleship. serving ? . involved ? . displayName ?? "Not serving" ) " )
}
SeekersHandler Methods
fetchAll(pageNumber:pageSize:seekerId:name:campus:leadStatus:email:leadId:contactNumber:)
Fetches all seekers with pagination and optional filters.
The page number to fetch (optional, default 1)
The page size to fetch (optional, default 50)
Filter by seeker ID (optional)
Filter by name (optional)
Filter by campus (optional, type-safe)
Filter by lead status (optional, type-safe) Options: .attempted, .followUp, .secondFollowUp, .thirdFollowUp, .fourthFollowUp, .lost, .converted, .doNotContact
Filter by email (optional)
Filter by lead ID (optional)
Filter by contact number (optional)
SeekerResponse containing seekers and pagination info
let response = try await congregation. seekers . fetchAll (
pageNumber : 1 ,
pageSize : 50 ,
seekerId : nil ,
name : "John" ,
campus : . eastCampus ,
leadStatus : . followUp ,
email : nil ,
leadId : nil ,
contactNumber : nil
)
for seeker in response.seekers {
print ( " \( seeker. fullName ?? "Unknown" ) - \( seeker. lead ? . status ? . displayName ?? "No status" ) " )
}
fetch(identifier:)
Fetches a specific seeker by identifier (leadId or phone, case-insensitive).
The identifier to fetch (leadId or phone)
Throws if operation fails
let seeker = try await congregation. seekers . fetch ( identifier : "LEAD001" )
print ( "Name: \( seeker. fullName ?? "Unknown" ) " )
print ( "Email: \( seeker. email ?? "No email" ) " )
print ( "Status: \( seeker. lead ? . status ? . displayName ?? "Unknown" ) " )
FilesHandler Methods
download(recordId:)
Downloads a file from Salesforce using a record ID.
The record ID to download the file from (15-18 character alphanumeric Salesforce ID)
File download response containing file data and metadata Show FileDownloadResponse structure
The filename of the downloaded file
The MIME content type of the file
The size of the file in bytes
The record ID that was used to download the file
The ContentDocument ID from Salesforce
Computed property: filename with extension
let fileResponse = try await congregation. files . download ( recordId : "a0x2w000002jxqn" )
print ( "Downloaded: \( fileResponse. fullFilename ) " )
print ( "Size: \( fileResponse. fileSize ) bytes" )
print ( "Type: \( fileResponse. contentType ) " )
// Save to disk
let fileURL = URL ( fileURLWithPath : "/path/to/save/ \( fileResponse. fullFilename ) " )
try fileResponse. data . write ( to : fileURL)
Complete Example
import AsyncHTTPClient
import CongregationKit
func main () async throws {
let httpClient = HTTPClient ( eventLoopGroupProvider : . shared )
defer { try ? httpClient. syncShutdown () }
// Initialize with credentials
let credentials = SalesforceCredentials (
clientId : ProcessInfo. processInfo . environment [ "SALESFORCE_CLIENT_ID" ] ! ,
clientSecret : ProcessInfo. processInfo . environment [ "SALESFORCE_CLIENT_SECRET" ] ! ,
username : ProcessInfo. processInfo . environment [ "SALESFORCE_USERNAME" ] ! ,
password : ProcessInfo. processInfo . environment [ "SALESFORCE_PASSWORD" ] !
)
let congregation = try await CongregationKit (
httpClient : httpClient,
credentials : credentials
)
// Fetch members
let memberResponse = try await congregation. members . fetchAll (
pageNumber : 1 ,
pageSize : 50 ,
expanded : [. contactInformation , . employmentInformation ]
)
print ( "Total members: \( memberResponse. metadata ? . total ?? 0 ) " )
for member in memberResponse.members {
print ( " \( member. memberName ?? "Unknown" ) " )
if let contact = member.contactInformation {
print ( " Email: \( contact. email ?? "None" ) " )
}
}
// Fetch seekers
let seekerResponse = try await congregation. seekers . fetchAll (
pageNumber : 1 ,
pageSize : 50 ,
seekerId : nil ,
name : nil ,
campus : nil ,
leadStatus : . followUp ,
email : nil ,
leadId : nil ,
contactNumber : nil
)
for seeker in seekerResponse.seekers {
print ( "Seeker: \( seeker. fullName ?? "Unknown" ) " )
print ( " Status: \( seeker. lead ? . status ? . displayName ?? "Unknown" ) " )
}
// Download a file
let file = try await congregation. files . download ( recordId : "a0x2w000002jxqn" )
try file. data . write ( to : URL ( fileURLWithPath : "/tmp/ \( file. fullFilename ) " ))
print ( "Downloaded \( file. fullFilename ) " )
}
try await main ()
Error Handling
do {
let congregation = try await CongregationKit (
httpClient : httpClient,
credentials : credentials
)
let member = try await congregation. members . fetch (
id : try MemberID ( validating : "TKT123456" )
)
print ( "Found member: \( member. memberName ?? "Unknown" ) " )
} catch let error as SalesforceAuthError {
print ( "Authentication failed: \( error. localizedDescription ) " )
} catch let error as MemberError {
switch error {
case . memberNotFound :
print ( "Member not found" )
case . invalidMemberID :
print ( "Invalid member ID format" )
case . fetchFailed ( let underlying) :
print ( "Fetch failed: \( underlying ) " )
default :
print ( "Member error: \( error. localizedDescription ) " )
}
} catch {
print ( "Unexpected error: \( error ) " )
}
Type Definitions
MemberID
Type-safe identifier for church members that validates and normalizes member IDs.
// Using the failable initializer
if let memberId = MemberID ( rawValue : "tkt123456" ) {
print (memberId. rawValue ) // "TKT123456"
}
// Using the throwing initializer
let memberId = try MemberID ( validating : "TKT789012" )
Campus
Enum representing church campuses.
LeadStatus
Enum representing seeker lead status:
.attempted
.followUp
.secondFollowUp
.thirdFollowUp
.fourthFollowUp
.lost
.converted
.doNotContact
MemberExpand
Enum specifying which related information to expand:
.contactInformation
.employmentInformation
.martialInformation
.discipleshipInformation