Skip to content

Conversation

@VeskeR
Copy link
Contributor

@VeskeR VeskeR commented Nov 11, 2025

This marks full transition to path-based API for LiveObjects. Included in this PR:

  • resolve TODOs related to path-based API
  • update all LiveObjects tests to use path-based API
  • remove publicly exposed LiveObject, LiveMap and LiveCounter types from ably.d.ts
  • remove lifecycle subscriptions API for liveobjects (API and implementation). OBJECT_DELETE events are handled by regular subscription events.

Summary by CodeRabbit

  • New Features

    • Added Subscription and StatusSubscription interfaces for simpler subscribe/unsubscribe handling.
    • Introduced JSON type aliases for clearer JSON value typing.
  • Refactor

    • Simplified object/value typing to a flexible record/value model.
    • Reworked object retrieval to return path-wrapped objects for consistent access patterns.
    • Streamlined map value handling to a unified value-centric model.
  • Chores

    • Removed deprecated lifecycle event APIs and legacy types.

@coderabbitai
Copy link

coderabbitai bot commented Nov 11, 2025

Walkthrough

Removed legacy lifecycle APIs and deprecated Live* types, replaced SubscribeResponse with Subscription/StatusSubscription, generalized LiveMap types to Record<string, API.Value>, changed RealtimeObject.get() to return PathObject<LiveMap>, and introduced global AblyObjectsTypes plus JSON type aliases.

Changes

Cohort / File(s) Summary
Type declarations and API surface
ably.d.ts
Removed legacy lifecycle types and many deprecated Live* declarations. Added Subscription and StatusSubscription. Introduced AblyObjectsTypes and AblyDefaultObject. Added Json, JsonScalar, JsonArray, JsonObject aliases. Updated RealtimeObject.get and on() signatures to PathObject/Subscription patterns.
Subscription migration
src/plugins/objects/instance.ts, src/plugins/objects/pathobject.ts, src/plugins/objects/pathobjectsubscriptionregister.ts, src/plugins/objects/liveobject.ts
Replaced SubscribeResponse with Subscription in imports and public method return types (subscribe, instanceSubscribe → subscribe). Adjusted internal calls to use subscribe/unsubscribe semantics.
Realtime object / PathObject changes
src/plugins/objects/realtimeobject.ts, src/plugins/objects/pathobject.ts
RealtimeObject.get() now returns a PathObject<LiveMap<T>> (wraps root LiveMap in DefaultPathObject). Removed getPathObject(). Narrowed PathObject root _root type to non-generic LiveMap.
LiveMap typing and value model
src/plugins/objects/livemap.ts, src/plugins/objects/livemapvaluetype.ts, src/plugins/objects/objectmessage.ts
Generalized LiveMap generics from API.LiveMapType to Record<string, API.Value>. Unified leaf values to API.Value/API.Primitive. Removed PrimitiveObjectValue alias; updated ObjectData.value type. Simplified many static signatures to use string keys and API.Value.
Lifecycle API removal
src/plugins/objects/liveobject.ts
Removed lifecycle enum/types and public lifecycle methods (on, off, offAll), removed _lifecycleEvents, stopped emitting lifecycle deleted event in tombstone(). Updated path update extraction to use update.update.
Objects pool / root changes
src/plugins/objects/objectspool.ts
Simplified getRoot() signature to return non-generic LiveMap (removed generic parameterization).
Subscription registration wiring
src/plugins/objects/pathobjectsubscriptionregister.ts
Updated subscribe signature and imports to return Subscription.
Batch context and primitive casts
src/plugins/objects/batchcontext.ts
Removed explicit Primitive cast when calling createMapSetMessage; now passes value directly.
Test helper update
test/common/modules/private_api_recorder.js
Removed 'call.LiveObject.getObjectId' from known private API identifiers.
Browser template test updates
test/package/browser/template/src/index-objects.ts
Updated sample usages to new types: entrypoint -> Ably.PathObject<LiveMap<...>>; use .get(...).value() for key access; replaced deprecated Live* types with current LiveMap/LiveCounter/PathObject variants; updated subscribe callback typing.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant RealtimeObject
    participant DefaultPathObject
    participant LiveMap

    rect rgba(200,220,255,0.6)
    Note over Client,RealtimeObject: New control flow: get() → PathObject<LiveMap>
    end

    Client->>RealtimeObject: get()
    RealtimeObject->>LiveMap: ensure root LiveMap is available (sync wait)
    RealtimeObject->>DefaultPathObject: wrap LiveMap in DefaultPathObject
    DefaultPathObject-->>Client: return PathObject<LiveMap<T>>

    Client->>DefaultPathObject: subscribe(listener)
    DefaultPathObject-->>Client: return Subscription (unsubscribe)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on:
    • RealtimeObject.get wrapping behavior and callers expecting previous LiveMap return.
    • LiveMap type generalization propagation and value handling (tombstones, object refs).
    • Removal of lifecycle APIs: ensure no remaining consumers or side-effects rely on them.
    • Subscription return type changes across public APIs and tests.

