Skip to main content
Wildo.ai Coming soon

Systems of record

Work with Salesforce records in your application

Connect a company's Salesforce org so an application can show its records live, keep a copy up to date, and create, change or delete records in Salesforce. Changes can flow one way at a time; editing the same records on both sides is not supported.

Salesforce accounts are read live; a new-account form sends a create request to Salesforce and receives its Id.

Work with Salesforce records in your application

A company that keeps its customers and opportunities in Salesforce can build internal tools on those records without moving them. The application declares the Salesforce org once and signs in to it as an integration, never as one of its own users.

Read in place: a screen asks Salesforce when it opens, through a SOQL query the engine builds. Copy kept current: a scheduled pipeline copies chosen fields into the application, walking the object in Id order so a long copy neither skips nor repeats a record when Salesforce changes underneath it.

Write back: a resource can create, change and delete records in Salesforce. Each write is sent exactly once and recorded; if the answer is lost, the application reports that the outcome is unknown instead of trying again and creating a duplicate. A pipeline can also send records created or changed in the application to Salesforce.

The same records cannot be edited on both sides with Wildo deciding who wins. That needs a version each record carries, and the Salesforce facet declares none, so a two-way pipeline is refused when the application starts.

Example — Open accounts from a delivery tool

A delivery team’s tool reads Salesforce accounts live, so the list always matches the CRM. When a project starts for a new client, the tool creates the account in Salesforce from its own form; the new account’s Id comes back in Salesforce’s answer and the record is read back from Salesforce by it.

For engineers

The facet offers two credential kinds, so the instance names one. A design example, in wildo.saas.config.ts:

systemsOfRecord: {
  crm: {
    vendor: 'salesforce',
    dialect: HttpApiTransportDialect.REST_JSON,
    credential: { kind: SystemOfRecordCredentialKind.OAUTH2_JWT_BEARER },
  },
},

OAUTH2_CLIENT_CREDENTIALS_SECRET sends a client id and secret; OAUTH2_JWT_BEARER signs an assertion with a private key for a named integration user. Omitting credential is refused at startup, listing both. The org’s instance URL (https://<myDomain>.my.salesforce.com) is the baseUrl address slot. The engine deliberately does not take it from the token response, so sandbox and production differ by address alone. Under the JWT bearer grant the environment also supplies clientId, tokenUrl and subject (the integration user) as addresses and privateKeyPem as the secret. A token is obtained once per lifetime and shared by concurrent reads; when Salesforce’s answer omits a lifetime, the facet’s two-hour fallback applies.

Copy an object through a keyset

The binding below is taken from the engine’s keyset integration test, which runs it against the SOQL double on MongoDB and PostgreSQL. Its destination schema and registration are omitted.

binding: {
  dialect: HttpApiTransportDialect.REST_JSON, system: 'salesforce', entityRef: 'Account',
  keyFields: [{ localField: 'id', remoteField: 'Id', codec: HttpApiKeyComponentCodec.STRING }],
  subjectRights: { posture: SystemOfRecordSubjectRightsPosture.NO_SUBJECT_DATA },
  operations: {
    read: { method: 'GET', pathTemplate: '/services/data/v59.0/sobjects/Account/{key}' },
    list: {
      method: 'GET', pathTemplate: '/services/data/v59.0/query', resultPath: 'records', cursorPath: 'nextRecordsUrl',
      statement: { language: HttpApiQueryStatementLanguage.SOQL, parameterName: 'q', keyset: { keyRemoteField: 'Id' } },
    },
  },
},

You write no SOQL. Each page is built as SELECT Id, <mapped fields> FROM Account WHERE … AND Id > '<last Id>' ORDER BY Id ASC LIMIT <n>. Salesforce may answer one page in several batches; the engine follows nextRecordsUrl to the end of the page, then stores the last Id, never the query locator, whose lifetime is not documented. Without a keyset, the object can be read live but cannot feed a pipeline: paging by offset is refused because a moving result skips rows. Ids compare in code-unit order, Salesforce’s own Id order, and a page served out of that order is refused rather than trusted.

Open writes on a read-through resource

A REST binding declares the request each opened verb sends. The engine’s write test declares Salesforce’s sObject shape like this (its remoteCallSemantics constant is omitted):

operations: {
  read: { method: 'GET', pathTemplate: `${SOBJECT}/{key}` },
  create: { method: HttpApiRestWriteMethod.POST, pathTemplate: SOBJECT, createdKey: { source: HttpApiRestCreatedKeySource.RESPONSE_BODY, path: 'id' } },
  update: { method: HttpApiRestWriteMethod.PATCH, pathTemplate: `${SOBJECT}/{key}` },
  delete: { method: HttpApiRestWriteMethod.DELETE, pathTemplate: `${SOBJECT}/{key}` },
},
writableVerbs: WRITE_VERBS,
writeConcurrency: { mode: HttpApiWriteConcurrencyMode.LAST_WRITER_WINS },

createdKey says where Salesforce answers the new record’s Id: in the body’s id. An update sends only the changed fields and is followed by a read-back. PUT cannot be declared, because it replaces a record and would erase the fields a partial update leaves out. Every write requires an Idempotency-Key from the caller; a retry under the same key replays the first outcome instead of sending again. LAST_WRITER_WINS is the only stance available: VERSION_CHECKED is refused at startup because the facet declares no row version.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.