Skip to content

Conversation

@lawrence-forooghian
Copy link
Collaborator

@lawrence-forooghian lawrence-forooghian commented Dec 1, 2025

This was skipped in dcdb350 due to time constraints. Resolves #50.

The ably-cocoa changes are in ably/ably-cocoa#2149.

Summary by CodeRabbit

  • New Features

    • Objects (maps, counters) now use server time for creation timestamps for consistent cross-client timestamps.
    • Added async server-time fetch capability used during object creation.
  • Tests

    • Test suites updated to assert against server-provided timestamps.
  • Chores

    • Updated dependency pins and package resolution metadata.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

Walkthrough

Adds a server-time fetch API to CoreSDK and switches map/counter creation to use server-provided timestamps; adjusts synchronization points accordingly; updates package pins and related tests/mocks to assert server-time-based IDs.

Changes

Cohort / File(s) Summary
Package Dependencies
AblyLiveObjects.xcworkspace/xcshareddata/swiftpm/Package.resolved, Package.resolved, Package.swift
Updated originHash entries; switched ably-cocoa and ably-cocoa-plugin-support from version pins to explicit revisions; updated delta-codec-cocoa revision and bumped version 1.3.3 → 1.3.5
CoreSDK Server Time API
Sources/AblyLiveObjects/Internal/CoreSDK.swift
Added fetchServerTime() async throws(ARTErrorInfo) -> Date to CoreSDK and implemented it in DefaultCoreSDK, bridging plugin callback into Swift concurrency using withCheckedContinuation and mapping errors to ARTErrorInfo
Object Creation Timestamp Migration
Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift
createMap and createCounter now validate channel state inside an initial sync, then call coreSDK.fetchServerTime() outside that sync, and construct/store the creation operation inside a subsequent synchronized block using the fetched server time
Tests & Mocks
Tests/AblyLiveObjectsTests/InternalDefaultRealtimeObjectsTests.swift, Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift
MockCoreSDK gained a serverTime property, initializer parameter, and fetchServerTime() implementation; tests updated to use serverTime-based expectations for object ID timestamps and removed reliance on a local mock clock

Sequence Diagram(s)

sequenceDiagram
    rect rgba(220,235,255,0.6)
    participant Caller as createMap/createCounter (caller)
    participant InternalQ1 as InternalDefaultRealtimeObjects\n(sync block 1)
    end
    participant CoreSDK as CoreSDK (async)
    participant Plugin as AblyPlugin (internal)
    rect rgba(220,235,255,0.6)
    participant InternalQ2 as InternalDefaultRealtimeObjects\n(sync block 2)
    participant Ops as Operation store
    end

    Caller->>InternalQ1: Enter sync — validate channel state
    InternalQ1-->>Caller: Exit sync
    Caller->>CoreSDK: await fetchServerTime()
    CoreSDK->>Plugin: nosync_fetchServerTime(for:) via continuation
    Plugin-->>CoreSDK: return server time / error
    CoreSDK-->>Caller: resume with Date or throw ARTErrorInfo
    Caller->>InternalQ2: Enter sync — build creationOperation(timestamp: serverTime)
    InternalQ2->>Ops: store creation operation
    InternalQ2-->>Caller: Complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review withCheckedContinuation usage and error mapping in DefaultCoreSDK.fetchServerTime()
  • Verify synchronization separation (validate then fetch then create) preserves thread safety and avoids races in InternalDefaultRealtimeObjects
  • Check all places that formerly used clock.now for creation timestamps are updated or intentionally unchanged
  • Inspect tests and MockCoreSDK to ensure serverTime behavior matches production semantics
  • Confirm package revision pin changes don't require code changes elsewhere

Poem

🐰 A tiny hop to fetch the time,

From distant server, crisp and prime.
Each object born with truthful beat,
No local tick can dare compete.
Hooray — our IDs now hop in time! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: implementing server time usage for object ID generation per specification requirements.
Linked Issues check ✅ Passed The pull request successfully implements RTO11f5 and RTO12f5 requirements from issue #50 by adding fetchServerTime() to CoreSDK and updating both map and counter creation to use server time instead of local clock.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing server time for object ID creation. Dependency updates (Package.resolved, Package.swift) support the ably-cocoa integration needed for this feature.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 50-use-server-time-for-object-ID

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot temporarily deployed to staging/pull/98/AblyLiveObjects December 1, 2025 17:47 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
Tests/AblyLiveObjectsTests/InternalDefaultRealtimeObjectsTests.swift (1)

11-22: Consider whether the clock parameter is still needed in the test helper.

The createDefaultRealtimeObjects helper still accepts a clock parameter (defaulting to MockSimpleClock()), but the tests validating timestamp behavior now use serverTime from MockCoreSDK instead. If the clock parameter is no longer used for timestamp generation in object creation, consider whether it's still needed here or should be documented for its remaining purpose (e.g., garbage collection timing).

Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift (1)