Poem

🐰
A map once typed with many names,
Now wrapped and renamed in noble games,
Subscriptions hop, lifecycle fades,
Values tidy, no more shades,
A rabbit cheers — the types rearranged!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Remove non-path-based API from LiveObjects' accurately describes the main objective of this changeset, which involves transitioning the LiveObjects API to path-based patterns and removing legacy non-path-based APIs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 liveobjects/remove-non-path-based-api

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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/2108/features November 11, 2025 09:05 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2108/bundle-report November 11, 2025 09:05 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2108/typedoc November 11, 2025 09:05 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: 1

📜 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 ccf87e5 and eb5afe1.

📒 Files selected for processing (13)
  • ably.d.ts (7 hunks)
  • objects.d.ts (1 hunks)
  • src/plugins/objects/batchcontext.ts (1 hunks)
  • src/plugins/objects/instance.ts (2 hunks)
  • src/plugins/objects/livemap.ts (11 hunks)
  • src/plugins/objects/livemapvaluetype.ts (2 hunks)
  • src/plugins/objects/liveobject.ts (3 hunks)
  • src/plugins/objects/objectmessage.ts (1 hunks)
  • src/plugins/objects/objectspool.ts (1 hunks)
  • src/plugins/objects/pathobject.ts (4 hunks)
  • src/plugins/objects/pathobjectsubscriptionregister.ts (2 hunks)
  • src/plugins/objects/realtimeobject.ts (1 hunks)
  • test/common/modules/private_api_recorder.js (0 hunks)
💤 Files with no reviewable changes (1)
  • test/common/modules/private_api_recorder.js
🧰 Additional context used
🧬 Code graph analysis (11)
src/plugins/objects/objectmessage.ts (1)
ably.d.ts (1)
  • Primitive (2373-2381)
src/plugins/objects/pathobject.ts (2)
ably.d.ts (3)
  • LiveMap (2415-2418)
  • Subscription (1680-1689)
  • Value (2438-2438)
src/plugins/objects/livemap.ts (1)
  • LiveMap (48-983)
src/plugins/objects/pathobjectsubscriptionregister.ts (1)
ably.d.ts (1)
  • Subscription (1680-1689)
src/plugins/objects/batchcontext.ts (1)
src/plugins/objects/livemap.ts (1)
  • LiveMap (48-983)
src/plugins/objects/liveobject.ts (2)
ably.d.ts (2)
  • EventCallback (1653-1653)
  • Subscription (1680-1689)
src/plugins/objects/instance.ts (1)
  • InstanceEvent (22-25)
src/plugins/objects/objectspool.ts (3)
ably.d.ts (1)
  • LiveMap (2415-2418)
objects.d.ts (1)
  • LiveMap (19-31)
src/plugins/objects/livemap.ts (1)
  • LiveMap (48-983)
src/plugins/objects/realtimeobject.ts (1)
ably.d.ts (3)
  • Value (2438-2438)
  • AblyDefaultObject (2487-3539)
  • LiveMap (2415-2418)
src/plugins/objects/instance.ts (1)
ably.d.ts (4)
  • EventCallback (1653-1653)
  • InstanceSubscriptionEvent (3688-3693)
  • Subscription (1680-1689)
  • LiveObject (2431-2431)
src/plugins/objects/livemapvaluetype.ts (2)
src/plugins/objects/livemap.ts (2)
  • LiveMap (48-983)
  • ValueObjectData (24-27)
ably.d.ts (2)
  • LiveMap (2415-2418)
  • Primitive (2373-2381)
src/plugins/objects/livemap.ts (4)
ably.d.ts (6)
  • Primitive (2373-2381)
  • Value (2438-2438)
  • LiveMap (2415-2418)
  • LiveObject (2431-2431)
  • ObjectMessage (3754-3801)
  • CompactedValue (2444-2469)
src/plugins/objects/liveobject.ts (1)
  • LiveObjectUpdate (18-26)
src/plugins/objects/realtimeobject.ts (1)
  • RealtimeObject (38-489)
src/plugins/objects/objectmessage.ts (1)
  • ObjectMessage (358-462)
ably.d.ts (4)
objects.d.ts (1)
  • LiveMap (19-31)
src/plugins/objects/livemap.ts (1)
  • LiveMap (48-983)
src/plugins/objects/realtimeobject.ts (1)
  • ObjectsEventCallback (32-32)
test/package/browser/template/src/index-objects.ts (1)
  • AblyObjectsTypes (27-29)
🪛 GitHub Actions: Test NPM package
objects.d.ts

[error] 14-14: Cannot redeclare exported variable 'LiveMap'.

⏰ 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). (6)
  • GitHub Check: test-browser (firefox)
  • GitHub Check: test-browser (webkit)
  • GitHub Check: test-browser (chromium)
  • GitHub Check: test-node (18.x)
  • GitHub Check: test-node (20.x)
  • GitHub Check: test-node (16.x)
