These 54 commits are when the Protocol Buffers files have changed:
| Commit: | a77ff58 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.7.20 (#274) * Reject duplicate attach of the same key on one client Rapidly re-attaching a document key whose previous attach is still in flight sent a second AttachDocument for the same key. The server reports the already-attached key as a misleading ErrClientNotFound, which handleConnectError escalates into deactivating the whole client. Guards attach locally with a document-scoped ErrAlreadyAttached before the request is sent. attachmentMap covers the resolved case; a new attachingDocs set covers the concurrent in-flight case, since attachmentMap is only populated once the attach round trip resolves. Swift uses defer rather than upstream's finally to clear the key, so it is released on every exit including a thrown error. * Add epoch and stable actor_id fields to the protocol Regenerates the Swift protobuf for the three fields yorkie-js-sdk#1338 adds, which offline persistence and stable-actor watch depend on: - ChangePack.epoch = 8, so a resumed session can present the persisted epoch alongside its checkpoint and have the server re-accept re-pushed changes. - ActivateClientResponse.actor_id = 2, the stable actor the server derives from the project and client key. - WatchRequest.actor_id = 3, declaring that actor on the watch stream so peer ids in watched/unwatched events match the presence CRDT keying. All three are new field numbers, so the change is additive and wire-compatible: a client that omits them behaves exactly as before and the server falls back to client_id as the actor. Generated with the pinned buf plugins (apple/swift v1.37.0, connectrpc/swift v1.0.3). The only removed lines are the three _protobuf_nameMap entries replaced by versions carrying the new fields; yorkie.connect.swift is unchanged because no service changed. * Add DocStore and SessionLock, the storage seams for offline resume Ports the two pluggable interfaces yorkie-js-sdk#1338 introduces, ahead of the serialize and resume logic that uses them. DocStore is a three-method async protocol keyed by document key, with an in-memory default. Data is a value type, so the defensive copies upstream makes around its Uint8Array buffers are unnecessary here. SessionLock keeps two sessions from resuming the same persisted document and re-pushing the same un-acknowledged changes. Upstream's default is built on the browser Web Locks API to keep tabs apart and is documented there as a no-op outside a browser. iOS has no tabs and an app is one process, so NoopSessionLock is the default and matches that non-browser behaviour rather than emulating Web Locks. The hazard does remain reachable on Apple platforms when a persistent store is shared between an app and its extensions through an App Group, so the protocol is public and the documentation points a caller needing that at NSFileCoordinator or a lock file. * Bump version to 0.7.20 and pin CI yorkie server to v0.7.20 Unlike the last three releases this one is not client-side only: offline resume needs a server that accepts a re-pushed change pack against a persisted checkpoint and epoch, and that supports stable-actor watch (yorkie#1969, yorkie#1970). The CHANGELOG entry carries that minimum explicitly, matching how v0.7.14 and v0.7.16 flagged theirs. Homebrew has two "update 0.7.20 bottle." commits. Pinned to the later one, which adds an arm64_golden_gate bottle and carries different sha256s for the platforms the earlier commit already had — the bottles were rebuilt, so the earlier hashes may no longer match what is hosted. * Serialize and restore a Document for offline resume The Document half of yorkie-js-sdk#1338: a document plus its un-acknowledged local changes can be written to bytes and read back in a later session, so those changes can be re-pushed against the persisted checkpoint and epoch. toBytes packs six blobs behind 4-byte little-endian length prefixes, in upstream's order: snapshot, checkpoint, changeID, pending changes, epoch, docID. The framing matches upstream exactly; the payloads are where this diverges. Three deliberate divergences: - Pending changes are serialized as protobuf rather than upstream's JSON change-structs. TypeScript gets toStruct nearly free from structural typing; Swift would need hand-rolled Codable for every operation type, and Converter already round-trips a Change. The blob is local-only and never crosses SDKs, so only round-trip fidelity within iOS is required. Revisit if a persisted blob ever moves between SDKs. - getPendingChangeStructs returns [Change]. Upstream's struct layer exists only to feed that JSON encoding, so there is nothing to invent here. - toBytes and the restore entry points throw, where upstream's toBytes does not: snapshotToBytes calls the already-throwing Converter.toElement, and swallowing that would mean a force-unwrap. This follows Converter.objectToBytes and treeToBytes, which throw for the same reason. Also wires epoch through applyChangePack and createChangePack, without which the field would be dead state, and adds Converter.snapshotToBytes as the encode counterpart to the existing bytesToSnapshot. The unpacker bounds-checks both the length prefix and the slice it implies before reading, so a truncated or hostile envelope throws rather than trapping. Four of the ten tests cover malformed input, and the framing is pinned: flipping the length decode to big-endian fails them. * Wire offline persistence and the stable actor into Client The Client half of yorkie-js-sdk#1338. Stable actor: activate now learns actor_id from the server and stamps it into document changes instead of the session id, and declares it on both watch streams so peer ids in watched/unwatched events match the presence CRDT keying. An older server sends no actor_id, in which case the session id remains the actor and nothing changes. Exposed as getActorID(). Offline persistence, active only when a store is configured. Attach takes a session lease before any restore or RPC so a second session fails fast rather than driving sync against a shared checkpoint, then restores the persisted document before building the change pack, so the resume re-pushes the un-acknowledged changes rather than an empty pack. The initial presence is seeded only when no restore happened; a restore brings the presence map back with it, and seeding around it would either append a spurious local change or be silently overwritten. Three ways a resume can fail to reconcile, each surfaced as a localChangesDropped event carrying the discarded changes rather than losing them silently: - the stored bytes cannot be restored, either because the actor guard rejects a store reused under another identity (actor-mismatch) or because they cannot be decoded at all (restore-failed). The changes are recovered from the stored bytes, since the document that failed to restore never took them on; - the server compacted past the persisted epoch and rejects the resume (epoch-reanchor), so the document is re-anchored and the attach retried once as a fresh one; - the server purged the document while this client was offline and minted a fresh one, detected by a changed document id or a server sequence that regressed to 0 under a non-empty local snapshot (document-purged). The document id the guard compares against is now recorded on every attach and persisted with the envelope. Re-anchoring resets the change id along with everything else, so the actor is re-stamped at each reset site. Persisting is driven off every local change rather than the sync round trip: what this feature has to survive is precisely the case where no sync ever runs to completion, so a change is stored the moment it is made. The lease lives on the Attachment, so its lifetime is exactly the attachment's; an attach that fails before creating one releases it rather than locking the key out of every retry. * Add tests for the offline-persistence Client seams Covers what is reachable without a server: the MemoryDocStore lifecycle including concurrent access through its actor, NoopSessionLock's deliberate lack of coordination, a hand-rolled contended lock proving the refusal contract attach relies on, the Document.onLocalChange hook that drives persistence (fires on local changes, not on remote ones), the localChangesDropped event for each reason, and a full store round trip asserting the pending changes a resumed session would re-push survive toBytes/fromBytes intact. The private resume paths -- prepareOfflineResume, releasePersistence, the epoch-reanchor retry and the purge guard -- are only reachable through a real attach round trip, so they belong to the integration suite; the file's header records that rather than faking a server. * Close the offline-persistence gaps found in review Ten defects in the Client half, found by reviewing the port against yorkie-js-sdk#1338 rather than in isolation. Three lose user data on ordinary paths. A failed restore no longer resets the document. restoreFromBytes is all-or-nothing and throws before touching it, so there was nothing of the stored copy to undo -- but the document may hold edits the caller made before attaching, and the reset destroyed them. They were not even in the dropped payload, which is read from the stored bytes. Sync persists again after applying the response pack. A push that is merely acked advances the checkpoint and drains localChanges without appending one, so the local-change hook never fires; the stored envelope kept an already-pushed change under a stale checkpoint until the next edit, and a resume re-pushed it. Upstream persists in both places for this reason; replacing one with the other was wrong. The in-flight marker is set before the first suspension point. It sat after the offline-resume preamble, which really suspends on acquiring the lease and loading the store, leaving a window where a second attach of the same key saw both containers empty, passed the guard and reached the server -- where the duplicate surfaces as a misleading ErrClientNotFound that deactivates the whole client, the failure js#1337 exists to prevent. Also: deactivate releases every held lease, which otherwise outlived the client and locked the document key out of every later session under a cross-process lock; persists for one document are chained so two writes cannot land out of order and leave the store holding the older envelope; the epoch re-anchor retry seeds the initial presence the resume path skips, without which the client attached with no presence entry and was invisible to peers; restore failures are classified by error code rather than error type, since every envelope decode failure is a YorkieError too and a truncated file was reported as someone else's store; the envelope decoder tolerates trailing blobs it does not know, so an app downgrade cannot discard un-pushed edits; and the lease is released when seeding the initial presence throws. The dropped-changes payload is now a public DroppedChange projection. Change's fields are internal, so an app handling the data-loss event could read nothing from it -- defeating the purpose of carrying the changes at all. Adds an integration suite covering what only a server can prove: that a resumed document's un-pushed changes reach the server, that the lease and stored copy are torn down on each path that ends an attachment, and that concurrent duplicate attach is rejected locally. * Cover the unusable-stored-copy path end to end The failed-restore branch had no test. Mutation-checked: restoring the reset that was removed makes this fail with the caller's edit gone from both the local document and the server, which is the defect it pins. * Align the tests with the stable actor, and cover the review fixes The stable actor is not just a new identifier: it changes how concurrent edits are resolved. The actor used to be the session id, a time-ordered ObjectID, so the second-created client always held the higher actor and won every tie -- an outcome the integration tests encode throughout. A stable actor is derived from the client key, so creation order no longer implies actor order. withTwoClientsAndDocuments now pins c1 to the lower actor and c2 to the higher, keeping the tie-break deterministic, as upstream does in its own helper. With that, the version-vector and presence assertions move from the session id to the actor, since that is what keys the version vector, the presence map and the peer ids in watched events now. This mirrors the test changes that ship with yorkie-js-sdk#1338. Note for callers: peers are keyed by the stable actor, so an app that used Client.id to find itself among getPresences() must use getActorID(). This is a behavioural change in a public API. Adds coverage for the review fixes: envelope tolerance of trailing blobs in both directions, the DroppedChange projection field by field, the actor-mismatch and corrupt-envelope error codes pinned together, and the persist chain driven through Client's own enqueuePersist rather than a reimplementation of it -- removing the chaining makes that test fail, which a mirror of the algorithm could never have shown. The offline persistence helpers are internal rather than private so the test can reach them. The epoch-reanchor branch stays uncovered by unit tests: activate makes a real RPC that cannot be mocked into success, so there is no seam to drive attach without a server. The integration suite covers it instead. * Note the stable-actor behavioural change in the changelog * Pass the actor to the remaining version-vector assertions Three call sites wrote `client.id` without a force-unwrap, so the earlier substitution missed them and they kept passing the session id into maxVectorOf. The version vector then carried no entry for the document's actor and garbageCollect collected nothing -- a silent no-op rather than a failure, which is why it read as "0 is not equal to 3". * Close the teardown gaps found in the second review A client torn down internally kept its session leases. When the server no longer knows a client, handleConnectError deactivates it in place rather than through deactivate(), and that path dropped the attachments -- and with them the only reference to each lease -- without releasing them. deactivate() could not clean up afterwards either, since it returns early once the status is already deactivated. Under a cross-process lock the document key was then locked out for the lifetime of the process. Teardown is now three phases rather than two: quiesce, remove, release. Detach queues a persist of its own by clearing presence, so removing the stored copy before that write had drained let the save land after the removal and resurrect the envelope. Simply removing after releasing would have reopened the other side of it -- another session resuming a copy that is about to be deleted -- so the two are separated by the drain. The stable actor is adopted only when it is a usable ActorID. Every ticket and change id encodes it through String.toData, which yields nil for anything that is not even-length hex and is then written as an empty actor with no error, collapsing tie-breaks and keying the version vector on "". A server sending something unexpected now falls back to the session id with a warning rather than silently corrupting every change. Also: restoreFromBytes, getPendingChangeStructs and resetForReanchor were public by accident of living in a public extension, and are internal mechanisms rather than API; a presence-seeding failure on the re-anchor retry is logged rather than swallowed, since the same input fails loudly on a normal attach; and the sync-path persist is gated on the attachment so it cannot outlive it. The lease-leak test injects ErrClientNotFound through the service mocking seam so it reaches handleConnectError with a lease actually held -- an earlier version passed with the fix reverted, because deactivate() had already released the lease and nothing was left to leak. * Set the changelog date to the PR-creation date * Address the CodeRabbit review A failed attach left everything it had taken. Once the attachment owns the session lease, the catch block released nothing, so a throw in the watch loop, the initialization wait or the initialRoot update left the attachment registered, the lease held and the document persisting behind an attach the caller saw fail. rollBackFailedAttach undoes all three. A storage read failure is no longer indistinguishable from an absent key. `try?` turned a transient read error into "nothing stored", so the attach went ahead fresh and the first local change overwrote the very envelope that could not be read. The attach still proceeds -- failing it outright would make a document unusable over a read glitch -- but the session does not persist, so whatever is stored survives for a session that can read it. A failed removal of the persisted copy is logged rather than swallowed. The lease is still released: SessionLock has no recovery API, so holding it until removal succeeds would strand it, locking the document key out for the lifetime of the process, which is worse than an envelope outliving its detach -- and a stale envelope is re-validated on the next resume by the actor guard, the epoch check and the purge guard. The change id now advances before the local-change hook fires. Not reachable today, since Document is main-actor isolated and the mutation is synchronous, so the task the hook starts cannot run until it returns; the ordering should not depend on that reasoning holding. test_can_handle_concurrent_attach_with_initialRoot builds its two clients directly rather than through the helper, so it never got the actor ordering swap and its expected winner became a coin flip under the stable actor. It passed the previous run by luck. Pinned the way upstream pins the same test. * Close the outside-diff findings from the CodeRabbit review The previous commit's canPersist fix was incomplete. It skipped the local-change hook when the store could not be read, but syncInternal persists on its own and was gated only on the store and a live attachment, so a successful sync still overwrote the envelope this session had deliberately left alone. The decision now rides on the attachment as persistsToStore and gates every enqueuePersist. remove() never released the session lease. detachInternal clears attachmentMap itself, so the attachment looked up after it is nil and the lease it held was never released -- locking the document key out of every later session under a cross-process lock. It is now captured before detachInternal, as the detach path already did. This is the second teardown path on this branch to leak a lease the same way, after handleConnectError. Both come from cleanup living in the callers of detachInternal rather than inside it, which is where upstream keeps it; the iOS split exists because detachInternal is synchronous and the release is async, but it has now cost two bugs. test_can_handle_concurrent_attach_with_initialRoot read doc2 where it meant doc1 in its final step, so it never actually asserted that user1's document accepts the higher actor's root after syncing.
The documentation is generated from this commit.
| Commit: | fe47a69 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.7.16 (#270) * Keep a tree position resolvable when two nodes claim one ID * Stamp merge-bound inserts and filter style-range interlopers * Stop undo and element splits from reusing a node's identity Adds Operation.TreeEdit.split_tickets (field 11) to the wire, so a replica applying an element split consumes the tickets the originating replica issued instead of reconstructing them. The iOS reconstruction fallback also re-added the content count on every call, so a multi-level split produced non-consecutive delimiters that diverged from the JS SDK and the server. The base is now captured once and advanced one delimiter per issued ticket, matching upstream. * Bump version to 0.7.16 and pin CI yorkie server to v0.7.16 * Address review: propagate position-resolution errors from style and edit `mergedAnchorInterloperGuard` and the merge-bound insert stamp swallowed a failing `toTreeNodePair` with `try?`, silently disabling the §9.4 guard or leaving an insert unstamped where the JS SDK aborts the operation. Both call sites already throw, so propagate instead — a silent cross-SDK divergence is worse than a visible error. Also drops the `offset == size` half of the splitText early return, which the new range guard already covers. * Add tests for v0.7.16 tree node identity, split tickets and merged style anchors * Note the v0.7.16 server requirement in the changelog * Extract tree edit and style helpers to satisfy the lint gate The v0.7.16 port pushed CRDTTree.edit, CRDTTree.style and TreeEditOperation.execute past the 110-line function_body_length limit, and left the split-ticket TODO block orphaned from its declaration. Pulls out resolveStyleRange, intendedMergeStamp, recordInsertedContent and makeSplitTicketIssuer. Behaviour is unchanged: 661 unit tests and the full integration suite against a v0.7.16 server pass exactly as before. * Port the remaining v0.7.16 unit tests for merge-stamp propagation Adds the two tests from yorkie-js-sdk#1317 that were missed: splitElement carrying mergedFrom/mergedAt onto the split product, and an insert declared inside a merged-away parent being stamped with the declared parent. Both were mutation-checked: reverting the cloneElement merge stamp fails the first. The second's mergedAt assertion does not discriminate the sibling-ticket lookup on iOS, because CRDTTreeNode.remove keeps the earliest tombstone where the JS SDK overwrites with the newer one; that divergence predates this sync and is noted in the test.
| Commit: | 316dce9 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.7.14 (#268) * Move tombstones with merge to preserve tree RGA anchors A merge collected only live children, so tombstoned children were left behind in the merge source. A concurrent insert that referenced one of those tombstones as its RGA anchor then resolved against a node the merging replica no longer had in the merge target, and the two replicas ordered the insert differently. Collect allChildren (innerChildren) instead, and move tombstones along with the live ones so they survive as RGA anchors in the merge target, where the RGA tie-break orders the concurrent insert consistently. Moving a tombstone with detachChild + append would corrupt the index, because detachChild unconditionally subtracts the child's paddedSize from the old ancestors even though a removed node contributes nothing. Add IndexTreeNode. moveChild, which re-parents with size accounting only for live children. iOS caches just the visible size and derives the include-removed total on demand, so unlike yorkie-js-sdk only that one dimension is relocated. Nodes without a parent are now skipped rather than appended untracked: without mergedFrom they are invisible to the merge-delete propagation and rebuild. * Flatten chained merge to converge an anchored concurrent insert When a merge lands on a parent that a prior concurrent merge had already merged away, the children piled up under the removed intermediate, so the chain P->Q->R stayed nested. rebuildMergeState can only ever represent the compressed chain — it records one mergedFrom pointer per child and reads the child's final physical parent — so runtime state and snapshot-derived state disagreed, and replicas that applied the two merges in different orders diverged. Resolve the destination through the new resolveMergeTarget, which follows the mergedInto chain past merge-away tombstones (with a seen set guarding against a concurrent mutual merge), and move children to that final target instead. mergedFrom/mergedAt are now stamped only on a child's first move, so a child carried through a chained merge keeps its original source and the original merge ticket; stamping on every move would record a different ticket depending on the order the merges were applied. mergedInto is now derived from moved children rather than the merge-source list, matching what rebuildMergeState can reconstruct, and the delete propagation compares against the resolved destination rather than fromParent. * Keep style ranges from crossing a concurrent merge anchor A style range boundary inside a merged-away parent resolved through the insertion boundary, which sits before the first child the merge moved. That extended the range over nodes a peer had concurrently inserted between the merge-source tombstone and those moved children — nodes the styling client saw outside its range, because at the time it styled they sat after the then-live parent. Add a boundary mode to findNodesAndSplitText. A range boundary now resolves to the position right after the merge-source tombstone, so the range neither grows over nor shrinks past nodes inserted at that anchor. style and removeStyle pass .range; edit keeps the insertion boundary, where RGA ordering must break ties. * Add identity-preserving restore for Tree undo/redo Reverse a Tree edit by reviving the nodes it removed and re-removing the nodes it inserted, both by ORIGINAL identity, instead of copy-reinserting content. Concurrent undos then converge, because both replicas act on the same nodes rather than each inserting a fresh copy. edit() captures one TreeRestoreSpan per node it transitions visible to tombstoned, plus one per node it inserts, and returns them only when the edit was merge- and split-free; otherwise the spans do not fully describe the deletion and the op layer keeps the copy-reinsert reverse. CRDTTree gains restore/retombstone and a recreate path that rebuilds a GC-purged node under its original id, anchoring it by same-insertion neighbour, then the captured boundary siblings, then a deterministic id-order fallback so every replica lands it in the same slot. These live in their own extension to keep the type within its length budget. compareNodeID is not ported: CRDTTreeNodeID is already Comparable on (createdAt, offset), the same total order the fallback needs. The guarded insPrev/insNext derefs in the upstream change were already optional-chained here. * Bump CI yorkie server to v0.7.14 TreeEdit.restore_spans/restore_mode/retombstone_spans (fields 8-10) only exist from server v0.7.14. The server re-encodes operations from its own domain model rather than relaying bytes, so a v0.7.13 server silently drops those fields: an identity-preserving Tree undo applies on the originating replica and reaches peers stripped, leaving them diverged. Pinned to the bottle commit rather than the version bump, whose bottle block is a placeholder that fails to pour. * Recreate purged text runs in order on restore, not reversed When every character of a run is its own insertion, none of the same-insertion anchor rungs fire during restore, so each recreated fragment fell through to the same fixed fallback anchor and prepended there — rebuilding the run reversed ("my name" came back as "eman ym"). Thread a chain anchor through restore: the last node placed at the current cursor, in document order. A fragment with no surviving same-insertion anchor now chains after it, keeping the run left-to-right. Added as rung (d), between the rightmost-piece rule and the operation's fallback anchor. This only reproduced once the tombstones were GC-purged; un-tombstoning in place already preserved order. * Add tests for v0.7.14 restore ordering and int32 promotion parity TextRestoreAfterGCTests ports the upstream regression: type a string character by character, delete a run, purge the tombstones, then undo — and assert the run returns forward rather than reversed. Verified it fails without the fix, producing "hello eman ym is". PrimitiveTests gains a parity guard for yorkie-js-sdk#1291. That change promotes out-of-int32 integers to Long because JS has a single number type and classified any integer as Integer, overflowing int32 on the wire. Swift cannot express the bug — every .integer construction is fed by a statically-typed Int32, and Int maps to .long — so there is no logic to port. The test pins the property that makes it unrepresentable. * Bump version to 0.7.14 and add the CHANGELOG entry Entries mirror the yorkie-js-sdk v0.7.14 changelog wording, one line per upstream change. The iOS PR links are backfilled once the PRs exist, so the links point at the PR that actually carries each change rather than a number guessed ahead of time. * Slice recreated tree text in UTF-16 with an exclusive end recreateFromSpan built the recreated fragment with String.substring(from:to:), which treats `to` as inclusive and indexes by grapheme. Tree text offsets are UTF-16 throughout, so the slice ran one character too long and, for a span containing an emoji, could return empty. The bug was masked whenever the recreated gap reached the end of the span, because the inclusive end clamps to count - 1 — so a whole-span undo looked correct. On a partial gap it produced a node whose value and size disagreed with the JS SDK, corrupting every ancestor's index accounting and overlapping the id range of a surviving piece, silently and without a throw. Slice the NSString directly with an exclusive NSRange and guard the bounds. Also add the tests the feature shipped without: restore/retombstone identity and idempotence, the moveChild size accounting in both dimensions (the one place the port deliberately diverges from JS), and a TreeRestoreSpan converter round-trip covering a tombstoned attribute and each malformed-timestamp rejection. * Backfill the PR links in the v0.7.14 CHANGELOG entry
| Commit: | a1396b0 | |
|---|---|---|
| Author: | Lewy | |
Add identity-preserving restore for Tree undo/redo Reverse a Tree edit by reviving the nodes it removed and re-removing the nodes it inserted, both by ORIGINAL identity, instead of copy-reinserting content. Concurrent undos then converge, because both replicas act on the same nodes rather than each inserting a fresh copy. edit() captures one TreeRestoreSpan per node it transitions visible to tombstoned, plus one per node it inserts, and returns them only when the edit was merge- and split-free; otherwise the spans do not fully describe the deletion and the op layer keeps the copy-reinsert reverse. CRDTTree gains restore/retombstone and a recreate path that rebuilds a GC-purged node under its original id, anchoring it by same-insertion neighbour, then the captured boundary siblings, then a deterministic id-order fallback so every replica lands it in the same slot. These live in their own extension to keep the type within its length budget. compareNodeID is not ported: CRDTTreeNodeID is already Comparable on (createdAt, offset), the same total order the fallback needs. The guarded insPrev/insNext derefs in the upstream change were already optional-chained here.
| Commit: | f83b39e | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.7.13 (#267) * Prevent panic when splitLevel walks past the tree root * Register GC pairs for pieces split off tombstoned nodes * Identity-preserving restore for Text undo/redo * Bump version to 0.7.13 and pin CI yorkie server to v0.7.13 * Add tests for v0.7.13 identity-preserving restore and GC split leaks * Address review: carry attributes on restored text, validate RestoreSpan from the wire * Resolve head-anchored reverse-edit range when restore fields are stripped * Clamp RestoreSpan bounds from the wire instead of throwing yorkie-js-sdk's fromPbSpan never rejects a span, so throwing here diverged from upstream and could wedge applyChangePack: the error escapes the sync loop, and the change pack re-pulls and re-fails indefinitely. Derive the span bounds from content length and clamp a negative start, which keeps the NSString slicing in restore() in bounds without rejecting the op. A nonconforming peer now degrades to a harmless no-op. * Cap RestoreSpan start so the derived end cannot overflow The clamp derived end as start + content length, and Swift's + traps on Int32 overflow. A wire span with a very large start therefore terminated the process instead of degrading to a harmless no-op, which is the opposite of what the clamp was for. Cap start at Int32.max - length so the derived span always stays representable, and cover the boundary with a regression test.
| Commit: | fa2e042 | |
|---|---|---|
| Author: | Lewy | |
Identity-preserving restore for Text undo/redo
| Commit: | 3e87069 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.7.12 (#266) * Add disablePresence option for presence-free Documents * docs: add CHANGELOG entry for v0.7.12 * chore: bump version to 0.7.12 and CI yorkie server pin * test: add disablePresence unit + integration coverage for 0.7.12 sync
| Commit: | c2f51ca | |
|---|---|---|
| Author: | Lewy | |
| Committer: | Lewy | |
Add disablePresence option for presence-free Documents
| Commit: | 523a607 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.7.10 (#263) * fix: deselect editor selection when undo/redo changes text In the RichTextEditor example, undoing/redoing a text edit (vs a style-only change) shifts the characters under the current selection, so the kept NSRange highlighted the wrong text (e.g. select "SDK", undo "SDK"->"SD" left "SD " selected). undo()/redo() now compare the document's plain text before/after and, when it changed, collapse the selection to a caret — mirroring Android, which deselects in the same case. Style-only undos leave the text unchanged and keep the selection. * fix: correct RichTextEditor remote-collaboration rendering - apply remote un-strike (and other style removals) by decoding a non-"true" attribute value as the unset style instead of ignoring it - identify peers by clientID rather than username so the participant list and cursors no longer treat a peer sharing the local display name as self - preserve the local scroll position on remote-driven re-renders (remote text, presence/cursor, watched/unwatched) so a peer's edit or selection no longer jumps the viewport to the last line; local edits still follow the caret * fix: collapse selection to caret after deleting a selected range Deleting a selected range left RTUITextField re-applying the now-stale multi-char selection (a UITextRange from the pre-delete, longer text) onto the shortened text, highlighting the wrong span. Collapse the selection to a caret at the end of the deleted range so updateUIView's backspace-style length subtraction lands the caret at the selection start (e.g. deleting 8...10 leaves the caret at 8). * fix: sync formatting toolbar state when applying a style Applying a style via a toolbar button did not update isBold/isItalic/ isUnderline/isStrikethrough, so the button only reflected the change on the next selection change ("reselect to activate"). Because the state stayed stale, the next tap re-sent the same value (e.g. bold:true on already-bold text), producing a same-value no-op style op that polluted the undo/redo stack (undo appearing to do nothing) and synced needlessly. Reflect the applied value on the toolbar immediately so toggling works and no no-op style ops are created. * Add disableGC attach option * Channel lifecycle via RefreshChannel only * chore: bump to 0.7.10 — changelog, version, CI yorkie-server pin * refactor: move Client channel methods into an extension The channel lifecycle methods (attachChannel/detachChannel/refreshChannel/ heartbeat/syncChannel) grew with the RefreshChannel-only consolidation, pushing the Client class body to ~999 lines against SwiftLint's 1000-line type_body_length limit. Relocate them verbatim into an extension Client (no behavior change; same-file extension retains private member access) to bring the class body to ~861 and restore margin. * docs: backfill 0.7.10 CHANGELOG PR link * test: rename single-char client variable to satisfy SwiftLint identifier_name * style: collapse double blank line at extension boundary (SwiftLint vertical_whitespace) * style: single-line Attachment init to satisfy SwiftFormat indent * test: cover channel-method guards and error-recovery branches Add integration tests for the previously-uncovered guard and error/recovery paths in Client's channel methods (attach/detach/sync not-active/not-detached/ not-attached guards, attachChannel first-call-failure rollback, and refreshChannel ErrSessionNotFound recovery), lifting Sources/Core/Client.swift patch coverage above the 75% bar.
| Commit: | cad0ae8 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.7.9 (#262) * feat(devtools): add cross-platform CRDT debugger Adds an opt-in devtools layer for diagnosing where iOS / Android / JS diverge during sync, by capturing the exact data a client exchanges with the server in the yorkie-js-sdk devtools format. - Sources/Devtools: an always-on ring-buffer recorder that encodes every replayable DocEvent to byte-compatible Array<DocEventsForReplay> JSON, plus iOS-only connection / sync-status diagnostics. Document gains an enableDevtools option, dumpDevtools/exportDevtools, and a publish() tap. - Sources/DevtoolsUI: an in-app SwiftUI inspector (document tree + change timeline) reading the recorder live. - Sources/DevtoolsServer: a debug-only loopback/LAN HTTP server that serves a self-contained browser viewer at / and the live buffer at /events, so a browser shows changes in real time and can diff two recordings to locate the first divergent operation. - Tools: standalone sample recordings and usage docs. - Examples/RichTextEditor: wires both inspectors in and moves undo/redo ahead of the B/I/U/S formatting buttons. * Fix Polling channel subscriber notification Port of yorkie-js-sdk#1247. On a channel refresh (polling), publish a presence-changed event only when the session count actually changed, mirroring the JS RefreshChannel path. The 'expose polling via React' part of the upstream PR has no iOS counterpart. * Add PeekChannel client Port of yorkie-js-sdk#1256. Adds the PeekChannel RPC (proto + regenerated Connect/protobuf), a YorkieService wrapper, and Client.peekChannel(_:) which reads a channel's session count without creating a server session, heartbeats, or event subscription. The usePeekChannel React hook from the upstream PR has no iOS counterpart. * chore: bump version and changelog to 0.7.9 * chore: bump CI yorkie server to v0.7.9 * test: port tests for PeekChannel and polling channel notification Port of channel_polling_test.ts at v0.7.9 covering yorkie-js-sdk#1256 (Add PeekChannel client) and yorkie-js-sdk#1247 (Fix Polling channel subscriber notification). Five tests in Tests/Integration/ChannelPollingTests.swift: - peekChannel returns session count without creating a session - peekChannel reflects increased count when a second client attaches - peekChannel throws errClientNotActivated when client is inactive - refreshChannel publishes presenceChanged when count increases (PR#1247) - refreshChannel does NOT publish presenceChanged when count is unchanged The two server-side peekChannel tests skip automatically with XCTSkip when the tunneled server predates v0.7.9 (unimplemented RPC). All five tests compile and run cleanly; three pass, two skip on the current tunnel. * Add live room member counts to Channel example via peekChannel * fix: satisfy CI-pinned SwiftFormat hoistTry and SwiftLint identifier_name in devtools tests * fix: move Client.peekChannel into an extension to satisfy type_body_length The added method pushed the Client class body to 1007 lines, over the 1000-line SwiftLint type_body_length limit. Extension bodies are not counted, so peekChannel moves into a public extension Client with no behavior change. * fix: extract devtools tap from Document.publish to satisfy cyclomatic_complexity The devtools recording tap added two branches to publish(_:), pushing its cyclomatic complexity to 26 (over the SwiftLint --strict warning threshold of 25). Move the tap into a recordForDevtools(_:) helper; no behavior change. * test: harden channel polling tests (unique keys, deterministic waits) - Replace second-resolution timestamp in channelTestKey with an 8-char UUID prefix to prevent key collisions across concurrent or rapidly-repeated runs. - Harden the "no event when count is unchanged" test with an inverted XCTestExpectation (1 s window) so late callbacks delivered on RunLoop.main are caught before the assertion fires, eliminating the race in the original plain-counter check. - Convert comment blocks preceding the class and helper declarations to /// doc comments to satisfy the SwiftFormat docComments rule. - Document why the line-58 defer teardown is not fragile (fire-and-forget deactivate, non-fatal by design; key uniqueness was the only real concern). * fix: address CodeRabbit review feedback - DevtoolsRecorder: replace single onUpdate closure with multicast addObserver/removeObserver so the browser server and SwiftUI inspector can observe concurrently without clobbering each other; fire on clear(). - Client.peekChannel: wrap RPC in do/catch + handleConnectError, matching attachChannel/detachChannel so auth/state recovery runs on failure. - DevtoolsServer/Resources/viewer.html: escape all recording-derived values before innerHTML; use null-prototype maps for path-tree segments. - DevtoolsUI/JSONNode: stable path-based id so OutlineGroup keeps expansion state across live refreshes (was a fresh UUID per build). - RichTextEditor example: gate devtools recording + LAN inspector behind #if DEBUG so release builds never record or expose document data. - Channel example: clear stale member count when peekChannel fails; add accessibility labels to icon-only undo/redo/inspector buttons. * test: use drain expectation to fix flaky unchanged-count polling test The "no event when count is unchanged" test was non-deterministically failing with "Fulfilled inverted expectation". Root cause: `attachChannel` unconditionally starts a Watch stream. The stream delivers a `ChannelPresenceEvent(type: .initialized, ...)` via `handleWatchChannelResponse` asynchronously after `attachChannel` returns. Because the stream dispatch is async (Task + RunLoop.main), this event can arrive during the 1 s inverted-expectation window that was installed immediately after `syncChannel` — firing the callback and tripping the inverted expectation even though `refreshChannel` correctly suppressed its own event (the PR #1247 fix is working correctly). Note: `Channel.isRealtime` only controls the heartbeat timer; it does NOT prevent the watch stream from starting, so `isRealtime: false` alone would not close the race. Fix: install a drain expectation before the first `syncChannel` and wait for it to be fulfilled (via `fulfillment(of:timeout:5.0)`) before swapping in the inverted "no event" expectation. This guarantees that both the refresh RPC path and the watch-stream initialization path have completed before the observation window opens, so any event arriving in the 1 s window is genuinely spurious and correctly fails the test. * test: use addTeardownBlock for awaited channel test cleanup Replace defer { Task { try? await ... } } fire-and-forget cleanup with addTeardownBlock, which XCTest awaits before the test completes — so client deactivation/detachment finishes before the next test runs instead of leaking activated clients across tests. Per CodeRabbit review. * fix: address CodeRabbit follow-ups (observer snapshot, JSONNode id escaping) - DevtoolsRecorder.notifyObservers snapshots observers before iterating, so an observer callback that (un)registers cannot mutate the dictionary mid-iteration. - JSONNode escapes '/' and '\' in each path segment when building the stable id, so an object key containing '/' cannot collide with a deeper path. * chore: exclude devtools modules from code coverage Devtools (recorder, browser server, SwiftUI inspector) is opt-in debug tooling; exclude Sources/Devtools, Sources/DevtoolsServer, Sources/DevtoolsUI from Codecov so it doesn't drag the SDK's coverage metric.
| Commit: | 25547d5 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.7.6 (#258) * Copy attributes to split node in cloneElement * Implement toJSON for Bytes and Date primitive types * Register LWW-losing element in GC set on Set conflict * Apply array-move-convergence with LWW position register Port the array-move convergence fix: each RGANode now carries an LWW position register (position_created_at/moved_at/removed_at in the proto) separate from the element, so concurrent moves converge by LWW on the position. moveAfter resolves the anchor by position identity and, on both win and loss, produces a dead position node returned for GC; MoveOperation carries position identity (via posCreatedAt) to stay wire-compatible with the Go server/JS. Dead position nodes serialize with position timestamps and no element. CRDTRoot's GC walk registers dead position nodes. Ports yorkie-js-sdk#1227. * Unify presence event emission with reconcilePresence * test: cover array-move LWW, primitive toJSON, cloneElement attrs, GC, presence Port the v0.7.6 test scenarios: array-move LWW position register and move convergence (RGATreeList), primitive toJSON for Bytes/Date, split node attribute copy, LWW-losing element GC on Set conflict, and the reconcilePresence event emission. Integration cases need a live yorkie server v0.7.6. * chore: bump version to 0.7.6 and update changelog * chore: bump CI yorkie server to v0.7.6 * test: expect base64 bytes in various-types after toJSON change #1225 changed Primitive.toJSON to emit Bytes as base64 (JS-faithful), so the pre-existing test_can_support_various_types must expect "AQI=" for [1,2] instead of the old decimal-array "[1,2]". Drop the now-resolved bytes/date TODO. * Fix array index corruption when appending after a tail move moveAfter's winner path deleted the old (now-dead) position node from the index splay tree while `last` still pointed at it. A subsequent insert/append that anchors on `last` (e.g. append after moving the tail element to the front) then failed to locate its anchor in the index tree, so the appended element was never indexed — get(index:) returned nil and crashed callers. Keep the dead position node in the splay tree with length 0 (re-splay to refresh weights) instead of deleting it, matching the LWW-loser path and JS, which keep dead position nodes splayed until GC purge. * Fix array move-node encoding and deepcopy to preserve positions - Converter.toRGANodes: encode a moved node's positionMovedAt from the actual move ticket (entry.posMovedAt), not positionCreatedAt, and branch on posMovedAt like JS toRGANodes — keeps the move timestamp on the wire. - CRDTArray.deepcopy: iterate all position nodes and reconstruct moved (addMovedElement) and dead (addDeadPosition) slots with their position timestamps, mirroring JS CRDTArray.deepcopy. Copying only live nodes dropped moved/dead positions, so a move on the clone could anchor after a wrong or GC'd position.
| Commit: | 9e8e138 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.7.5 (#257) * Add Counter dedup mode with HyperLogLog for UV measurement Port the dedup Counter feature: a new HyperLogLog (HLL.swift, xxhash64 — byte-compatible with the Go server) backs a dedup mode on CRDTCounter (Int32-valued, value = HLL cardinality estimate). Adds the public JSONDedupCounter authoring type (wired through JSONObject/JSONArray), IncreaseOperation gains an `actor` field (dedup increases require an actor and are not undoable), and the converter/proto carry the new VALUE_TYPE_INTEGER_DEDUP_CNT, Counter.hll_registers, and Increase.actor. Also folds in "Simplify Counter interface and replace long with bigint" (yorkie-js-sdk#1216): iOS already uses native Int32/Int64 (no Long library), so only the interface shape is mirrored — the bigint change is a no-op here. Skipped upstream yorkie-js-sdk#1218 (default transport gRPC-Web -> Connect): iOS already uses connect-swift's Connect protocol, so there is nothing to switch. Ports yorkie-js-sdk#1215 and #1216. * Enable splitLevel=1 concurrent tests and add split undo/redo Port split undo/redo: a split's reverse is a boundary-deletion that merges the split elements back (TreeEditOperation gains redoSplitLevel + toSplitReverseOperation, so the boundary-deletion's own reverse re-splits). IndexTree.splitElement now excludes removed children from the (single, visible) size, matching the upstream visibleSize fix. Ports yorkie-js-sdk#1219. * Add undo/redo support for TreeStyleOperation CRDTTree.style()/removeStyle() now also return the previous attribute values (and, for style, the keys that were newly added) captured from the first styled node; TreeStyleOperation builds a reverse op from them (combined / removeStyle / style). Callers in JSONTree updated to the new return arity. CRDTTree also carries the #1219 split-loop tweak (findOffset(includeRemoved: true)) since it lives in the same file. Ports yorkie-js-sdk#1221. * test: cover Counter dedup/HLL and Tree split/style undo Add HLL structural tests, CRDT dedup-counter + JSONDedupCounter unit tests, and integration suites for Counter dedup convergence, Tree split-level-1 undo/redo, and TreeStyle undo/redo (incl. overlapping-undo correctness), mirroring the yorkie-js-sdk v0.7.5 test scenarios. Integration cases require a live yorkie server v0.7.5. * chore: bump version to 0.7.5 and update changelog * chore: bump CI yorkie server to v0.7.5 * Fix tree style undo capturing tombstoned attribute values style()/removeStyle() captured previous attributes via getNodeByKey(key)?.value, which returns a tombstoned (removed) attribute's value. After a removeBold, the bold key is tombstoned but was still read as an existing attribute, so the reverse op restored the stale value instead of removing the key — undo/redo of style removal left the attribute in place. Gate the capture on RHT.has(key:) (non-removed) to match yorkie-js-sdk, so a tombstoned attribute counts as absent and the reverse op removes it. * Log instead of silently dropping HLL restore failure on deepcopy deepcopy() used `try? copy.restoreHLL(bytes)`, silently swallowing any error and producing a dedup-counter copy with an empty HLL but the original's timestamps. hllBytes() always yields a valid 16384-byte payload so this cannot happen for valid state, but a future invariant break should be loud: log the error via Logger.error rather than dropping it. (Avoids try!, per repo style.)
| Commit: | 65cd1b8 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.7.4 (#256) * Mirror tree merge snapshot encoding fix from Go SDK Add `merged_from` (field 9, TreeNodeID) and `merged_at` (field 10, TimeTicket) to the TreeNode proto so a node moved by a concurrent merge carries the merge witness and its immutable merge ticket in the snapshot encoding. `merged_at` is stored alongside `merged_from` because the source parent's `removed_at` may be overwritten by later LWW tombstones and cannot serve as the merge-time causal boundary. Regenerated resources.pb.swift via buf (pinned plugins). Ports yorkie-js-sdk#1210. * Fix convergence bugs in concurrent tree merge/split Port the v0.7.4 Tree merge/split convergence fixes. Because the upstream changes are tightly interleaved across tree.ts / index_tree.ts (later fixes build on the mergedFrom/mergedAt fields introduced mid-series), they are ported as one cohesive change rather than reconstructed per-PR: - ticketKnown() causal-visibility helper; toBeMergedNodes gating so elements created by concurrent ops are not merged (Fix 9). - detachChild() to move alive nodes between parents (vs removeChild which now silently skips an already-detached child during GC purge). - mergedFrom/mergedAt/mergedInto on CRDTTreeNode; rebuildMergeState() on snapshot load reconstructs the mergedInto cache from persisted mergedFrom; converter round-trips mergedFrom/mergedAt. - findNodesAndSplitText redirect: insertion into a merge-tombstoned parent forwards to the merge target. - advancePastUnknownSplitSiblings / hasUnknownSplitSibling: edit and style ranges advance past concurrent split siblings; style/removeStyle skip End-token styling when an unknown split sibling extended the range. - edit() cascade-deletes unknown split siblings and propagates deletes to children moved by prior merges; remove() now returns Bool to gate GC pairs; split()/splitElement take a versionVector and keep merge-moved children whose source is local in the left node (Fix 8, via the shouldStayLeftOnSplit IndexTreeNode hook so the generic tree stays free of CRDT types). - traverseInPosRange returns early on an empty (fromIdx > toIdx) range. Ports yorkie-js-sdk#1202, #1203, #1204, #1205, #1206, #1211 (and the #1210 encoding consumed here). Intentional iOS divergences: the write-only `preTombstoned` set from the JS source is omitted (dead code); remove() keeps iOS's existing LWW tombstone direction (only the Bool return is added) — both tracked as follow-ups. * test: cover tree merge/split convergence and mergedFrom/mergedAt encoding Port the v0.7.4 tree convergence tests: converter mergedFrom/mergedAt round-trip and splitText/deepcopy merge-metadata unit tests, plus the concurrent merge/split convergence integration scenarios (overlapping, contained, and side-by-side merges) mirroring yorkie-js-sdk integration/tree_test.ts. Integration cases require a live yorkie server v0.7.4. * chore: bump version to 0.7.4 and update changelog * chore: bump CI yorkie server to v0.7.4 * Fix RichTextEditor undo/redo not re-rendering after revert document.undo()/redo() revert the CRDT correctly, but the editor never repainted from the reverted document: syncTextSnapShot() reused the on-screen attributed string when the cached `content` matched the doc text (so a content revert re-applied the stale string) and early-returned on the didFinishSync guard when the plain string was unchanged (so a style-only revert never rendered). Add a `force` path to syncTextSnapShot() that always rebuilds the attributed string from the document and skips both shortcuts; undo()/redo() now call it with force: true. Normal typing keeps the optimized path. * chore: extract tree edit helpers to satisfy SwiftLint Split the merge-move, cascade-delete-split-sibling, and propagate-deletes-to-merged-children blocks out of edit() into private helpers (applyMergeMoves, collectUnknownSplitSiblings, propagateDeletesToMergedChildren) to bring edit() under the cyclomatic-complexity and function-body-length limits. No behaviour change. * Fix tombstone-inclusive tree traversal for concurrent merge+delete convergence The overlapping-merge-and-delete-element-node convergence case diverged: after a client merged two paragraphs, a concurrent delete of the second paragraph's content from another client was not propagated, leaving the merged-in text alive ("<p>ab</p>" instead of "<p>a</p>"). Root cause: edit()'s range traversal excluded tombstoned nodes, so the merge-source parent (tombstoned by the local merge) was never visited and propagateDeletesToMergedChildren never fired. yorkie-js-sdk threads `includeRemoved=true` from edit() through the range walk; iOS lacked the parameter entirely. Thread `includeRemoved` (default false) through findOffset, tokensBetween, indexOf, addSizeOfLeftSiblings, toTreePos, toIndex and traverseInPosRange, and pass true from edit() only (style/removeStyle keep the visible-only walk, matching JS). iOS keeps a single visible `size`, so the JS totalSize notion is computed on the fly via nodeLength(includeRemoved:)/ paddedLength(includeRemoved:). * test: harden merge-state assertions and document shared-ops convergence test Address PR review feedback: - ConverterTests: assert merge-link identity (movedChild.mergedFrom == sourceParent.id, sourceParent.mergedInto == firstP.id) instead of only non-nil, so the roundtrip regression can't pass with wrong links. - TreeMergeConvergenceTests: document that multi_level_split_with_concurrent_merge_and_text_split intentionally shares its setup/operations with the cascade-delete test (mirroring the upstream pair 1:1) and asserts convergence-only by design.
| Commit: | fe574cc | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.7.1 (#253) * Retain peer presence across watch stream reconnections * Add tree-level schema validation and ProseMirror example Ports the tree-level schema validation from yorkie-js-sdk#1181: a TreeNodeRule (node_type/content/marks/group) in the ruleset proto, a ProseMirror-compatible content-expression parser/matcher, and a tree validator wired into the yorkie.Tree ruleset check. The ProseMirror example and ANTLR schema grammar are JS-only (the iOS SDK consumes pre-parsed rulesets via proto) and are not ported. * test: cover tree-level schema validation and presence retention * chore: bump CI yorkie server to v0.7.1 * chore: bump version to 0.7.1 and update changelog * test: assert CreateSchema status; drop ProseMirror from changelog note
| Commit: | d7c8aff | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | blurfx | |
Apply updates from yorkie-js-sdk 0.6.49 (#251)
| Commit: | 6135265 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.49 (#251) * Add undo/redo support for Text.Style operations * Return unwrapped primitive values from Array iterator * chore: bump version to 0.6.49 and update changelog * test: cover mixed style-undo reverse op and heterogeneous Array elements()
| Commit: | b0f6b85 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.48 (#250) * Replace WatchDocument and WatchChannel with unified Watch RPC Also extracts the shared watch stream lifecycle (yorkie-js-sdk#1160); iOS already centralizes the watch lifecycle in doWatchLoop/runWatchLoop, so no separate helper is introduced. * Remove --backend-disable-webhook-validation from docker-compose * chore: bump version to 0.6.48 and update changelog
| Commit: | 7ae8d0b | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.47 (#249) * Refactor channel terminology from presence to session * Add backend-disable-webhook-validation to CI
| Commit: | 594f0de | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.43 (#247) * Apply updates from yorkie-js-sdk 0.6.43 * Refactor Channel example room management with categories * Bump yorkieVersion to 0.6.43
| Commit: | 6cca038 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.42 (#246)
| Commit: | fcd6ab5 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.41 (#244) * feat: add GetRevision and align revision RPC fields with js-sdk v0.6.41 - Regenerate proto: add GetRevision RPC + GetRevisionRequest/Response - ListRevisionsRequest project_id -> client_id - RestoreRevisionRequest revision_id field 2 -> document_id, revision_id -> field 4 - Add Client.getRevision and YorkieService wrapper - Note RevisionSummary.snapshot is now YSON format * feat: port YSON module from js-sdk v0.6.41 - YSONValue enum + YSONText/YSONTree value types - YSON.parse (regex preprocess + JSONSerialization + postprocess), textToString, treeToXML - Type guards (isText/isTree/isInt/isLong/isDate/isBinData/isCounter/isObject) - 24 unit tests ported from yson_test.ts * refactor: thread OpSource and ExecutionResult through Operation.execute Foundation for undo/redo (js-sdk parity): - Operation.execute now returns ExecutionResult{opInfos, reverseOp?} and takes source: OpSource - Each operation keeps its body as a private executeOpInfos helper; reverseOp is nil for now - Change.execute threads source; Document.applyChanges passes source Behavior-preserving; reverse-op generation lands in follow-up commits. * feat: add CRDTRoot.findElementPairByCreatedAt for undo/redo ancestor walk Phase 4a CRDT primitive. (getPreviousCreatedAt already exists on CRDTArray/RGATreeList; CRDTObject.set executedAt deferred to the reverse-Set path.) * feat: generate reverse operations for undo/redo Phase 4b (js-sdk parity): - Operation.execute returns ExecutionResult? (nil = skip during undo/redo) - Add->Remove, Remove->Add/Set, Move->Move, Set->Set/Remove, ArraySet->ArraySet - Set/Remove ancestor-removed undo-skip; Set undo deregister of tombstoned element - setCreatedAt/setPrevCreatedAt mutators for history reconciliation - reverse ops carry a placeholder executedAt, reassigned at undo time (Phase 5) Behavior-preserving: reverse paths only run when source == .undoRedo (not yet wired). * feat: add History for undo/redo stacks Phase 4c: History class with undo/redo stacks (max depth 50), push/pop/clear, and reconcileCreatedAt (value-type-safe for struct ops). HistoryOperation enum covers operation + presence cases. reconcileTextEdit deferred to the reverse-Edit phase; presence-undo wiring deferred. * feat: wire undo history into the local update path Phase 5a: - Change.execute returns ChangeExecutionResult{operations, opInfos, reverseOps} - Document.update pushes reverseOps to the undo stack, clears redo on new ops, and reconciles ArraySet createdAt in the history - internalHistory/isUpdating state added executeUndoRedo + public undo/redo facade follow in Phase 5b. * feat: implement Document undo/redo execution Phase 5b (js-sdk parity): - Public canUndo/canRedo + undo()/redo(); executeUndoRedo applies reverse ops on a fresh change (source .undoRedo), reassigning executedAt and createdAt for Add/ArraySet, pushing the inverse onto the opposite stack - isUpdating guard; clearHistory on snapshot apply - CRDTElement.setCreatedAt (createdAt now settable; Primitive/Counter let->var) - 5 local undo/redo unit tests Known limitation (XCTExpectFailure + follow-up): undoing an object-property overwrite needs the ElementRHT positionedAt mechanism (newer than the iOS port). * feat: implement reverse EditOperation for Text undo/redo (#1126) Phase 6 (js-sdk parity): - RGATreeSplit.normalizePos/refinePos; edit returns removedValues - CRDTText refinePos/normalizePos/posToIndex; edit returns removedValues - EditOperation is now a class; computes a reverse edit (re-inserts removed content), refines its range on undo replay, and reconciles its range against remote edits (reconcileOperation, 6 cases) - History.reconcileTextEdit; Document.applyChanges reconciles text-edit ranges - 2 text undo/redo unit tests (insert + delete) pass * test: add undo/redo coverage; chore: bump CI server to v0.6.41; docs: changelog Phase 7: - test-writer: array add/remove/move undo/redo, reconcileOperation range-shift, ancestor-removed undo-skip (unit) + a two-client convergence integration test - critic-reviewer fixes: document deliberate ArraySet nil-reverse divergence and the intentional reconcileTextEdit stack-copy - bump CI yorkie server pin + docker-compose images to v0.6.41 - CHANGELOG v0.6.41 entry * feat(example): add Undo/Redo to RichTextEditor; expose Document.clearHistory - ContentViewModel.undo()/redo() re-render from the document snapshot; refresh canUndo/canRedo on every document event; toolbar Undo/Redo buttons - Make Document.clearHistory public and call it after the one-time content scaffolding in initializeClient, so undo can never remove 'content' and break the editor (fixes 'get(key:) content does not exist' after undo) Both the SDK and the RichTextEditor example build (BUILD SUCCEEDED). * fix(example): defer undo/redo state publish; use local server - refreshUndoRedoState() publishes canUndo/canRedo on the next main-actor turn to avoid 'Publishing changes from within view updates' (it is called from the document subscription, which can fire during a SwiftUI view update) - point the RichTextEditor client at the local server (no API key needed) * chore(examples): apply formatter output and build artifact updates SwiftFormat-on-save reformatting (RichTextEditor + Todomvc), Xcode package resolution churn, and build info plist updates. No functional changes. * fix: preserve document order of removed text nodes in reverse edit Pre-PR critic found a convergence bug: RGATreeSplit.deleteNodes returned removed nodes in a Swift Dictionary (unordered), so undoing a multi-node text deletion restored scrambled content and diverged from peers. Return removed nodes in document order instead. - add multi-node delete-undo test asserting exact restored content - add deterministic EditOperation.reconcileOperation 6-case + no-op tests - lint fixes (redundantSelf/hoistTry in YSON, identifier names, orphaned doc comment) * docs: backfill PR link in v0.6.41 changelog entry
| Commit: | f5ae52b | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.40 (#243) * Apply updates from yorkie-js-sdk 0.6.40 Port revision management (yorkie-js-sdk#1127): regenerate protobuf (RevisionSummary field renumber + Project/UpdatableProjectFields auto_revision_enabled), add the RevisionSummary type + converter, and Client.createRevision/listRevisions/restoreRevision with an integration test. * docs: add v0.6.40 changelog entry
| Commit: | bf5f6f8 | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.39 * Apply updates from yorkie-js-sdk 0.6.39 Port additive protocol changes from yorkie-js-sdk v0.6.39: - resources.proto: add ChannelSummary and RevisionSummary messages - yorkie.proto: add CreateRevision/ListRevisions/RestoreRevision RPCs and their request/response messages - Regenerate Swift protobuf/connect sources Pin buf swift plugins (apple/swift v1.37.0, connectrpc/swift v1.0.3) so protobuf generation is reproducible and stays free of unrelated codegen drift (e.g. the nonisolated keyword in newer protoc-gen-swift). * chore: bump CI yorkie server to v0.6.39 * chore: resolve build warnings across SDK and integration tests - Drop spurious 'await' on APIs that became synchronous in the v0.6.39 sync (1348 call sites across the integration suite; TextViewModel example). - CRDTArray.deepcopy: explicit '_ = try?' for the discarded insert result. - ChangeID.setClocks: remove dead 'var vector' clone (value type, never mutated). - JSONArray.moveAfterByIndex: drop no-op @discardableResult (Void return). - TextIntegrationTests: 'var' -> 'let' for never-mutated iterators. * docs: add CHANGELOG.md reconstructed from GitHub releases Reconstructs the project changelog from all 43 GitHub Releases (v0.2.19 -> v0.6.37) in Keep a Changelog format. * chore: satisfy swiftformat redundantSelf in DocumentIntegrationTests The await removal exposed bare property accesses that the project's '--self insert' rule requires to be qualified. Applied swiftformat 0.55.6 (the CI-pinned version): 14 'self.' insertions, no behaviour change. * docs: add v0.6.39 changelog entry
| Commit: | b012eff | |
|---|---|---|
| Author: | longhoang-lewy | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.36 (#240)
| Commit: | ad6444e | |
|---|---|---|
| Author: | Ivan | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.34 (#238) * Sync 0.6.34 * Fix swiftlint * Fix swift format * Remove duplicate files * Fix test * Fix test * Fix test * Fix swiftformat * Fix unit test * Remove unused code
| Commit: | 3b02ee3 | |
|---|---|---|
| Author: | Ivan | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.28 (#236) * Sync 0.6.28 * Update brew 0.6.28
| Commit: | 66bd225 | |
|---|---|---|
| Author: | Ivan | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.23 (#223) * Sync #1030 * Sync #1037 * Import Helpers * Fix review
| Commit: | efc2437 | |
|---|---|---|
| Author: | ivan.do | |
Sync #1038
| Commit: | 344bd57 | |
|---|---|---|
| Author: | Ivan | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.18 (#219) * Sync #1010 * Update Yorkie server 0.6.18 * Resolve dependency * Specific protobuf to 1.31 * Require swift proto buf as 1.31 * Fix swiftformat * Add Yorkie Test Helper * Fix swift review Remove unused file
| Commit: | 8f9d9bb | |
|---|---|---|
| Author: | Ivan | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.13 (#215) * Sync #992 * Sync https://github.com/yorkie-team/yorkie-js-sdk/pull/993 * Fix brew install from local file * Fix swiftlint * fix swiftformat * Fix swiftformat * Fix swiftformat * Remove fatalError * Update Sources/Document/Document.swift Co-authored-by: James (An Nguyen) <nguyenngocan.hcmut@gmail.com> * Fix wrong definition of Function --------- Co-authored-by: James (An Nguyen) <nguyenngocan.hcmut@gmail.com>
| Commit: | 9e50901 | |
|---|---|---|
| Author: | Ivan | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.10 (#213) * TS tree_style_operation.ts packages/sdk/src/document/operation * Sync #969 * Configure yorkie server 0.6.10 * Fix swiftlint * Fix swiftFormat * Fix swiftformat * Fix swiftFormat
| Commit: | 9712ad6 | |
|---|---|---|
| Author: | Ivan | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk 0.6.0 (#206) * Sync #938 * Sync #942 * fix swiftlint * Remove unused code * Update yorkie.pb
| Commit: | 964729c | |
|---|---|---|
| Author: | LOG | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk v0.5.3 (#199) * Add (Pb)VersionVector proto message and matching VersionVector struct * Update converter to use version vector * Update Document to handle version vector in applychangepack * Define version vector and add into change_id, changepack * Update GC in CRDT Root to use min synced version vector * Add YorkieTestHelper target for shared test helpers * Apply missing version vector updates * Apply version vector to tests * Fix: Import YorkieTestHelper only during `swift test` to resolve test command failure
| Commit: | 748134e | |
|---|---|---|
| Author: | LOG | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk v0.5.2 (#197) * Add support for a new operation type ArraySet in the protocol * Bump minimum deployment target to iOS 15 * Update dependency versions * Apply review comments by coderabbitai
| Commit: | 1aa3dd8 | |
|---|---|---|
| Author: | LOG | |
| Committer: | GitHub | |
Apply updates from yorkie-js-sdk v0.4.27 (#192) * Added the generated ErrorInfo model from googleapis/google/rpc/error_details.proto * Add YorkieService wrapper for YorkieServiceClient with API mocking support * Handle retry for syncLoop and watchLoop / Handle ErrClientNotActivated and ErrClientNotFound * Fix watch loop reconnect timer issue in manual syncMode * Fix ensure onlineClients are reset on document detach * Handle local changes correctly when receiving snapshot * Fix Xcode warnings * Apply SwiftFormat * Fix 1 failing test in ClientIntegrationTests * Apply review comments by coderabbitai
| Commit: | 01142d8 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Add RHTNode removal to converter for consistency (#182) * Fix incorrect indexes in TreeChange * Update swift-integration.yml * Update swift-integration.yml * Update swift-integration.yml * Add RHTNode removal to converter for consistency
| Commit: | 4b063d8 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Handle concurrent editing and styling in Tree (#166)
| Commit: | cbde6a0 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Implement Tree.RemoveStyle (#158) * Implement Protocol Changes for Tree.RemoveStyle * merge similar functions
| Commit: | 314b944 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Restore interface changes due to server DB sharding (#148) * Revert "Reflect interface changes of server DB sharding" This reverts commit a56bb269a7039f3d8e15423269bc7a5ae40e1b9f. * update ci docker version
| Commit: | b1d58ea | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Reflect interface changes of server DB sharding (#145) * Reflect interface changes of server DB sharding * fix lint
| Commit: | 6dacc0d | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Implement splitLevel of Tree.Edit (#139) * Refactor Tree codes * correct lint error * Implement splitLevel of Tree.Edit
| Commit: | f15db57 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Support concurrent formatting of Text (#116) * refactor TCs of Text * Support concurrent formatting of Text
| Commit: | 2310f0a | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Change TreeNode to have IDs instead of insPrev, insNext (#103) * Change the value of XXXChange to Change in Document.subscribe * initial commit * add Test codes. * Expose pathToIndex API * Bump up protobuf * change yorkie server to 0.4.5 * Remove duplicated codes * remove redundant codes * Initial commit * Correcting misported code * Add TC for presence * change xcode version to 14.3.1 * Update Tree.edit to allow insertion of multiple contents at once * Replace selection with presence * Rename TextRangeStruct to TextPosStructRange * Clean up methods related to presence * Add presence.get() to get presence value in doc.update() * Change 'Documents' from plural to singular in DocEvent * Cleanup proto * Concurrent case handling for Yorkie.tree * fix tree style TC * Change TreeNode to have IDs instead of insPrev, insNext * correct typo. and remove forced unwrap * apply review comment * apply review comment * correct typo
| Commit: | f200e12 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Concurrent case handling for Yorkie.tree (#102) * Change the value of XXXChange to Change in Document.subscribe * initial commit * add Test codes. * Expose pathToIndex API * Bump up protobuf * change yorkie server to 0.4.5 * Remove duplicated codes * remove redundant codes * Initial commit * Correcting misported code * Add TC for presence * change xcode version to 14.3.1 * Update Tree.edit to allow insertion of multiple contents at once * Replace selection with presence * Rename TextRangeStruct to TextPosStructRange * Clean up methods related to presence * Add presence.get() to get presence value in doc.update() * Change 'Documents' from plural to singular in DocEvent * Cleanup proto * Concurrent case handling for Yorkie.tree * fix tree style TC * correct typo. and remove forced unwrap * apply review comment * apply review comment * correct typo
| Commit: | 87fd0f5 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Cleanup proto (#101) * Change the value of XXXChange to Change in Document.subscribe * initial commit * add Test codes. * Expose pathToIndex API * Bump up protobuf * change yorkie server to 0.4.5 * Remove duplicated codes * remove redundant codes * Initial commit * Correcting misported code * Add TC for presence * change xcode version to 14.3.1 * Update Tree.edit to allow insertion of multiple contents at once * Replace selection with presence * Rename TextRangeStruct to TextPosStructRange * Clean up methods related to presence * Add presence.get() to get presence value in doc.update() * Change 'Documents' from plural to singular in DocEvent * Cleanup proto * apply review comment * apply review comment * correct typo
| Commit: | 8be3818 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Change 'Documents' from plural to singular in DocEvent (#100) * Change the value of XXXChange to Change in Document.subscribe * initial commit * add Test codes. * Expose pathToIndex API * Bump up protobuf * change yorkie server to 0.4.5 * Remove duplicated codes * remove redundant codes * Initial commit * Correcting misported code * Add TC for presence * change xcode version to 14.3.1 * Update Tree.edit to allow insertion of multiple contents at once * Replace selection with presence * Rename TextRangeStruct to TextPosStructRange * Clean up methods related to presence * Add presence.get() to get presence value in doc.update() * Change 'Documents' from plural to singular in DocEvent * apply review comment * apply review comment * correct typo
| Commit: | 35b77a1 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Move Presence from Client to Document (#94) * Change the value of XXXChange to Change in Document.subscribe * initial commit * add Test codes. * Expose pathToIndex API * Bump up protobuf * change yorkie server to 0.4.5 * Remove duplicated codes * remove redundant codes * Initial commit * Correcting misported code * Add TC for presence * change xcode version to 14.3.1
| Commit: | 580bcd9 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Allow multi tree nodes when edit (#93) * Change the value of XXXChange to Change in Document.subscribe * initial commit * add Test codes. * Expose pathToIndex API * Bump up protobuf * change yorkie server to 0.4.5 * Remove duplicated codes * remove redundant codes
| Commit: | 653c066 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Implement yorkie.Tree for text editors using tree model (#90) * Change the value of XXXChange to Change in Document.subscribe * initial commit * add Test codes. * Cleanup of test-related terminology * Expose pathToIndex API * Bump up protobuf * modify action script * correct script * disable todo lint rule * Revert "Expose pathToIndex API" This reverts commit d5a6a0226601ef3dc008939e1c030768c5b9bd0c. * Revert "Bump up protobuf " This reverts commit 89f1ba1360ae9784919647fe113d92264c1d20c1. * set yorkie server version to 0.4.4 * set yorkie server to 0.4.4 * fix sync() concurrency * bump to 0.4.4 * Update swift-integration.yml * Revert "Update swift-integration.yml" This reverts commit 318dc04b03ca6acde97901efca4dcbae9eab1c15. * add more debug info. * Update swift-integration.yml * fix typo * change params to underscore * fix compile error * Revert "Update swift-integration.yml" This reverts commit 318dc04b03ca6acde97901efca4dcbae9eab1c15. * fix misused idString * change the lamport in the TimeTicketStruct to string * cleanup codes * complete == function * change throw exception to assert * remove unnecessary code * change throw exception to precondition * open ElementNode init() * rename JSON nodes * revert edit() parameter to 0.4.4 * remove forced unwrap * fix lint error * fix JSONTree iterration * fix tc validation function * change docker action * Revert "change docker action " This reverts commit f5120da2a6ebfedee179fde3c0a9970917d14d09. * Change JSONTree attributes value type to Any * remove forced unwrap * fix bool converting * Revert "fix bool converting" This reverts commit b761b93ef7c9374654b137600ee0fb7c36e144a9. * remove duplicated codes * Update swift-integration.yml * cleanup ci action * Update swift-integration.yml * Update swift-integration.yml * Fix compile error
| Commit: | 8e84992 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Add Client sync mode (#65) * Add Client sync mode * change syncMode to realtimeSyncMode * Add test codes * fix warning by xcode 14.3 * revert codes for xcode 14.3 * Remove unnecessary checkpoint validation from applyChangePack * Match function names with JS * lint code * Remove pullOnly mode * revert a unnessary change * Revert codes position to diff changes of codes clearly * Revert codes * Add comments for SyncMode * Refactor the codes related to getPeerPresence (#77) * add test code for pauseRemoteChanges and resumeRemoteChanges * remove waits in TC * revert waits in TC
| Commit: | b5ec0be | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Add RemoveDocument API (#64) * Add RemoveDocument API * Elaborate on the value of the PeersChanged event * Add TC for the stream connection status change event * Fix the watchLoop start and stop for pause, resume * Removed the codes for changing the status of the doc, before sending the API * Invalidate watchLoop reconnection timer when disconnecting stream * Throws an error when server returns removed doc for the attached result
| Commit: | 4387216 | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Remove duplicated key from TextNodeAttr (#52)
| Commit: | c865d0a | |
|---|---|---|
| Author: | Jung gyun Ahn | |
| Committer: | GitHub | |
Implement Text Element (#50) * Implements RGATreeSplit * Add throw noSuchElement error for failed forced unwrap
| Commit: | e48397b | |
|---|---|---|
| Author: | 원형식 | |
| Committer: | GitHub | |
Initial project setup (#2) Project structure based on a default swift package project and yorkie-js-sdk. - grpc-swift - swift-protobuf - SwiftLint - SwiftFormat