183-189: The second withSync block for creationOperation may be unnecessary.

The creationOperation computation at lines 185-188 doesn't appear to read or write any mutable state from mutableStateMutex — it only uses the entries and timestamp parameters. The sync block seems to be present only for consistency with the previous structure.

Consider whether this could be simplified to compute creationOperation outside the mutex:

-        let creationOperation = try mutableStateMutex.withSync { _ throws(ARTErrorInfo) in
-            // RTO11f
-            ObjectCreationHelpers.nosync_creationOperationForLiveMap(
-                entries: entries,
-                timestamp: timestamp,
-            )
-        }
+        // RTO11f
+        let creationOperation = ObjectCreationHelpers.nosync_creationOperationForLiveMap(
+            entries: entries,
+            timestamp: timestamp,
+        )

The try keyword also appears unnecessary since the closure doesn't actually throw.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f928c7e and 81057cb.

📒 Files selected for processing (7)
  • AblyLiveObjects.xcworkspace/xcshareddata/swiftpm/Package.resolved (1 hunks)
  • Package.resolved (1 hunks)
  • Package.swift (1 hunks)
  • Sources/AblyLiveObjects/Internal/CoreSDK.swift (2 hunks)
  • Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift (2 hunks)
  • Tests/AblyLiveObjectsTests/InternalDefaultRealtimeObjectsTests.swift (4 hunks)
  • Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/swift.mdc)

**/*.swift: Specify an explicit access control level (SwiftLint explicit_acl) for all declarations in Swift code (tests are exempt)
When extending a type, put the access level on the extension declaration rather than on each member (tests are exempt)
Prefer implicit .init(...) when the type can be inferred in initializer expressions
Prefer enum case shorthand (.caseName) when the type can be inferred
For JSONValue or WireValue, prefer using literal syntax via ExpressibleBy*Literal where possible
Prefer Swift raw string literals for JSON strings instead of escaping double quotes
When an array literal begins with an initializer expression, place the initializer on the line after the opening bracket

Files:

  • Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift
  • Sources/AblyLiveObjects/Internal/CoreSDK.swift
  • Tests/AblyLiveObjectsTests/InternalDefaultRealtimeObjectsTests.swift
  • Package.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift
Tests/**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

Tests/**/*.swift: Use the Swift Testing framework (import Testing), not XCTest, in test files
Do not use fatalError for expectation failures; prefer Swift Testing’s #require
Only add labels to test cases or suites when the label differs from the suite struct or test method name
Tag tests per CONTRIBUTING.md’s "Attributing tests to a spec point" with exact comment format; distinguish @spec vs @specPartial; do not repeat @spec for the same spec point
Add comments in tests to clarify when certain test data is irrelevant to the scenario
In tests, import Ably using import Ably
In tests, import AblyLiveObjects using @testable import AblyLiveObjects
In tests, import _AblyPluginSupportPrivate using import _AblyPluginSupportPrivate (do not use internal import)
When passing a logger to internal components in tests, use TestLogger()
When unwrapping optionals in tests, prefer #require over guard let

Files:

  • Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift
  • Tests/AblyLiveObjectsTests/InternalDefaultRealtimeObjectsTests.swift