🔇 Additional comments (11)
src/plugins/objects/batchcontext.ts (1)

84-84: LGTM! Correct alignment with function signature.

The removal of the cast to Primitive is correct, as LiveMap.createMapSetMessage expects value: API.Value, which can be a primitive, LiveCounterValueType, or LiveMapValueType.

src/plugins/objects/pathobjectsubscriptionregister.ts (1)

6-6: LGTM! Consistent with API refactoring.

The change from SubscribeResponse to Subscription aligns with the PR-wide transition to Subscription-based abstractions. The implementation correctly returns an object with an unsubscribe method matching the Subscription interface.

Also applies to: 63-63

src/plugins/objects/objectmessage.ts (1)

44-44: LGTM! Type standardization.

The change from PrimitiveObjectValue to API.Primitive standardizes primitive value typing across the codebase and aligns with the removal of the PrimitiveObjectValue alias.

src/plugins/objects/livemapvaluetype.ts (1)

77-77: LGTM! Consistent type handling.

Both changes correctly align with the removal of PrimitiveObjectValue and the standardization on API.Primitive. Line 77 removes an unnecessary cast since LiveMap.validateKeyValue expects value: API.Value, and line 141 correctly types primitive values as API.Primitive for ValueObjectData.

Also applies to: 141-141

src/plugins/objects/objectspool.ts (1)

33-34: LGTM! API simplification.

The removal of the generic type parameter simplifies the API and aligns with the broader refactoring to use non-generic LiveMap. This change is consistent with the PathObject-based access pattern introduced in the PR.

src/plugins/objects/pathobject.ts (1)

14-14: LGTM! Consistent API refactoring.

All changes align with the PR objectives:

  1. Lines 14, 354: Migration from SubscribeResponse to Subscription aligns with the new subscription-based abstractions.
  2. Line 34: Removing the generic type parameter from LiveMap simplifies the internal API.
  3. Line 385: Direct assignment is cleaner since this._root is already LiveMap, which extends Value.

These changes are consistent with the broader transition to PathObject-based access patterns.

Also applies to: 34-34, 354-354, 385-385

src/plugins/objects/instance.ts (1)

12-12: LGTM! Proper subscription delegation.

The changes correctly implement the migration to Subscription:

  1. Lines 12, 172: Import and return type updated to use Subscription.
  2. Line 176: Implementation properly delegates to the underlying LiveObject's subscribe method and transforms the internal InstanceEvent to the public InstanceSubscriptionEvent format, including converting ObjectMessage to its user-facing representation.

Also applies to: 172-172, 176-176

src/plugins/objects/realtimeobject.ts (1)

82-91: Return type upgrade looks solid.

Gating on the existing sync semaphore before handing back the DefaultPathObject keeps the old lifecycle guarantees while exposing the new path-based surface. Nicely done.

src/plugins/objects/liveobject.ts (1)

71-81: Subscription wrapper matches the new contract.

Switching to the { unsubscribe } shape plugs straight into the new Subscription interface without altering the underlying emitter behaviour. Looks good to me.

src/plugins/objects/livemap.ts (1)

499-525: compact still normalizes nested structures correctly.

Iterating via entries() means we continue to skip tombstoned keys and base64 wrap buffers, so the observable JSON footprint stays intact after the generics refactor.

ably.d.ts (1)

1670-1710: Typings line up with the runtime changes.

The new Subscription / StatusSubscription interfaces give consumers a consistent unsubscribe/off contract and match the objects returned in code. Looks good.

@VeskeR VeskeR force-pushed the PUB-2065/path-based-batch-api branch 2 times, most recently from aea9de3 to 80fd55e Compare November 11, 2025 09:22
@VeskeR VeskeR force-pushed the liveobjects/remove-non-path-based-api branch 2 times, most recently from 81e9e97 to e7d734e Compare November 11, 2025 09:24
@github-actions github-actions bot temporarily deployed to staging/pull/2108/bundle-report November 11, 2025 09:24 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2108/features November 11, 2025 09:24 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2108/typedoc November 11, 2025 09:24 Inactive
Copy link
Contributor

@mschristensen mschristensen left a comment

Choose a reason for hiding this comment

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

Small issue to fix on the test otherwise looks great

const entryInstance = entryPathObject.instance();

await Promise.all(
expectedKeys.map((key) => (entryInstance.get(key) ? undefined : waitForMapKeyUpdate(entryInstance, key))),
);
}

