
Use external records where your application needs them
Data can remain in its original system and be read when needed, or become a synchronized part of your application. Wildo supports both choices through resource declarations.
Read-through suits remote reference data. A local copy lets your application add its own fields, attachments and relationships, with freshness determined by the synchronization schedule.
Example — Keep customer accounts and local notes together
An application copies company accounts from Odoo, then lets its users add internal notes. A later synchronization updates the mapped account fields while the application retains the fields it owns.
For engineers
A virtual resource uses persistenceAdapter: HTTP_API with httpApiBinding. Its repository translates supported reads to the external system; ordinary writes are unavailable and it has no local row for extra fields or files. A pipeline declaration belongs to an ordinary local resource and populates that resource through the data-seeding engines.
Both use HttpApiReadClient for provider access, dialect framing and remote calls. A binding declares the provider reference, remote entity, key mapping, tenancy and erasure stance. Those are required semantic decisions, not optional labels.
This full pipeline block is selected from Wonder Todos’ external-customers.resources-config.ts; the surrounding resource factory and operation declarations are omitted:
externalDataPipeline: {
binding: {
dialect: HttpApiTransportDialect.ODOO_JSONRPC,
providerRef: 'odoo',
entityRef: 'res.partner',
keyFields: [{ localField: 'id', remoteField: 'id', codec: HttpApiKeyComponentCodec.INTEGER }],
tenancy: {
stance: HttpApiTenancyStance.SINGLE_TENANT_BINDING,
justification: 'Wonder Todos serves one company, whose Odoo holds one client list; there is no per-tenant partition to push down.',
},
erasure: { stance: HttpApiErasureStance.NO_SUBJECT_DATA },
},
remoteKeyLocalField: 'odooPartnerRef',
mapping: [
{ kind: ExternalDataMappingEntryKind.REMOTE_FIELD, remoteField: 'name', localField: 'name' },
{ kind: ExternalDataMappingEntryKind.REMOTE_FIELD, remoteField: 'email', localField: 'email' },
],
// Only companies: Odoo's `res.partner` holds individuals too, and syncing them would make the
// NO_SUBJECT_DATA erasure stance above false.
sourceFilter: { is_company: true },
population: ExternalDataDestinationPopulation.CLOSED,
orphanPolicy: ExternalDataOrphanPolicy.REPORT,
// Hourly. Client lists move at human speed, and every run is a round trip to someone else's
// production system.
scheduleCron: '0 * * * *',
},
The closed population means the source supplies the records: this resource declares no caller create operation. Its local internalNote is editable, while imported fields exclude create/update input in the schema. The mapping owns only its declared fields. Register the schema, resource factory and relationships through the shared module, and configure the named provider’s backend access.
Treat synchronization as a population contract
remoteKeyLocalField holds a stable identity derived from the external binding. An extraction that reaches the source’s end selects sync reconciliation; a bounded pass selects upgrade reconciliation and persists its continuation. Reaching the end after resuming does not reconstruct the keys imported by earlier runs.
| Requirement | Suitable lane |
|---|---|
| Read the external response on demand | Virtual read-through |
| Add local notes or files | Local pipeline destination |
| Avoid remote latency on ordinary reads | Local pipeline destination |
| Keep no duplicated row | Virtual read-through |
Keep REPORT for imports spanning several runs. The final resumed batch can classify earlier imported rows as absent because reconciliation receives that batch’s keys, not the accumulated population; reporting preserves those rows while you inspect the result. Automatic orphan deletion requires a complete population basis. A completed extraction is not a claim that every row passed transformation; inspect rejections and reconciliation results. Set the tenancy stance from the actual source partition, and do not use NO_SUBJECT_DATA for a source containing people’s information.