Sources/AblyLiveObjects/**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/swift.mdc)

In AblyLiveObjects library (non-test) code, import modules as: Ably with import Ably, and _AblyPluginSupportPrivate with internal import _AblyPluginSupportPrivate

Files:

  • Sources/AblyLiveObjects/Internal/CoreSDK.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift
Sources/**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

For testsOnly_ property declarations, do not add generic explanatory comments (their meaning is understood)

Files:

  • Sources/AblyLiveObjects/Internal/CoreSDK.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift
🧬 Code graph analysis (3)
Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift (1)
Sources/AblyLiveObjects/Internal/CoreSDK.swift (1)
  • fetchServerTime (87-107)
Sources/AblyLiveObjects/Internal/CoreSDK.swift (2)
Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift (1)
  • fetchServerTime (41-43)
Sources/AblyLiveObjects/Utility/MarkerProtocolHelpers.swift (1)
  • castPluginPublicErrorInfo (51-53)
Tests/AblyLiveObjectsTests/InternalDefaultRealtimeObjectsTests.swift (1)
Tests/AblyLiveObjectsTests/Helpers/TestFactories.swift (1)
  • createInternalQueue (688-691)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Example app, tvOS (Xcode 16.3)
  • GitHub Check: Example app, macOS (Xcode 16.3)
  • GitHub Check: Xcode, macOS (Xcode 16.3)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 16.3)
  • GitHub Check: Example app, iOS (Xcode 16.3)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 16.3)
  • GitHub Check: Xcode, tvOS (Xcode 16.3)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 16.3)
  • GitHub Check: Xcode, iOS (Xcode 16.3)
  • GitHub Check: SPM, release configuration (Xcode 16.3)
  • GitHub Check: SPM (Xcode 16.3)
  • GitHub Check: check-documentation
  • GitHub Check: Generate code coverage
🔇 Additional comments (10)
Package.resolved (1)

1-94: Lock file updates are consistent with Package.swift changes.

The resolved revisions match the explicit revision pins added in Package.swift for ably-cocoa and ably-cocoa-plugin-support. The delta-codec-cocoa version bump to 1.3.5 appears to be a transitive dependency update.

AblyLiveObjects.xcworkspace/xcshareddata/swiftpm/Package.resolved (1)

1-94: Workspace lock file is consistent with root Package.resolved.

Package.swift (1)

23-24: Revision pins with TODO comments are appropriate for pre-release development.

The explicit revision pins for ably-cocoa and ably-cocoa-plugin-support allow depending on the new fetchServerTime() API before it's released. The TODO comments appropriately flag these for unpinning before the next release.

Consider creating a tracking issue to ensure these pins are reverted before release, or ensure this is captured in your release checklist.

Also applies to: 29-30

Tests/AblyLiveObjectsTests/InternalDefaultRealtimeObjectsTests.swift (2)

1205-1247: Test correctly validates server time usage in map creation.

The test now uses MockCoreSDK with a fixed serverTime and verifies the object ID contains the expected timestamp in milliseconds. This properly tests RTO11f7.


1338-1373: Test correctly validates server time usage in counter creation.

Consistent with the map creation test, this validates RTO12f5 by checking the counter object ID contains the server timestamp in milliseconds.

Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift (1)

11-16: Mock implementation correctly provides fetchServerTime() for testing.

The serverTime property with a default value and the synchronous return in fetchServerTime() is appropriate for mock testing. This aligns with the CoreSDK protocol extension shown in the relevant code snippets.

Also applies to: 40-43

Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift (2)

174-204: Good refactoring to support async server time fetch.

The approach of validating channel state first, then fetching server time outside the mutex (since it's async), then computing the creation operation is correct. The mutex is properly released during the async fetchServerTime() call.

Note: There's a potential time-of-check-to-time-of-use (TOCTOU) gap where the channel state could change between the initial validation (line 177) and the subsequent operations. However, this is acceptable because:

  1. The publish call will fail with an appropriate error if the channel becomes invalid
  2. This is a standard optimistic validation pattern for async operations

224-230: Server time integration for counter creation looks correct.

The pattern matches the map creation flow: fetch server time, then use it for the creation operation. This correctly implements RTO12f5.

Sources/AblyLiveObjects/Internal/CoreSDK.swift (2)

11-12: LGTM!

The protocol method declaration is clean, follows the established typed throws pattern, and the documentation appropriately references the RTO16 spec.


87-107: LGTM!

The implementation correctly bridges the callback-based nosync_fetchServerTime to async/await using withCheckedContinuation with a Result type, then uses .get() to convert to typed throws. Good practices observed:

  • Dispatches to internalQueue before calling the nosync_ method, maintaining the queue contract
  • Defensive dispatchPrecondition to verify callback execution context
  • Clean capture list [client, pluginAPI] avoiding unnecessary self capture
  • Proper error conversion using ARTErrorInfo.castPluginPublicErrorInfo
  • Clear precondition message for the invariant that both serverTime and error cannot be nil

@lawrence-forooghian lawrence-forooghian force-pushed the 50-use-server-time-for-object-ID branch from 81057cb to 1d08044 Compare December 1, 2025 17:52
@github-actions github-actions bot temporarily deployed to staging/pull/98/AblyLiveObjects December 1, 2025 17:55 Inactive
This was skipped in dcdb350 due to time constraints. Resolves #50.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift (1)

183-189: Consider removing the unnecessary synchronization block.

The nosync_creationOperationForLiveMap helper is a static function that doesn't access shared mutable state (confirmed by the nosync_ prefix and the fact that the lambda ignores the mutableState parameter with _). Wrapping this call in withSync adds unnecessary lock overhead.

Apply this diff to simplify:

-        let creationOperation = mutableStateMutex.withSync { _ in
-            // RTO11f
-            ObjectCreationHelpers.nosync_creationOperationForLiveMap(
-                entries: entries,
-                timestamp: timestamp,
-            )
-        }
+        // RTO11f
+        let creationOperation = ObjectCreationHelpers.nosync_creationOperationForLiveMap(
+            entries: entries,
+            timestamp: timestamp,
+        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 81057cb and e3940fa.

📒 Files selected for processing (7)
  • AblyLiveObjects.xcworkspace/xcshareddata/swiftpm/Package.resolved (1 hunks)
  • Package.resolved (1 hunks)
  • Package.swift (1 hunks)
  • Sources/AblyLiveObjects/Internal/CoreSDK.swift (2 hunks)
  • Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift (2 hunks)
  • Tests/AblyLiveObjectsTests/InternalDefaultRealtimeObjectsTests.swift (4 hunks)
  • Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • Tests/AblyLiveObjectsTests/InternalDefaultRealtimeObjectsTests.swift
  • AblyLiveObjects.xcworkspace/xcshareddata/swiftpm/Package.resolved
  • Package.swift
  • Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift
🧰 Additional context used
📓 Path-based instructions (3)
**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/swift.mdc)

**/*.swift: Specify an explicit access control level (SwiftLint explicit_acl) for all declarations in Swift code (tests are exempt)
When extending a type, put the access level on the extension declaration rather than on each member (tests are exempt)
Prefer implicit .init(...) when the type can be inferred in initializer expressions
Prefer enum case shorthand (.caseName) when the type can be inferred
For JSONValue or WireValue, prefer using literal syntax via ExpressibleBy*Literal where possible
Prefer Swift raw string literals for JSON strings instead of escaping double quotes
When an array literal begins with an initializer expression, place the initializer on the line after the opening bracket