describe('realtime/objects', function () {
describe.only('realtime/objects', function () {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should remove this .only

Copy link
Contributor Author

Choose a reason for hiding this comment

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

? T
: any;

declare global {
Copy link
Contributor

Choose a reason for hiding this comment

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

Unrelated to this PR specifically, but:

I had a thought about this default typings declaration. I don't know this this is very useful to be honest, since you likely use multiple channels in your app, and each channel probably needs a different schema. Therefore I am tempted to remove this in the new version of the API and rely only on the type parameter approach.

Let's discuss

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seeing in our demos how easy it is to provide an explicit type parameter the first time you call object.get<MyObject> I'd agree, seems like there is no actual need to have a global interface.
Also explaning how to use global type is harder than just saying "provide type for your object as a type parameter to the .get() call", so it's a win-win

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done in #2113

@VeskeR VeskeR force-pushed the PUB-2065/path-based-batch-api branch from 80fd55e to 58bcf67 Compare November 14, 2025 09:36
@VeskeR VeskeR force-pushed the liveobjects/remove-non-path-based-api branch from e7d734e to 70540ff Compare November 14, 2025 09:37
@github-actions github-actions bot temporarily deployed to staging/pull/2108/features November 14, 2025 09:38 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2108/typedoc November 14, 2025 09:38 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2108/bundle-report November 14, 2025 09:38 Inactive
Base automatically changed from PUB-2065/path-based-batch-api to integration/objects-breaking-api November 14, 2025 09:41
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 (3)
src/plugins/objects/liveobject.ts (1)

3-3: LiveObject.subscribe and path notifications match the new subscription model

Returning a { unsubscribe } Subscription from subscribe() is consistent with the new public typings and integrates cleanly with DefaultInstance.subscribe. notifyUpdated still clears all listeners on tombstone, so subscriptions remain self-cleaning on delete.

In _notifyPathSubscriptions, using Object.keys(update.update) under the 'LiveMapUpdate' guard is fine as long as map updates always provide a concrete update object rather than undefined. If there is any path where a non-noop LiveMapUpdate could have update absent, adding a defensive if (update.update) guard would avoid a possible Object.keys(undefined) at runtime.

Also applies to: 71-81, 307-319

ably.d.ts (2)

1669-1712: Subscription / StatusSubscription wiring looks coherent across the API

The new Subscription and StatusSubscription interfaces, and their use in:

  • RealtimeObject.get (returning Promise<PathObject<LiveMap<T>>>),
  • RealtimeObject.on (returning StatusSubscription),
  • PathObjectBase.subscribe (returning Subscription),
  • InstanceBase.subscribe (returning Subscription),

all line up with the implementations in liveobject.ts, instance.ts, pathobject.ts, and realtimeobject.ts, which return simple objects exposing unsubscribe or off. The structural types match the runtime shapes, so this should be a non-breaking, clearer replacement for the old SubscribeResponse.

Only minor nit: you now have both OnObjectsEventResponse in realtimeobject.ts and StatusSubscription here describing effectively the same shape. At some point it may be worth consolidating those to avoid parallel concepts, but that's optional.

Also applies to: 2315-2351, 2497-2533, 3383-3413


2373-2402: Json aliases are sensible; confirm JsonObject’s allowance for undefined is intentional*

Adding Json, JsonScalar, JsonArray, and JsonObject and then using JsonArray | JsonObject inside Primitive makes the value-space for LiveObjects explicit and JSON-oriented, which is helpful.

JsonObject is defined as { [prop: string]: Json | undefined }, which allows properties whose value type includes undefined. That’s reasonable if you want to model optional fields in user data, but it technically goes beyond strict JSON (where undefined values are typically omitted rather than represented). If your intent is to mirror “JSON-encodable data with optional properties” rather than strict JSON, this is fine; otherwise, consider tightening this to Json and letting optional keys model absence.

📜 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 eb5afe1 and 70540ff.

📒 Files selected for processing (12)
  • ably.d.ts (7 hunks)
  • src/plugins/objects/batchcontext.ts (1 hunks)
  • src/plugins/objects/instance.ts (2 hunks)
  • src/plugins/objects/livemap.ts (11 hunks)
  • src/plugins/objects/livemapvaluetype.ts (2 hunks)
  • src/plugins/objects/liveobject.ts (3 hunks)
  • src/plugins/objects/objectmessage.ts (1 hunks)
  • src/plugins/objects/objectspool.ts (1 hunks)
  • src/plugins/objects/pathobject.ts (4 hunks)
  • src/plugins/objects/pathobjectsubscriptionregister.ts (2 hunks)
  • src/plugins/objects/realtimeobject.ts (1 hunks)
  • test/common/modules/private_api_recorder.js (0 hunks)
💤 Files with no reviewable changes (1)
  • test/common/modules/private_api_recorder.js
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/plugins/objects/objectmessage.ts
  • src/plugins/objects/objectspool.ts
  • src/plugins/objects/pathobjectsubscriptionregister.ts
  • src/plugins/objects/pathobject.ts
  • src/plugins/objects/batchcontext.ts
🧰 Additional context used
🧬 Code graph analysis (6)
src/plugins/objects/instance.ts (1)
ably.d.ts (4)
  • EventCallback (1653-1653)
  • InstanceSubscriptionEvent (3688-3693)
  • Subscription (1680-1689)
  • LiveObject (2431-2431)
src/plugins/objects/realtimeobject.ts (2)
ably.d.ts (3)
  • Value (2438-2438)
  • AblyDefaultObject (2487-3539)
  • LiveMap (2415-2418)
src/plugins/objects/livemap.ts (1)
  • LiveMap (48-983)
src/plugins/objects/livemapvaluetype.ts (2)
src/plugins/objects/livemap.ts (2)
  • LiveMap (48-983)
  • ValueObjectData (24-27)
ably.d.ts (2)
  • LiveMap (2415-2418)
  • Primitive (2373-2381)
src/plugins/objects/liveobject.ts (2)
ably.d.ts (2)
  • EventCallback (1653-1653)
  • Subscription (1680-1689)
src/plugins/objects/instance.ts (1)
  • InstanceEvent (22-25)
ably.d.ts (4)
src/plugins/objects/livemap.ts (1)
  • LiveMap (48-983)
objects.d.ts (1)
  • LiveMap (16-28)
src/plugins/objects/realtimeobject.ts (1)
  • ObjectsEventCallback (32-32)
test/package/browser/template/src/index-objects.ts (1)
  • AblyObjectsTypes (27-29)
src/plugins/objects/livemap.ts (2)
ably.d.ts (6)
  • Primitive (2373-2381)
  • Value (2438-2438)
  • LiveMap (2415-2418)
  • LiveObject (2431-2431)
  • ObjectMessage (3754-3801)
  • CompactedValue (2444-2469)
src/plugins/objects/liveobject.ts (1)
  • LiveObjectUpdate (18-26)
⏰ 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). (6)
  • GitHub Check: test-browser (firefox)
  • GitHub Check: test-browser (chromium)
  • GitHub Check: test-browser (webkit)
  • GitHub Check: test-node (16.x)
  • GitHub Check: test-node (18.x)
  • GitHub Check: test-node (20.x)
🔇 Additional comments (10)
src/plugins/objects/livemapvaluetype.ts (1)

77-77: Initial entry validation and primitive casting look correct

Using LiveMap.validateKeyValue for entries ensures initial map state is validated consistently with runtime set operations, and casting leaf values to API.Primitive in ValueObjectData aligns with the new ObjectData.value typing. I don't see behavioural regressions here.

Also applies to: 141-143

src/plugins/objects/instance.ts (1)

12-14: Instance.subscribe now aligned with Subscription API

Importing Subscription and returning it from DefaultInstance.subscribe matches the new public typings, and subscribeIterator correctly reuses the unsubscribe function from that object. The runtime behaviour stays the same while the API surface becomes consistent.

Also applies to: 172-182

src/plugins/objects/realtimeobject.ts (1)

82-92: get() returning a PathObject wrapper is consistent with the path-based API

Switching get() to return a DefaultPathObject around the root LiveMap (instead of the map itself) matches the new PathObject<LiveMap<T>> return type while preserving the existing sync-wait semantics. This is the right place to centralize the path-based API.

ably.d.ts (1)

2471-2494: AblyObjectsTypes / AblyDefaultObject typing pattern looks correct

The global AblyObjectsTypes with an index signature plus the AblyDefaultObject conditional:

  • Defaults to Record<string, Value> when users don’t provide a custom AblyObjectsTypes['object'].
  • Switches to the user-provided AblyObjectsTypes['object'] when present and constrained to Record<string, Value>.
  • Produces a clear string literal error type if the user’s object type doesn’t match the expected shape.

That matches the documentation comments and the example given in the RealtimeObject.get doc, and is a neat way to let advanced users strongly type their root object without impacting default ergonomics. I’d just suggest double-checking with your example app / template that augmenting AblyObjectsTypes behaves as expected under your supported TypeScript versions.

src/plugins/objects/livemap.ts (6)

24-52: LGTM! Type generalization aligns with path-based API migration.

The changes successfully generalize the LiveMap type system:

  • ValueObjectData.value correctly uses API.Primitive matching the type definition
  • LiveMapUpdate<T> and LiveMap<T> now use Record<string, API.Value> as the type constraint with proper defaults
  • The branded interface pattern with [API.__livetype]: 'LiveMap' provides type-safe discrimination at compile time

These changes are consistent with the broader migration to value-centric typing.


68-82: LGTM! Static factory methods correctly use non-generic return types.

The removal of generic type parameters from zeroValue and fromObjectState is consistent with the type generalization strategy, where LiveMap now defaults to Record<string, API.Value>.


196-218: LGTM! Value resolution correctly handles API.Value types.

The get method and _getResolvedValueFromObjectData properly handle the value-centric model:

  • Tombstoned objects and missing entries return undefined as expected
  • Line 922 safely casts the internal LiveObject to the public API.LiveObject interface
  • Return types align with the API.Value type definition

Also applies to: 903-923


499-525: LGTM! Compact method correctly implements recursive value compaction.

The return type API.CompactedValue<API.LiveMap<T>> properly leverages the type-level transformation that:

  • Recursively compacts nested LiveMap values to plain objects
  • Converts LiveCounter to number
  • Transforms Buffer/ArrayBuffer to base64 strings
  • Preserves other primitives as-is

The implementation correctly handles all these cases.


141-161: LGTM! Method signatures are consistent with type system changes.

All updated method signatures correctly use:

  • string keys throughout (no generic key types)
  • API.Value for values
  • Proper generic constraints with TKey extends keyof T & string in iterators
  • Correct handling of ObjectIdObjectData vs ValueObjectData for parent reference tracking

Also applies to: 236-294, 477-490, 947-982


87-182: Review comment is valid but reflects design limitation, not a bug.

The type safety concern is real: validateKeyValue accepts any object (line 173-181) while the cast to API.Primitive expects only specific types (string, number, boolean, Buffer, ArrayBuffer, JsonArray, JsonObject).

However, verification shows this is handled by design: the encodeObjectDataFn at line 412-427 uses JSON.stringify for objects, which silently drops non-serializable properties. This means objects with methods or symbols will lose data during serialization, but without throwing errors.

The validation is intentionally loose to provide flexibility, with serialization as the final safety gate. If this behavior is acceptable for your use case, no changes are needed. If you want stricter type checking to prevent silent data loss, validateKeyValue could be enhanced to reject objects with non-serializable properties.

This marks full transition to path-based API for LiveObjects.
Included in this PR:
- resolve TODOs related to path-based API
- update all LiveObjects tests to use path-based API
- remove publicly exposed LiveObject, LiveMap and LiveCounter types from
  ably.d.ts
- remove lifecycle subscriptions API for liveobjects (API and
  implementation). OBJECT_DELETE events are handled by regular
  subscription events.
- update package tests to use the new path-based API for Objects
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
test/package/browser/template/src/index-objects.ts (1)

2-24: Fix LiveMapPathObject generic argument to use the map entry type, not the LiveMap wrapper

The examples overall are aligned with the new path-based API, but this line is likely wrong:

const map: Ably.LiveMapPathObject<MyCustomObject['mapKey']> = myObject.get('mapKey');
  • MyCustomObject['mapKey'] is LiveMap<{ foo: 'bar'; nestedMap?: LiveMap<{ baz: 'qux' }> }>
  • LiveMapPathObject<T> expects T extends Record<string, Ably.Value> describing the map’s entry shape, not the LiveMap<...> wrapper type.

This will typically violate the generic constraint and can also break get('foo') key typing.

You probably want the entry type instead, e.g.:

type MapEntryType = MyCustomObject['mapKey'] extends LiveMap<infer U> ? U : never;

const map: Ably.LiveMapPathObject<MapEntryType> = myObject.get('mapKey');

The rest of the typings in this file (PathObject<LiveMap<MyCustomObject>>, .get(...).value(), subscription callback types, and the explicit-object get<ExplicitObjectType>()) look consistent with the new declarations.

Also applies to: 44-63, 67-77

src/plugins/objects/livemap.ts (2)

903-923: LiveCounter is missing implements API.LiveCounter declaration.

The double cast at line 922 exists because LiveCounter (line 15 in src/plugins/objects/livecounter.ts) extends LiveObject but does not declare implements API.LiveCounter, unlike LiveMap which properly declares implements API.LiveMap<T>.

The cast refObject as unknown as API.LiveObject is a workaround for this missing interface implementation. To eliminate the unsafe cast and improve type safety, add the interface declaration:

export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate> implements API.LiveCounter {

This would align LiveCounter with LiveMap's pattern and allow proper type narrowing without requiring the double cast.


87-118: Type safety issue: createMapSetMessage() cannot handle bare LiveObject instances despite accepting them in the type signature.

The method accepts value: API.Value (which includes LiveMap | LiveCounter instances), but the implementation only checks for LiveCounterValueType.instanceof() and LiveMapValueType.instanceof(). If a bare LiveMap or LiveCounter instance is passed—which is allowed by the type system through DefaultBatchContext.set(key: string, value: Value) at line 77 of batchcontext.ts—the code falls through to line 116 and incorrectly casts it to API.Primitive.

Either narrow the parameter type to exclude bare LiveObject instances, or add runtime validation to reject them with a clear error message.

🧹 Nitpick comments (2)
src/plugins/objects/realtimeobject.ts (1)

34-36: Internal OnObjectsEventResponse vs public StatusSubscription naming

RealtimeObject.on still returns an internal OnObjectsEventResponse ({ off(): void }), while the public typings now expose StatusSubscription. Structurally these match, so there is no runtime or typing bug, but you may want to:

  • Rename OnObjectsEventResponse to StatusSubscription (or import the public type) for consistency and to avoid future drift.

Also applies to: 94-103

src/plugins/objects/liveobject.ts (1)

307-319: Consider guarding LiveMapUpdate.updatedKeys computation against malformed updates

Here:

if (update._type === 'LiveMapUpdate') {
  const updatedKeys = Object.keys(update.update);
  for (const key of updatedKeys) {
    // ...
  }
}

this assumes update.update is always a non-null object. If a malformed LiveMapUpdate were ever constructed with update.update null or non-object, this would throw.

Given this is internal, it’s low risk, but you could defensively narrow for robustness:

if (update._type === 'LiveMapUpdate' && update.update && typeof update.update === 'object') {
  const updatedKeys = Object.keys(update.update as Record<string, unknown>);
  // ...
}
📜 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 70540ff and 9c9d510.

📒 Files selected for processing (13)
  • ably.d.ts (7 hunks)
  • src/plugins/objects/batchcontext.ts (1 hunks)
  • src/plugins/objects/instance.ts (2 hunks)
  • src/plugins/objects/livemap.ts (11 hunks)
  • src/plugins/objects/livemapvaluetype.ts (2 hunks)
  • src/plugins/objects/liveobject.ts (3 hunks)
  • src/plugins/objects/objectmessage.ts (1 hunks)
  • src/plugins/objects/objectspool.ts (1 hunks)
  • src/plugins/objects/pathobject.ts (4 hunks)
  • src/plugins/objects/pathobjectsubscriptionregister.ts (2 hunks)
  • src/plugins/objects/realtimeobject.ts (1 hunks)
  • test/common/modules/private_api_recorder.js (0 hunks)
  • test/package/browser/template/src/index-objects.ts (3 hunks)
💤 Files with no reviewable changes (1)
  • test/common/modules/private_api_recorder.js
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/plugins/objects/batchcontext.ts
  • src/plugins/objects/objectmessage.ts
  • src/plugins/objects/objectspool.ts
  • src/plugins/objects/pathobject.ts
  • src/plugins/objects/pathobjectsubscriptionregister.ts
  • src/plugins/objects/livemapvaluetype.ts
🧰 Additional context used
🧬 Code graph analysis (6)
src/plugins/objects/instance.ts (1)
ably.d.ts (4)
  • EventCallback (1653-1653)
  • InstanceSubscriptionEvent (3688-3693)
  • Subscription (1680-1689)
  • LiveObject (2431-2431)
src/plugins/objects/realtimeobject.ts (2)
ably.d.ts (3)
  • Value (2438-2438)
  • AblyDefaultObject (2487-3539)
  • LiveMap (2415-2418)
src/plugins/objects/livemap.ts (1)
  • LiveMap (48-983)
src/plugins/objects/liveobject.ts (2)
ably.d.ts (2)
  • EventCallback (1653-1653)
  • Subscription (1680-1689)
src/plugins/objects/instance.ts (1)
  • InstanceEvent (22-25)
test/package/browser/template/src/index-objects.ts (4)
ably.d.ts (2)
  • LiveMap (2415-2418)
  • LiveCounter (2423-2426)
src/plugins/objects/batchcontext.ts (2)
  • size (71-75)
  • value (30-34)
src/plugins/objects/instance.ts (2)
  • size (133-139)
  • value (76-104)
src/plugins/objects/pathobject.ts (2)
  • size (259-276)
  • value (144-183)
ably.d.ts (4)
src/plugins/objects/livemap.ts (1)
  • LiveMap (48-983)
objects.d.ts (1)
  • LiveMap (16-28)
src/plugins/objects/realtimeobject.ts (1)
  • ObjectsEventCallback (32-32)
test/package/browser/template/src/index-objects.ts (1)
  • AblyObjectsTypes (27-29)
src/plugins/objects/livemap.ts (3)
ably.d.ts (6)
  • Primitive (2373-2381)
  • Value (2438-2438)
  • LiveMap (2415-2418)
  • LiveObject (2431-2431)
  • ObjectMessage (3754-3801)
  • CompactedValue (2444-2469)
src/plugins/objects/liveobject.ts (1)
  • LiveObjectUpdate (18-26)
objects.d.ts (1)
  • LiveMap (16-28)
⏰ 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). (6)
  • GitHub Check: test-node (20.x)
  • GitHub Check: test-node (18.x)
  • GitHub Check: test-node (16.x)
  • GitHub Check: test-browser (firefox)
  • GitHub Check: test-browser (chromium)
  • GitHub Check: test-browser (webkit)
🔇 Additional comments (14)
ably.d.ts (6)

1669-1711: Subscription / StatusSubscription abstractions look consistent

The new Subscription and StatusSubscription interfaces are minimal and align with how subscribe/on are implemented internally (returning objects with unsubscribe/off methods). No issues from a typing or runtime perspective.


2315-2341: RealtimeObject.get typing matches the new path-based API

get<T extends Record<string, Value> = AblyDefaultObject>(): Promise<PathObject<LiveMap<T>>> nicely ties together:

  • Defaulting T via AblyDefaultObject for global configuration.
  • Returning a PathObject<LiveMap<T>> instead of a raw LiveMap, consistent with the rest of the path-based surface.

This matches the implementation in src/plugins/objects/realtimeobject.ts and provides a clear, type-safe entrypoint.


2383-2402: Json / Primitive type refinements are reasonable

Introducing Json, JsonScalar, JsonArray, and JsonObject, and including JsonArray/JsonObject in Primitive, cleanly expresses “JSON-encodable” values and remains compatible with the existing runtime checks that treat plain objects and arrays as primitives. No concerns here.


2471-2494: AblyObjectsTypes / AblyDefaultObject conditional typing is sound

The declare global AblyObjectsTypes with an index signature plus:

export type AblyDefaultObject =
  unknown extends AblyObjectsTypes['object']
    ? Record<string, Value>
    : AblyObjectsTypes['object'] extends Record<string, Value>
      ? AblyObjectsTypes['object']
      : `Provided type definition for the channel \`object\` in AblyObjectsTypes is not of an expected Record<string, Value> type`;

gives:

  • A sensible default (Record<string, Value>) when users do not augment AblyObjectsTypes.
  • A strict check that user-provided object types are Record<string, Value>, with a readable error string otherwise.

This interacts correctly with RealtimeObject.get’s default generic and is a good pattern for global customization.


2527-2533: PathObjectBase.subscribe returning Subscription is consistent

Changing PathObjectBase.subscribe to return Subscription aligns the public typings with the internal pattern of returning { unsubscribe } from DefaultPathObject.subscribe, and with the new reusable Subscription abstraction. No issues.


3410-3413: InstanceBase.subscribe using Subscription matches implementation

InstanceBase.subscribe(listener: EventCallback<InstanceSubscriptionEvent<T>>): Subscription matches DefaultInstance.subscribe in src/plugins/objects/instance.ts, which returns an object with an unsubscribe method. This unifies the subscription model across path and instance APIs.

src/plugins/objects/instance.ts (1)

12-14: Instance subscriptions correctly adopt the new Subscription type

Importing Subscription and updating:

  • subscribe(listener): Subscription to wrap the underlying LiveObject.subscribe, and
  • subscribeIterator() to use the returned { unsubscribe }

keeps behavior the same while aligning with the new public Subscription abstraction. Type and runtime wiring look correct.

Also applies to: 172-192

src/plugins/objects/realtimeobject.ts (1)

82-92: RealtimeObject.get now correctly returns a root PathObject

The new get<T>():

  • Validates channel configuration, waits for initial sync if necessary, and
  • Returns new DefaultPathObject(this, this._objectsPool.getRoot(), [])

which is consistent with the public type Promise<API.PathObject<API.LiveMap<T>>> and the path-based API design. The sync-wait semantics are preserved; only the return type has shifted to the new PathObject abstraction.

src/plugins/objects/liveobject.ts (1)

3-4: LiveObject.subscribe correctly adopts Subscription and remains aligned with access checks

Importing Subscription and implementing:

subscribe(listener: EventCallback<InstanceEvent>): Subscription {
  this._realtimeObject.throwIfInvalidAccessApiConfiguration();
  this._subscriptions.on(LiveObjectSubscriptionEvent.updated, listener);
  const unsubscribe = () => this._subscriptions.off(LiveObjectSubscriptionEvent.updated, listener);
  return { unsubscribe };
}

is consistent with the new public API and with how DefaultInstance.subscribe consumes it. Access validation and cleanup behavior are correct.

Also applies to: 71-81

src/plugins/objects/livemap.ts (5)

26-26: LGTM - Type changes align with path-based API transition.

The changes to use API.Primitive for value storage, widen LiveMap to Record<string, API.Value>, and add the branded type marker [API.__livetype]: 'LiveMap' are consistent with the transition to a path-based, API-centric model described in the PR objectives.

Also applies to: 42-52


68-82: Static constructors now return non-generic LiveMap.

The removal of generic type parameters from zeroValue and fromObjectState reduces type information at call sites but aligns with the transition to a simpler path-based API. This trade-off appears intentional.


166-182: LGTM - Validation updated for string keys and API.Value.

The validation correctly checks that keys are strings and values are of supported types. The check at lines 173-179 permits string, number, boolean, and object types, which aligns with API.Primitive (including Buffer, ArrayBuffer, JsonArray, JsonObject as objects).


197-218: LGTM - Simplified return logic for undefined cases.

The changes to return undefined directly for tombstoned map, missing entries, and tombstoned entries (lines 201, 208, 213) simplify the code and are consistent with the updated type model.


499-525: LGTM - Updated return type aligns with API-centric model.

The return type API.CompactedValue<API.LiveMap<T>> correctly represents the recursive unwrapping behavior where LiveMap becomes a plain object, LiveCounter becomes a number, and binary types become base64 strings. The implementation is consistent with this type.

@VeskeR VeskeR merged commit 882064f into integration/objects-breaking-api Nov 26, 2025
17 of 20 checks passed
@VeskeR VeskeR deleted the liveobjects/remove-non-path-based-api branch November 26, 2025 09:21
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.

3 participants