Skip to content

[WIP] Prototype pulling LiveObjects plugin into this repo#2205

Draft
lawrence-forooghian wants to merge 304 commits into
mainfrom
liveobjects-into-cocoa
Draft

[WIP] Prototype pulling LiveObjects plugin into this repo#2205
lawrence-forooghian wants to merge 304 commits into
mainfrom
liveobjects-into-cocoa

Conversation

@lawrence-forooghian
Copy link
Copy Markdown
Collaborator

No description provided.

lawrence-forooghian and others added 30 commits July 23, 2025 11:06
We adopt a pattern whereby the LiveObjects SDK functions correctly as
long as the user is holding a reference to any object vended by the
public API of the SDK. We do this by copying the ably-cocoa approach of
having separate public and internal versions of objects.

All written by me, except for getting Cursor to help with updating the
tests in response to the new way of injecting CoreSDK.

Resolves #9.
It's a bit annoying that Xcode's autocomplete didn't do this when I
created these implementations.
Didn't do this in ce8c022 because I didn't have a good idea of our
threading approach. But for the initial approach that we'll be taking —
namely, calling the callbacks on ably-cocoa's callback queue — I think
we'll need it.

Haven't done it for batch stuff yet because I don't yet know whether
it'll be necessary (will get a better idea when we implement it).

