Skip to content

iCloud transport

Instead of a server, the reference app can sync through the user's iCloud account. The data model and the merge rules are the same; only the transport differs. This page describes the mapping so that other Apple-platform clients can join an inventory shared through iCloud, and so that implementers understand what both transports have in common.

Server iCloud
Record identity (entity, id) CloudKit record name <entity>~<id>
Version hlc hlc field, same format
Merge the server and every client apply the merge rules every client applies the merge rules; CloudKit only stores the latest saved version
Change feed server change log and cursor CloudKit change tokens, managed by CKSyncEngine
Attachments content-addressed HTTP endpoints LVEAttachment records with a CKAsset
Authentication tokens, pairing the user's Apple Account
Sharing users on the same server a zone-wide CKShare
Number ranges server reservation not available; device numbering
Locked templates enforced by the server not enforced; clients only show them read-only
Item Value
Container iCloud.com.dxtsystems.lve
Zone name LVE
Owner the zone in the owner's private database, created by the owner's app on first sync
Member the same zone, reached through the member's shared database with the owner's user record name as zone owner

The app uses CKSyncEngine with one engine per device and role, and persists the engine state between launches.

One CloudKit record per protocol record, holding the same envelope as a change.

Item Value
Record type LVERecord
Record name <entity>~<id> with the id in upper case, for example article~8C1F3D2E-5B7A-4A61-9F0E-2B4C6D8E0A13
entity (String) entity type, as in the change
hlc (String) version stamp
deleted (Int64) 1 for a tombstone, 0 otherwise
payload (Bytes) the payload as UTF-8 JSON bytes; absent for tombstones

Tombstones are LVERecords with deleted = 1. CloudKit record deletions are never used, so a deleted record keeps its stamp and older versions cannot bring it back.

Item Value
Record type LVEAttachment
Record name att~<sha256> with the lowercase hexadecimal SHA-256 of the file
file (Asset) the file

The same keys as on the server reference attachments (see Attachments). An attachment record is saved together with every record change that references it; saving an existing attachment again changes nothing. A receiving client stores the asset's bytes under their SHA-256.

Item Value
Record type LVEPlan
Record name plan~team, in the owner's LVE zone
teamActive (Int64) 1 while the owner's Team plan is active, 0 otherwise
updatedAt (Date/Time) when the owner's app last changed teamActive

The record tells members whether the owner's Team plan, which covers them, is active (see Plans).

  1. The app hands every outbox entry to the engine as a pending save of <entity>~<id>, plus a pending save of att~<sha256> for every attachment the change references and the device holds.
  2. When the engine asks for the record, the app builds it from the current version of the record in its store (not from the outbox entry) and reuses the last known server record, so that the save carries CloudKit's change tag.
  3. A successful save acknowledges the outbox entries of that record with the saved stamp.

CloudKit rejects a save whose change tag is outdated with serverRecordChanged and returns the server's record. The app then:

  1. merges the server version into its store with the merge rules,
  2. saves its own version again, now with the server's change tag, if its stamp is greater; otherwise it acknowledges its own entry, because the server version won.

This reproduces "greatest stamp wins" on top of CloudKit, whose own conflict handling would otherwise keep whichever save came last.

CloudKit error Handling
serverRecordChanged merge, then save again only if the local stamp is greater
zoneNotFound create the zone again and retry
unknownItem forget the cached server record and retry as a new record
networkFailure, networkUnavailable, serviceUnavailable, requestRateLimited, zoneBusy retry
anything else mark the outbox entry rejected with CloudKit's error text

For every fetched record:

  • LVEAttachment: store the asset's bytes.
  • LVERecord: build a change from entity, the id from the record name, hlc, deleted and payload, observe the stamp and merge it with the merge rules. Records without entity or hlc are skipped.

Movements keep their first version, as everywhere. Unknown entity types are stored and saved back unchanged.

The owner shares the whole zone:

  1. The owner's app creates (or reuses) a zone-wide CKShare (CKRecordNameZoneWideShare) with no public permission and a title, and presents the system sharing interface. Participants are invited by the owner through iCloud.
  2. A participant opens the invitation. The app accepts the share and syncs as member of the owner's zone through its shared database.
  3. Owner and members read and write the same records; each merges with the same rules.

When the owner deletes the zone or stops sharing, the member's app reports that the share ended.

Role Needs
Owner, own devices only Pro (or Team) on the owner's devices
Owner sharing with a team Pro for the owner's own sync; Team so that members can sync
Member nothing of its own; covered by the owner's Team plan

Members cannot see the owner's purchases. The owner's app therefore records whether its Team plan is active in the LVEPlan record whenever the plan changes, but only after it has shared the inventory from that device. Members read the record at the start of every sync round:

  • no record: treated as active,
  • record could not be read: the last known value stays,
  • teamActive = 0: the member pauses sync until the value is 1 again.

Because this is updated only while the owner's app runs, members keep syncing until the owner's app has seen the plan end. A paused device keeps its data and its outbox; see When the licence lapses for the same behaviour with a server.

Turning on iCloud disconnects a server connection (the token, cursor and reserved numbers are dropped) and queues every record for upload, as connecting to a server does. A device uses one transport at a time. To move an inventory from iCloud to a server, connect one device that holds all data to the server: it pushes everything, and other devices then connect to the server as well.