Skip to content

Client SDKs

Synchro’s client story is native-first.

Swift and Kotlin are real sync engines, not thin wrappers over a generic client. React Native exposes the same product surface through a bridge over those native engines.

Synchro currently ships three client surfaces:

  • Swift for Apple platforms
  • Kotlin for Android
  • React Native as a bridge over the native SDKs

The native SDKs own:

  • local SQLite access
  • local mutation capture
  • durable queueing
  • background and foreground sync scheduling
  • applying server state without echo loops

They do not define server sync semantics. They execute the server contract locally and productize the offline runtime for the app.

inspectRetainedMutationRecords() returns every retained queue record, which includes local terminal states such as exceeds_push_limit. Each value is a RetainedMutationInspection, which is one of these records:

  • A current record holds a PendingMutationInspection with its table ID, primary-key binding, authored schema, and authored field values.
  • A legacy record holds a LegacyMutationInspection. It comes from a queue that existed before the mutation ledger. It has only the stored fields. The binding fields and authored values are absent.

The SDK does not fill the absent legacy fields with placeholder values. A record with only some bindings fails inspection. The Kotlin ledger requires every binding, so Kotlin returns only current records. To select the unresolved queue, filter the records by status.

for record in try client.inspectRetainedMutationRecords() {
switch record {
case .current(let mutation): print(mutation.tableID, mutation.authoredFields)
case .legacy(let mutation): print(mutation.tableName, mutation.recordID)
}
}
client.inspectRetainedMutationRecords().forEach { record ->
when (record) {
is RetainedMutationInspection.Current -> println(record.mutation.tableID)
is RetainedMutationInspection.Legacy -> println(record.mutation.recordID)
}
}

inspectRejectedMutationRecords() returns every retained terminal outcome as a RetainedRejectionInspection:

  • A current record holds a RejectedMutationInspection with the exact mutation JSON and rejection JSON.
  • A legacy record holds a LegacyRejectionInspection. It comes from a rejection table that existed before the mutation ledger. It has only the stored fields. The mutation JSON and rejection JSON are absent.

A rejected record with only one of the two JSON values fails inspection.

React Native returns the same records. A record has representation: "current" with the current fields, or representation: "legacy" with the stored fields only.

These methods keep their published signatures and behavior. Existing code compiles without a change. Use the record methods above for new code.

Deprecated method Result Legacy record Replacement
inspectPendingMutations() current records in unresolved states fails with an invalid-response error inspectRetainedMutationRecords() filtered by status
inspectRetainedMutations() current records, including local terminal states fails with an invalid-response error inspectRetainedMutationRecords()
inspectRejectedMutations() current rejected records fails with an invalid-response error inspectRejectedMutationRecords()

The failure on a legacy record is the published behavior. The deprecated methods do not drop a legacy record, and they do not fill its absent fields. Swift can hold a legacy import from a pre-ledger queue. Kotlin can hold only a legacy rejection. React Native forwards the native result.

React Native exposes the same product surface as the native SDKs, but it must remain a bridge.

SQL calls cross the bridge as SQL strings plus native typed bind arrays. Query results come back as native row objects. The bridge does not serialize SQL params or SQL rows through JSON, so positional null bind values are preserved on iOS and Android.

It does not own:

  • queue semantics
  • rebuild semantics
  • conflict semantics
  • server protocol interpretation
  • Swift is released through Swift Package Manager from the repo root Package.swift
  • Kotlin is released from clients/kotlin/synchro
  • React Native is released from clients/react-native
  • Client consumption has copyable local and published installation declarations, writable database requirements, and a first-sync flow.

The native SDKs support copying a preinitialized SQLite database into place before first open.

Use this for:

  • warm-starting the synced-table schema
  • bundling portable reference data or other installer-time content you want available immediately
  • reducing first-start schema work and initial traffic

Generate the seed artifact with:

  • api/go/cmd/synchro-seed

Current seed rules:

  • the generator may include schema plus explicitly server-declared portable scopes
  • if no portable scopes are declared, the generator produces schema-only output
  • the SDKs keep rows for assigned valid portable scopes; unassigned scopes lose seed provenance and only unchanged cache rows while pending authored intent and application-visible local row state remain for explicit server outcome
  • first authenticated connect validates each seed receipt before issuing a user-bound and client-bound runtime cursor; stale assigned scopes lose only continuation state and rebuild after push-first intent reconciliation
  • auth-scoped user data still arrives through normal sync after login

The current iOS integration requires both Synchro and GRDB in the application Podfile. See Client consumption for the exact published and local declarations.

The SDK contract is defined in: