Our planned Sync Engine will be built around a powerful generic workflow factory. This means you will not need to write a new, boilerplate-heavy workflow for every entity you want to sync. Instead, you will simply define the API endpoint, implement a data transformer, and the engine will handle the rest.
This guide will walk through the planned process for adding a new sync process for Planning Center "Donations".
Before you begin, you will need to ensure you have:
- Defined the
PCODonation(API-specific) andBaseDonation(canonical) schemas in the API Adapter library. - Read
04-defining-endpoints.mdto understand how to configure an endpoint with the currentpcoApiAdapter()function.
This is the step you can do today. You must describe the API endpoint for fetching the new entity. This is done in the API Adapter library (@openfaith/pco).
In a file like adapters/pco/modules/giving/pcoDonationEndpoints.ts, add your new definition:
import { pcoApiAdapter } from '@openfaith/pco/api/pcoApiAdapter'
import { PcoDonation } from '@openfaith/pco/modules/giving/pcoDonationSchema' // You would create this
export const getAllDonationsDefinition = pcoApiAdapter({
apiSchema: PcoDonation,
entity: 'Donation',
method: 'GET',
module: 'giving',
name: 'list',
path: '/giving/v2/donations',
isCollection: true,
// Define capabilities based on PCO's API documentation
includes: ['person', 'payment_source'],
orderableBy: ['created_at'],
queryableBy: {
fields: ['created_at', 'updated_at'],
special: ['search']
},
} as const)Make sure this new definition is exported from your endpoints index file and added to your HttpApiGroup.
🚧 This step is planned but not yet implemented. The generic workflow will need to know how to convert the BaseDonation model into the model required by our application's database. This logic will be added to our planned TransformerService.
In the future Sync Engine project, you will navigate to something like src/services/transformer.ts and add a case for the new entity.
// This is planned functionality - not yet implemented
// src/services/transformer.ts
// A map of entity names to their transformation functions
const transformerMap = {
'Person': transformPerson,
'Group': transformGroup,
'Donation': transformDonation, // Add the new one here
};
function transformDonation(data: ReadonlyArray<BaseDonation>): ReadonlyArray<DbDonation> {
// Your business logic to map fields will go here.
return data.map(donation => ({
id: donation.id,
amount_in_cents: donation.amountCents,
donor_id: donation.relationships.person.id,
created_on: new Date(donation.createdAt),
}));
}
// The live implementation of our service will use this map.
export const TransformerServiceLive = Layer.succeed(
TransformerService,
{
transform: (entityName, data) => Effect.succeed(transformerMap[entityName](data))
}
);🚧 This automation is planned but not yet implemented.
You will not need to write a new workflow or manually register it.
The planned Sync Engine's main application entry point will be configured to:
- Import all endpoint definitions from the
@openfaith/pcopackage. - Iterate through them and use the
createSyncWorkflowfactory to generate a durable workflow for each one. - Automatically merge all these generated workflow layers together.
By simply defining the endpoint and providing a transformer, the new "Donations" sync will be fully integrated, durable, scalable, and ready to be scheduled by the master sync orchestrator.
- ✅ Define Endpoint: Create the endpoint definition using
pcoApiAdapter() - ✅ Add to HttpApiGroup: Include the endpoint in your API definition
- ✅ Test API Integration: Use the HttpApiClient to manually test the endpoint
- ✅ Create Schemas: Define the PCO and canonical data models
- 🚧 Generic Workflow Factory: Automatic workflow generation from endpoint definitions
- 🚧 TransformerService: Data transformation pipeline from API to database models
- 🚧 Sync Orchestration: Automatic scheduling and execution of sync jobs
- 🚧 Dependency Management: Handling entity relationships and sync order
- 🚧 Progress Tracking: Monitoring and resuming sync operations
- 🚧 Error Handling: Retry logic and failure recovery
Until the Sync Engine is implemented, you can manually create basic sync functionality:
// Current workaround - manual sync implementation
import { Effect } from 'effect'
import { PcoHttpClient } from '@openfaith/pco/api/pcoApi'
import { db } from '@openfaith/db' // Your database layer
const syncDonationsManually = Effect.gen(function* () {
const client = yield* PcoHttpClient
// Fetch donations from PCO
const donationsResponse = yield* client.giving.getAll({})
// Transform the data (manual transformation)
const transformedDonations = donationsResponse.data.map(donation => ({
id: donation.id,
amount_in_cents: donation.attributes.amount_cents,
donor_id: donation.relationships.person?.data?.id,
created_on: new Date(donation.attributes.created_at),
// ... other field mappings
}))
// Save to database
yield* db.insertDonations(transformedDonations)
console.log(`Synced ${transformedDonations.length} donations`)
})
// Run the manual sync
Effect.runPromise(syncDonationsManually.pipe(
Effect.provide(/* your services layer */)
))The plan is to evolve from this manual approach to the automatic system:
- Implement
@effect/workflowand@effect/cluster - Create basic sync workflow templates
- Add manual workflow triggering
- Build the workflow factory that reads endpoint definitions
- Implement the TransformerService pattern
- Create automatic workflow registration
- Add dependency-aware scheduling
- Implement multiple sync strategies (full, delta, reconciliation)
- Build monitoring and management tools
The current endpoint definitions you create today will be the foundation that powers this future automation, ensuring your work won't be wasted when the full sync engine is implemented.