Now that the callback can be called on any thread, we can no longer
easily use the "capture the return value of `subscribe` pattern so that
we can unsubscribe later" pattern. So, I've decided to pass the
subscripiton as a second argument to the callback. Might be there's a
better pattern we can use (e.g. pass in an object to the `subscribe`
call, like JS `fetch`'s AbortController), but this'll do for now.
The correct behaviour wasn't clear from the spec when I wrote cb427d8,
but new spec PR [1] makes it seem that this is the right thing to do
(still needs clarifying though).

[1] ably/specification#346
This is preparation for implementing subscriptions. Cursor updated the
tests.
Motivation as in 3f6de86; the new spec points in [1] tell us these can
throw.

[1] ably/specification#346
[ECO-5328] Further initial setup of plugin
[ECO-5329] Implement the protocol-level interactions with ably-cocoa
[ECO-5390] Encode and decode binary and JSON data per spec
[ECO-5416, ECO-5417] Rename some things to match spec
[ECO-5332] Implement `OBJECT_SYNC`
[ECO-5330] Implement remaining `LiveMap` access API
[ECO-5333] Apply `OBJECT` `ProtocolMessage` operations
[ECO-5334] Buffer `OBJECT` ProtocolMessage operations during a sync
[ECO-5384] Implement memory management pattern similar to ably-cocoa
Based on [1] at 2963300.

Have not implemented RTL04b1's channel mode checking for same reason as
mentioned in 8d881e2.

Have not currently tested `replaceData`'s return value; will do once [2]
clarified.

[1] ably/specification#346
[2] https://github.com/ably/specification/pull/346/files#r2201363446
This is preparation for adding additional fields (e.g. tombstonedAt) per
RTLM3a in [1].

[1] ably/specification#350
We'll use this when setting the upcoming tombstonedAt value for objects
and map entries.

This was all generated by Cursor; my only change was to add some locking
into the mock class.
Per [1] at 488e932. Preparation for implementing this tombstoning spec.

[1] ably/specification#350
Preparation for adding the `tombstone` method from [1].

(This approach is a _bit_ weird but it's what I could think of that's
compatible with the existing LiveObjectMutableState approach.)

[1] ably/specification#350
There isn't a spec PR for this yet, but it's needed in order to
implement the write spec [1]. Asked about it in [2]. It's implemented in
JS in [3].

Got Cursor to do this.

[1] ably/specification#353
[2] https://github.com/ably/specification/pull/353/files#r2228017382
[3] ably/ably-js#2065
This will be needed for the write API (to extract object IDs from the
entries that the user supplies when creating a LiveMap).
This will be needed for the write API (to incorporate the values that
the user supplies when creating a LiveObject).
This will be needed for the write API (to extract object IDs from the
entries that the user supplies when creating a LiveMap).
Generated by Cursor at my request. Useful for tests. Will refine this
(e.g. to hide the usage of AsyncStream, and maybe tweak the name) in #4.
lawrence-forooghian and others added 22 commits March 16, 2026 09:56
Merge `integration/protocol-v6` into `main`
Since we're doing a v2 release of plugin-support, the @optional
workarounds introduced in 8bfbbaa are no longer needed. Make all methods
required, convert the siteCode getter method back to a property, and
fold the completionWithResult: variant back into the original
completion: method.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merge `integration/protocol-v6` into `main`
Remove backwards-compatibility scaffolding from apply-on-ACK APIs
Since plugin-support v2 makes all previously @optional methods
required, the backwards-compatibility scaffolding introduced alongside
them is no longer needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prepares this repo to be merged into ably-cocoa under a sub-directory
of that name. Done as a single move commit so that the subsequent
merge into ably-cocoa is conflict-free at every path (no top-level
collisions with ably-cocoa's .gitignore, COPYRIGHT, Package.swift,
README.md, or LICENSE), while every pre-move commit on this branch
retains its original SHA. Cross-references to those SHAs from
elsewhere (PR descriptions, commit messages) continue to resolve.

Original file history is reachable via `git log --follow` on the new
paths, or by walking from the plugin-support-pre-merge tag in
ably-cocoa.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prepares this repo to be merged into ably-cocoa under a sub-directory
of that name. Done as a single move commit so that the subsequent
merge into ably-cocoa is conflict-free at every path, while every
pre-move commit on this branch retains its original SHA.
Cross-references to those SHAs from elsewhere (PR descriptions,
commit messages) continue to resolve.

Note: the ably-common submodule's registration moves with the rest
of the repo to merged-repos/.../.gitmodules. Git only consults a
.gitmodules file at the repo root, so submodule operations against
this branch will fail until the submodule is re-registered at the
new path in ably-cocoa's root .gitmodules — to be done as a
follow-up commit after the merge.

Original file history is reachable via `git log --follow` on the
new paths, or by walking from the liveobjects-plugin-pre-merge tag
in ably-cocoa.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings every commit from the ably-cocoa-plugin-support repository
into ably-cocoa's history, with the entire repo contents living
under merged-repos/ably-cocoa-plugin-support/. The relocation
happened in the move commit fd04eab on the plugin-support side, so
this merge introduces zero path collisions and zero conflicts.

Every pre-move commit retains its original SHA. The pre-merge tip
is preserved as the tag 'plugin-support-pre-merge' for queries that
want to walk the original repo's main branch from the original tip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings every commit from the ably-liveobjects-swift-plugin repository
into ably-cocoa's history, with the entire repo contents living
under merged-repos/ably-liveobjects-swift-plugin/. The relocation
happened in the move commit 758242c on the plugin side, so this
merge introduces zero path collisions and zero conflicts.

Every pre-move commit retains its original SHA. The pre-merge tip
is preserved as the tag 'liveobjects-plugin-pre-merge'.

Known follow-ups required before the consolidated tree is functional:

- The ably-common submodule that the plugin used for its tests is
  now registered only in merged-repos/.../.gitmodules. Git only
  consults the .gitmodules at the repo root, so the submodule needs
  to be re-registered in ably-cocoa's root .gitmodules.

- The merged tree contains two Package.swift files (ably-cocoa's at
  root, plus the plugin's at merged-repos/.../Package.swift). The
  inner one is no longer used by SPM but is misleading; it should
  either be deleted or stubbed when the plugin's targets are folded
  into the root Package.swift.

- The plugin's .github/workflows/check.yaml lands under
  merged-repos/... and will silently not run; CI needs unified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every top-level type, extension, and free function in AblyLiveObjects
gains an @available(macOS 10.15, iOS 13, tvOS 13, *) attribute.

In the standalone ably-liveobjects-swift-plugin package the floor
was macOS 11 / iOS 14 / tvOS 14, so these annotations were
unnecessary. Once the target is folded into ably-cocoa's package,
which declares macOS 10.11 / iOS 9 / tvOS 10, the Swift compiler
needs an explicit @available on any declaration that uses a
post-floor API (Swift Concurrency runtime, CryptoKit, Scanner's
modern API, etc.). The chosen floor (10.15 / 13) matches what the
APIs in question actually require — it is the lowest floor at
which AblyLiveObjects can compile, and is one major version lower
than the standalone plugin's previous floor.

Generated mechanically by prepending @available to every line
matching `(access-modifier)? (final )? (class|struct|enum|protocol
|actor|extension|typealias|func)` at file scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps swift-tools-version from 5.3 to 6.1 (matches the version the
former ably-liveobjects-swift-plugin Package.swift required) and
restructures the manifest to fold both ex-external-packages in as
internal targets:

- Drops the package-level dependency on ably-cocoa-plugin-support.
- Declares _AblyPluginSupportPrivate as an internal target whose
  path points into merged-repos/ably-cocoa-plugin-support/Sources/.
  The Ably target and the AblyTests test target now reference it by
  name rather than as a cross-package product.
- Declares AblyLiveObjects as an internal Swift target whose path
  points into merged-repos/ably-liveobjects-swift-plugin/Sources/,
  with dependencies on the in-package Ably and _AblyPluginSupportPrivate
  targets. Exposes it as a separate .library product so consumers
  opt in to LiveObjects independently of Ably.

What is NOT done in this commit, on purpose:

- The former plugin's tests, BuildTool, Example app, and CI workflow
  remain in-tree at merged-repos/ but are not declared as targets.
- The former plugin's transitive dependencies (swift-async-algorithms,
  swift-argument-parser, Table, swift-docc-plugin) are not declared
  here, since the AblyLiveObjects library target doesn't use them.
- The package-wide `platforms:` floor stays at macOS 10.11 / iOS 9
  / tvOS 10. AblyLiveObjects's higher API requirements are gated
  by @available in the previous commit, not by raising the floor.

Verified: `swift build` and `swift build --build-tests` both
succeed (warnings from pre-existing Swift 6 strict-concurrency
checks remain).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the gitlink at
merged-repos/ably-liveobjects-swift-plugin/Tests/AblyLiveObjectsTests/ably-common
with a symlink to ably-cocoa's existing submodule at
Test/AblyTests/ably-common.

The plugin's pinned ably-common SHA (60fd9cf, Nov 2024) was an
ancestor of ably-cocoa's pinned SHA (783496f, Sep 2025), so the
ably-cocoa pin is strictly more recent. The plugin's tests will see
the same test fixtures they had before, plus whatever was added in
the ten months between the two pins.

With this change there is a single ably-common submodule registered
in the repo (still at the same root .gitmodules entry that ably-cocoa
already had), instead of needing a second registration at the plugin
test location. SPM's resources(.copy) follows the symlink at build
time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same mechanical sweep as 45af634 applied to the test sources, with
one adjustment: @available is intentionally omitted from
declarations that are decorated with Swift Testing's @suite or @test
attributes. The macro expansion conflicts with an adjacent explicit
@available ("Attribute 'Suite' cannot be applied to this structure
because it has been marked '@available …'"), so we rely on the
macros' own availability handling for those declarations.

In practice this is just the @Suite-decorated
ObjectsIntegrationTests struct; the @Test-decorated free functions
weren't matched by the sweep regex anyway because the @test
attribute sits on its own line above them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a .testTarget for the plugin's existing test suite at
merged-repos/ably-liveobjects-swift-plugin/Tests/AblyLiveObjectsTests/,
with dependencies on AblyLiveObjects, Ably, and
_AblyPluginSupportPrivate. Excludes the dotfile configs and
CLAUDE.md; copies the ably-common directory (now a symlink to
ably-cocoa's existing submodule) as a test resource.

Sets swiftLanguageMode(.v5) on the pre-existing AblyTests and
AblyTesting targets. Both contain code written under Swift 5
semantics: under Swift 6 language mode (the default once
swift-tools-version is bumped to 6.1) the existing strict-
concurrency warnings get promoted to errors. Pinning these two
targets to .v5 preserves their previous behaviour while the
new AblyLiveObjects/AblyLiveObjectsTests targets continue to
build in Swift 6 mode, where they were written.

Verified: `swift build --build-tests` clean from a fresh .build,
and a smoke run of `swift test --filter InternalDefaultLiveCounterTests`
passes (26 tests in 12 suites).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an .executableTarget for the developer/CI tool from the former
plugin package, pointing at
merged-repos/ably-liveobjects-swift-plugin/Sources/BuildTool/. Adds
its three runtime dependencies at package level:

  - swift-argument-parser (for CLI option parsing)
  - swift-async-algorithms (used in process orchestration)
  - Table (terminal-table formatting)

These are scoped to BuildTool only — they are not declared as
target dependencies of the Ably or AblyLiveObjects library
products, so they do not enter library consumers' build graphs.
They are still added to Package.resolved, which means consumers
fetch them at resolution time even though nothing they use
compiles against them.

`swift run BuildTool --help` works and lists all the previous
subcommands.

Known gap: BuildTool's `test-library`, `build-library`,
`build-example-app`, and `build-documentation` subcommands all
invoke xcodebuild with `-scheme AblyLiveObjects` and (for tests)
`-testPlan UnitTests`. Those scheme/test-plan names exist only in
the standalone plugin's Xcode workspace, not in ably-cocoa's
Ably.xcodeproj. Wiring them up needs either an Xcode scheme
addition (out of scope here) or a BuildTool refactor that calls
`swift test` directly. Until then, the substitute for
`swift run BuildTool test-library --platform macOS --only-unit-tests`
is `swift test --filter AblyLiveObjectsTests --skip ObjectsIntegrationTests`,
which passes 277 tests across 87 suites in this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Translates the SPM-only subset of the former plugin repo's
check.yaml into a new workflow at
.github/workflows/check-liveobjects.yaml at the actual root, where
GitHub Actions can see it. Two jobs:

  - build-and-test-spm: builds the AblyLiveObjects target and runs
    the unit tests via `swift test --filter AblyLiveObjectsTests
    --skip ObjectsIntegrationTests`.
  - build-release-configuration-spm: builds AblyLiveObjects with
    --configuration release.

Both invocations were verified locally in this branch.

Also removes the original check.yaml from
merged-repos/ably-liveobjects-swift-plugin/.github/workflows/,
which lived under merged-repos/ and could never run (GitHub Actions
only reads root-level .github/workflows/). Its full original
contents remain reachable via the liveobjects-plugin-pre-merge tag.

What is NOT yet ported, with the reason in each case:

  - lint: requires `swift run BuildTool lint`, which shells out to
    `mint run swiftformat`, `mint run swiftlint`, and
    `npm run prettier:check` from CWD. Running it from this repo's
    root would walk into ably-cocoa's pure-ObjC source and fail.
    Either BuildTool needs scoping to merged-repos/, or the lint
    command needs to cd in. Out of scope here.

  - generate-matrices: outputs an Xcode-version matrix for the
    Xcode-based jobs below. Not useful until those jobs work.

  - build-and-test-xcode, build-release-configuration-xcode,
    code-coverage, check-example-app: all route through
    `swift run BuildTool {build-library, test-library, …}` which
    invokes `xcodebuild -scheme AblyLiveObjects -testPlan UnitTests`.
    AblyLiveObjects has no Xcode scheme in ably-cocoa's
    Ably.xcodeproj, and the test plans (UnitTests.xctestplan,
    AllTests.xctestplan) under merged-repos/.../TestPlans/ aren't
    wired into any project. Wiring needs either: an Xcode scheme
    addition for AblyLiveObjects in Ably.xcodeproj, or a refactor
    of BuildTool to call `swift test` directly.

  - check-documentation: needs the swift-docc-plugin dependency
    declared at the package level, plus the AWS-credentials and
    sdk-upload-action setup parameterised for ably-cocoa rather
    than ably-liveobjects-swift-plugin.

  - all-checks-completed: trivial once the rest are restored;
    just an aggregating job.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3274f105-9c3d-4b5e-9c8e-3b4e182c20d3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch liveobjects-into-cocoa

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/2205/features May 14, 2026 19:31 Inactive
lawrence-forooghian and others added 5 commits May 14, 2026 16:42
Adds a checked-in shared scheme at
.swiftpm/xcode/xcshareddata/xcschemes/AblyLiveObjects.xcscheme so
that Xcode (and xcodebuild via Ably.xcworkspace) sees a stable
AblyLiveObjects scheme rather than relying on auto-generation,
which produces a scheme without a configured test action.

The TestPlans block references the two .xctestplan files that came
in from the plugin, now living at
merged-repos/ably-liveobjects-swift-plugin/TestPlans/. The
AllTests test plan is the default; the UnitTests plan declares
.integration as a skippedTag so that BuildTool's --only-unit-tests
flag works through `xcodebuild -testPlan UnitTests`.

Based on the plugin's previous in-repo scheme
(merged-repos/.../.swiftpm/xcode/xcshareddata/xcschemes/AblyLiveObjects.xcscheme)
with only the TestPlanReference paths updated to point into
merged-repos/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XcodeRunner now prepends `-workspace Ably.xcworkspace` to every
xcodebuild invocation. Without this, xcodebuild's auto-discovery
picks Ably.xcodeproj at the repo root, which has no AblyLiveObjects
scheme — the AblyLiveObjects (and BuildTool) schemes are SPM-
generated and only visible when xcodebuild operates against the
workspace.

This is a behavioural change away from how BuildTool worked in the
standalone ably-liveobjects-swift-plugin repo (no workspace there;
xcodebuild auto-discovered the SPM package). Since BuildTool now
lives only in this consolidated repo, hardcoding the workspace
path is the right trade — making it configurable would just be
yagni.

With this change, `swift run BuildTool test-library --platform macOS
--only-unit-tests` runs the unit suite via the UnitTests test plan
(271 tests across 85 suites) and the other xcodebuild-routed
subcommands (build-library, build-library-for-testing) succeed too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the brittle `swift test --skip ObjectsIntegrationTests`
invocation with `swift run BuildTool test-library --only-unit-tests`,
which selects tests via the UnitTests.xctestplan's `.integration`
skippedTag rather than by a hardcoded class name regex. Future
integration tests added with the .integration tag will be filtered
correctly regardless of their class name.

Adds two new jobs translated from the original plugin CI:

  - build-and-test-xcode: builds for testing and runs the unit
    suite via BuildTool for each of macOS, iOS, and tvOS.
  - build-release-configuration-xcode: builds the library in
    release configuration via BuildTool, also matrixed over the
    three platforms.

Still not ported, with the same reasons as before (see commit
7ab9a1e): lint, code-coverage, check-example-app,
check-documentation, generate-matrices, and the all-checks-completed
aggregator. The first three depend on tooling that needs scoping
or extra dependencies; documentation needs swift-docc-plugin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The plugin's standalone repo could call its developer tool plainly
"BuildTool" because that repo contained only the LiveObjects code.
In the consolidated ably-cocoa package the name is misleading — it
implies a general-purpose build tool for the whole package, when in
fact every subcommand operates on the AblyLiveObjects target only
(test-library, build-library, build-example-app, build-documentation,
etc.).

Three rename touchpoints:

  - Package.swift: executableTarget name "BuildTool"
    -> "LiveObjectsBuildTool".
  - BuildTool.swift: struct BuildTool -> LiveObjectsBuildTool, with
    an explicit `commandName: "liveobjects-build-tool"` on the
    CommandConfiguration so that the kebab-case help text stays
    short rather than becoming "live-objects-build-tool" via
    ArgumentParser's automatic derivation.
  - .github/workflows/check-liveobjects.yaml: `swift run BuildTool`
    -> `swift run LiveObjectsBuildTool` across the four invocations.

The source directory remains at
merged-repos/ably-liveobjects-swift-plugin/Sources/BuildTool/ —
renaming the directory would just churn `git log --follow` for no
real benefit. SPM target-name and directory-name are decoupled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renames the four jobs in check-liveobjects.yaml so that their IDs
carry the liveobjects- prefix:

  - build-and-test-spm                    -> liveobjects-build-and-test-spm
  - build-release-configuration-spm       -> liveobjects-build-release-configuration-spm
  - build-and-test-xcode                  -> liveobjects-build-and-test-xcode
  - build-release-configuration-xcode     -> liveobjects-build-release-configuration-xcode

In the consolidated repo there will eventually be CI jobs from
several different feature areas (ably-cocoa core, LiveObjects,
maybe plugin-support specifics). Without a prefix, generic IDs
like "build-and-test-spm" collide conceptually with whatever
other workflow files name their own SPM jobs, and the GitHub
Actions UI shows them all interleaved. The prefix makes it
immediately clear which subsystem each job belongs to.

The human-readable `name:` strings are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

4 participants