Files:

  • Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift
  • Sources/AblyLiveObjects/Internal/CoreSDK.swift
Sources/AblyLiveObjects/**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/swift.mdc)

In AblyLiveObjects library (non-test) code, import modules as: Ably with import Ably, and _AblyPluginSupportPrivate with internal import _AblyPluginSupportPrivate

Files:

  • Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift
  • Sources/AblyLiveObjects/Internal/CoreSDK.swift
Sources/**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

For testsOnly_ property declarations, do not add generic explanatory comments (their meaning is understood)

Files:

  • Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift
  • Sources/AblyLiveObjects/Internal/CoreSDK.swift
🧬 Code graph analysis (1)
Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift (4)
Sources/AblyLiveObjects/Utility/DispatchQueueMutex.swift (1)
  • withSync (25-35)
Sources/AblyLiveObjects/Internal/CoreSDK.swift (2)
  • nosync_validateChannelState (124-136)
  • fetchServerTime (87-107)
Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift (1)
  • fetchServerTime (41-43)
Sources/AblyLiveObjects/Internal/ObjectCreationHelpers.swift (1)
  • nosync_creationOperationForLiveMap (103-160)
🔇 Additional comments (6)
Package.resolved (2)

2-2: Origin hash change looks consistent with dependency updates

The updated "originHash" is expected whenever the dependency graph (pins) changes; nothing to fix here.


5-28: Confirm new dependency pins (SHAs and version) are intentional and aligned with server‑time work

The new pins for:

  • ably-cocoa → revision 23954b55bea7fa24beaa6e43beb580d501e6b6e1
  • ably-cocoa-plugin-support → revision 37ad19df2cc6063c74ec88ecefc2ddf491db5ebf

are correctly matched in Package.swift and in sync with Package.resolved. Both include TODO comments confirming these are intentional temporary pins to be unpinned before the next release.

Note: delta-codec-cocoa 1.3.5 in Package.resolved is a transitive dependency (pulled through ably-cocoa) and does not require an entry in Package.swift—this is standard Swift Package Manager behavior.

Sources/AblyLiveObjects/Internal/CoreSDK.swift (2)

11-12: LGTM!

The protocol method declaration is well-documented and properly typed. The addition of server time retrieval capability aligns with the PR objectives to implement RTO16.


87-107: LGTM!

The implementation correctly bridges the callback-based nosync_fetchServerTime API to Swift's async/await. The defensive precondition checks on line 94 and lines 99-101 ensure the contract with pluginAPI is upheld, and the error mapping to ARTErrorInfo is consistent with the existing codebase.

Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift (2)

224-230: LGTM!

The shift to server time for counter creation correctly implements RTO12f5. The implementation is clean and doesn't introduce unnecessary synchronization overhead.

Note: The same channel state validation timing concern mentioned for createMap applies here as well.


175-178: Channel state could change between validation and publish.

The validation at line 177 occurs while the lock is held, but the lock is released before fetchServerTime() at line 181. During this async operation, the channel could transition to DETACHED, FAILED, or SUSPENDED. The publish() call at line 192 does not validate channel state before sending, so a message could be published even after the channel has entered an invalid state.

Consider re-validating the channel state immediately before publish(), or holding the lock through the entire sequence if feasible per the RTO11 specification requirements.

Copy link
Collaborator

@maratal maratal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@lawrence-forooghian lawrence-forooghian merged commit f35f293 into main Dec 2, 2025
18 checks passed
@lawrence-forooghian lawrence-forooghian deleted the 50-use-server-time-for-object-ID branch December 2, 2025 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Use server time when creating objectID

3 participants