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
Native SDK responsibilities
Section titled “Native SDK responsibilities”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.
Retained mutation inspection
Section titled “Retained mutation inspection”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
PendingMutationInspectionwith 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
RejectedMutationInspectionwith 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.
Deprecated inspection methods
Section titled “Deprecated inspection methods”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 boundary
Section titled “React Native boundary”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
Installation notes
Section titled “Installation notes”- 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.
Client guides
Section titled “Client guides”Seeded database support
Section titled “Seeded database support”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
React Native on iOS
Section titled “React Native on iOS”The current iOS integration requires both Synchro and GRDB in the application Podfile. See Client consumption for the exact published and local declarations.
Specification
Section titled “Specification”The SDK contract is defined in: