Skip to content

Conversation

@VeskeR
Copy link
Contributor

@VeskeR VeskeR commented Nov 7, 2025

This also removes the now-obsolete RealtimeObject.batch() API.

Resolves PUB-2065

Summary by CodeRabbit

  • New Features

    • Expanded, strongly-typed batch API for live objects providing contextual access to values, entries, keys and collection methods.
    • Per-instance and path-level async batch methods to run grouped modifications and flush them atomically.
  • Refactor

    • Centralized root-managed batch orchestration and unified message construction for more reliable, efficient batch publishes.
    • Removed legacy batch entrypoint in favor of the new batch function/context model.

@coderabbitai
Copy link

coderabbitai bot commented Nov 7, 2025

Walkthrough

Replaces the legacy batch callback API with a typed BatchFunction/BatchContext system. Adds DefaultBatchContext and RootBatchContext, moves per-type wrappers into instance-backed contexts, updates LiveMap to emit async multi-message sets, adds Instance/PathObject.batch, and removes the RealtimeObject root batch API.

Changes

Cohort / File(s) Summary
Type declarations
ably.d.ts
Introduced BatchFunction<T>, BatchContext family (BatchContextBase, LiveMapBatchContext, LiveCounterBatchContext, PrimitiveBatchContext, AnyBatchContext), and BatchOperations<T>. Removed BatchCallback. Updated LiveMap/LiveCounter/Any operations and PathObject/Instance typings to use BatchFunction/BatchContext.
Unified batch context
src/plugins/objects/batchcontext.ts
Replaced per-type wrapper with export class DefaultBatchContext implements AnyBatchContext. Added instance-backed APIs: get, value, compact, id, entries/keys/values, size, and mutation ops (set, remove, increment, decrement). Delegates queuing to RootBatchContext and enforces closed-state via _throwIfClosed().
Root batch manager
src/plugins/objects/rootbatchcontext.ts
New RootBatchContext (extends DefaultBatchContext) that memoizes wrapped instances, queues async message constructors, exposes flush(), close(), isClosed(), wrapInstance(), and queueMessages(), and publishes aggregated messages on flush.
Removed per-type wrappers
src/plugins/objects/batchcontextlivecounter.ts, src/plugins/objects/batchcontextlivemap.ts
Deleted both files; their functionality consolidated into DefaultBatchContext/RootBatchContext.
Instance-level batching
src/plugins/objects/instance.ts
Added DefaultInstance.batch(fn: BatchFunction) which creates a RootBatchContext, calls fn with a BatchContext, awaits flush(), and closes; added isLiveMap() and isLiveCounter() helpers; keys() now yields empty iterator if value not a LiveMap.
PathObject batching
src/plugins/objects/pathobject.ts
Added DefaultPathObject.batch(fn: BatchFunction) (two occurrences). Validates resolved instance is a LiveObject, creates RootBatchContext, invokes fn, awaits flush(), ensures close(), and returns empty iterator for keys on certain resolution errors (92005).
LiveMap message flow
src/plugins/objects/livemap.ts
createMapSetMessage() made async and now returns Promise<ObjectMessage[]> (consolidates value-type object-creation messages). set() updated to await and publish multiple messages. Removed createMapSetMessageForValueType().
RealtimeObject cleanup
src/plugins/objects/realtimeobject.ts
Removed exported BatchCallback type and the root-level batch() method and import of BatchContext.
Module report
scripts/moduleReport.ts
Removed deleted wrapper files from allowed set and added src/plugins/objects/rootbatchcontext.ts.
Tests
test/realtime/objects.test.js
Updated tests to reflect DefaultBatchContext / RootBatchContext naming and behaviors; expanded tests for nested contexts and new BatchContext APIs; renamed helper assertions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Instance/PathObject
    participant RootBatchContext
    participant DefaultBatchContext
    participant RealtimeObject

    User->>Instance/PathObject: batch(fn: BatchFunction)
    activate Instance/PathObject
    Instance/PathObject->>RootBatchContext: create RootBatchContext
    activate RootBatchContext
    RootBatchContext->>DefaultBatchContext: provide BatchContext for instance
    DefaultBatchContext->>RootBatchContext: queueMessages(msgCtor)
    Note right of RootBatchContext `#DDFFDD`: msg constructors stored
    Instance/PathObject->>RootBatchContext: await flush()
    RootBatchContext->>RootBatchContext: execute msgCtors -> messages[]
    RootBatchContext->>RealtimeObject: publish(messages[])
    RootBatchContext->>Instance/PathObject: close()
    deactivate RootBatchContext
    deactivate Instance/PathObject
    User-->>User: Promise resolves
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus areas:
    • Type correctness for conditional BatchContext<T> mappings and exported typings in ably.d.ts.
    • That DefaultBatchContext correctly replicates and consolidates behavior formerly in BatchContextLiveMap and BatchContextLiveCounter (access checks, message construction, mutation semantics).
    • RootBatchContext.flush() ordering, aggregation, and publish semantics to avoid message loss/duplication.
    • All callers of LiveMap.createMapSetMessage() updated to handle Promise<ObjectMessage[]>.
    • Lifecycle/closed-state handling across DefaultBatchContext / RootBatchContext to prevent races or leaked state.
    • Tests in test/realtime/objects.test.js aligned with new APIs and coverage for nested contexts.

Poem

🐇 I nibbled old callbacks until they hopped away,
Typed BatchFunction in paw, I tidy up the day.
RootBatchContext drums, DefaultBatchContext bounds,
Messages queued and flushed in neat, thumping rounds —
A rabbit’s small cheer for batching on the way!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: updating the batch API to use PathObject/Instance and handle object creation.
✨ 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 PUB-2065/path-based-batch-api

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.39.7)
test/realtime/objects.test.js

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/2107/features November 7, 2025 08:26 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2107/bundle-report November 7, 2025 08:26 Inactive
@VeskeR VeskeR force-pushed the PUB-2065/path-based-batch-api branch from 5019f75 to 8b57e60 Compare November 7, 2025 08:34
@github-actions github-actions bot temporarily deployed to staging/pull/2107/features November 7, 2025 08:35 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2107/bundle-report November 7, 2025 08:35 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: 2

📜 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 ee505f3 and 8b57e60.

📒 Files selected for processing (10)
  • ably.d.ts (9 hunks)
  • scripts/moduleReport.ts (1 hunks)
  • src/plugins/objects/batchcontext.ts (1 hunks)
  • src/plugins/objects/batchcontextlivecounter.ts (0 hunks)
  • src/plugins/objects/batchcontextlivemap.ts (0 hunks)
  • src/plugins/objects/instance.ts (4 hunks)
  • src/plugins/objects/livemap.ts (3 hunks)
  • src/plugins/objects/pathobject.ts (4 hunks)
  • src/plugins/objects/realtimeobject.ts (0 hunks)
  • src/plugins/objects/rootbatchcontext.ts (1 hunks)
💤 Files with no reviewable changes (3)
  • src/plugins/objects/batchcontextlivemap.ts
  • src/plugins/objects/realtimeobject.ts
  • src/plugins/objects/batchcontextlivecounter.ts
🧰 Additional context used
🧬 Code graph analysis (6)
src/plugins/objects/pathobject.ts (3)
ably.d.ts (3)
  • LiveMap (2377-2380)
  • BatchFunction (1672-1672)
  • BatchContext (3004-3010)
src/plugins/objects/objectid.ts (1)
  • LiveObjectType (4-4)
src/plugins/objects/rootbatchcontext.ts (1)
  • RootBatchContext (11-78)
src/plugins/objects/livemap.ts (4)
src/plugins/objects/realtimeobject.ts (1)
  • RealtimeObject (38-502)
src/plugins/objects/livecountervaluetype.ts (1)
  • LiveCounterValueType (20-97)
src/plugins/objects/livemapvaluetype.ts (1)
  • LiveMapValueType (34-166)
src/plugins/objects/objectmessage.ts (2)
  • ObjectMessage (360-464)
  • PrimitiveObjectValue (36-36)
src/plugins/objects/rootbatchcontext.ts (3)
src/plugins/objects/batchcontext.ts (1)
  • DefaultBatchContext (15-118)
ably.d.ts (3)
  • ObjectMessage (3614-3661)
  • Instance (3512-3518)
  • Value (2400-2400)
src/plugins/objects/realtimeobject.ts (1)
  • RealtimeObject (38-502)
src/plugins/objects/instance.ts (2)
ably.d.ts (5)
  • LiveMap (2377-2380)
  • BatchFunction (1672-1672)
  • LiveObject (2393-2393)
  • BatchContext (3004-3010)
  • LiveCounter (2385-2388)
src/plugins/objects/rootbatchcontext.ts (1)
  • RootBatchContext (11-78)
src/plugins/objects/batchcontext.ts (2)
src/plugins/objects/instance.ts (3)
  • InstanceEvent (22-25)
  • value (76-104)
  • DefaultInstance (27-217)
src/plugins/objects/rootbatchcontext.ts (2)
  • InstanceEvent (6-9)
  • RootBatchContext (11-78)
ably.d.ts (6)
src/plugins/objects/livemap.ts (1)
  • LiveMap (49-994)
objects.d.ts (2)
  • LiveMap (16-28)
  • LiveCounter (33-43)
src/plugins/objects/batchcontext.ts (1)
  • value (35-38)
src/plugins/objects/instance.ts (1)
  • value (76-104)
src/plugins/objects/pathobject.ts (1)
  • value (145-184)
src/plugins/objects/livecounter.ts (2)
  • value (64-67)
  • LiveCounter (15-301)
🪛 GitHub Actions: API Reference
ably.d.ts

[warning] 1-1: TypeDoc warnings: several members (e.g., LiveMapBatchContext.set, .set.key, .set.value, remove.__type, and other __type members) do not have documentation in ably.d.ts.

⏰ 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 (16.x)
  • GitHub Check: test-browser (firefox)
  • GitHub Check: test-browser (webkit)
  • GitHub Check: test-browser (chromium)
  • GitHub Check: test-node (20.x)
  • GitHub Check: test-node (18.x)

Comment on lines +38 to +41
if (this._queuedMessageConstructors.length > 0) {
await this._realtimeObject.publish(msgs);
}
} finally {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against empty publishes before flushing

If any queued constructor resolves to an empty ObjectMessage array, we still call publish([]) because the guard only checks _queuedMessageConstructors.length. RealtimeObject.publish reduces the array without an initial value, so an empty publish throws at runtime. Please gate on msgs.length > 0 (or otherwise skip the publish) to avoid a hard failure.

-      if (this._queuedMessageConstructors.length > 0) {
+      if (msgs.length > 0) {
         await this._realtimeObject.publish(msgs);
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (this._queuedMessageConstructors.length > 0) {
await this._realtimeObject.publish(msgs);
}
} finally {
if (msgs.length > 0) {
await this._realtimeObject.publish(msgs);
}
} finally {
🤖 Prompt for AI Agents
In src/plugins/objects/rootbatchcontext.ts around lines 38 to 41, the code only
checks this._queuedMessageConstructors.length before calling await
this._realtimeObject.publish(msgs), which can still call publish([]) if
constructors resolve to empty arrays; change the guard to skip publishing when
msgs is empty by checking msgs.length > 0 (or otherwise return/continue) before
calling publish so publish never receives an empty array.

…ject creation

This also removes the now-obsolete RealtimeObject.batch() API.

Resolves PUB-2065
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: 2

♻️ Duplicate comments (1)
src/plugins/objects/rootbatchcontext.ts (1)

36-40: Guard: skip publish([]) and publish only when msgs.length > 0

Current check can still call publish([]) when ctors resolve to empty arrays. Switch the guard to msgs.length.

-      if (this._queuedMessageConstructors.length > 0) {
-        await this._realtimeObject.publish(msgs);
-      }
+      if (msgs.length > 0) {
+        await this._realtimeObject.publish(msgs);
+      }
🧹 Nitpick comments (11)
scripts/moduleReport.ts (1)

344-346: Allowlist updated for RootBatchContext — good.

Nice catch adding src/plugins/objects/rootbatchcontext.ts to the Objects bundle allowlist.

  • Follow-up: if batchcontextlivecounter.ts / batchcontextlivemap.ts were removed, consider pruning them here as well to avoid “referenced in allowedFiles but does not contribute” errors at runtime.
ably.d.ts (3)

2685-2731: AnyPathObject shape looks coherent; minor generic hygiene.

Interface surface matches the runtime impl (get/value/instance/compact) and adds collection helpers. Consider avoiding generic name reuse downstream (see AnyOperations comment) to keep inference clearer across chained calls.


2981-2993: TypeDoc noise from mapped/conditional types — hide or alias.

The BatchContextOperations<T> mapped/conditional type often triggers TypeDoc “__type/this/args” warnings. Two options:

  • Mark it @internal and expose a named interface alias for docs, or
  • Wrap with an exported alias and document that alias instead.

This should address the current API Reference warnings.


3124-3219: AnyOperations: avoid generic shadowing and tighten key/value generics.

Methods reuse the generic name T, which collides with the interface’s ambient types and can confuse inference inside consumers.

Suggested tweaks:

  • Rename method-level generics (set/remove) to M (or similar).
  • Keep batch generic as-is (LiveObject-only) — OK.

Example:

- set<T extends Record<string, Value> = Record<string, Value>>(key: keyof T & string, value: T[keyof T]): Promise<void>;
+ set<M extends Record<string, Value> = Record<string, Value>>(key: keyof M & string, value: M[keyof M]): Promise<void>;
- remove<T extends Record<string, Value> = Record<string, Value>>(key: keyof T & string): Promise<void>;
+ remove<M extends Record<string, Value> = Record<string, Value>>(key: keyof M & string): Promise<void>;
src/plugins/objects/instance.ts (1)

194-206: Batch lifecycle looks correct; consider renaming the method generic.

Validation + RootBatchContext creation + flush() + finally close() is sound. To avoid generic shadowing with the class’s T, rename the method’s generic to U:

- async batch<T extends LiveObjectType = LiveObjectType>(fn: BatchFunction<T>): Promise<void> {
+ async batch<U extends LiveObjectType = LiveObjectType>(fn: BatchFunction<U>): Promise<void> {
-   fn(ctx as unknown as BatchContext<T>);
+   fn(ctx as unknown as BatchContext<U>);
src/plugins/objects/pathobject.ts (1)

374-391: PathObject.batch mirrors Instance.batch — good; rename method generic to avoid shadowing.

Control flow and error messaging (path-aware) are solid. Minor: rename the method generic to U for clarity:

- async batch<T extends LiveObjectType = LiveObjectType>(fn: BatchFunction<T>): Promise<void> {
+ async batch<U extends LiveObjectType = LiveObjectType>(fn: BatchFunction<U>): Promise<void> {
-   fn(ctx as unknown as BatchContext<T>);
+   fn(ctx as unknown as BatchContext<U>);
src/plugins/objects/rootbatchcontext.ts (2)

66-68: Minor: prefer const for wrappedInstance

Use const; the binding isn’t reassigned.

-      let wrappedInstance = new DefaultBatchContext(this._realtimeObject, instance, this);
+      const wrappedInstance = new DefaultBatchContext(this._realtimeObject, instance, this);

6-9: Type duplication: centralize InstanceEvent

InstanceEvent is also declared in batchcontext.ts. Consider defining it once (e.g., a shared types module) and importing to avoid drift.

src/plugins/objects/batchcontext.ts (3)

75-83: Capture objectId before queuing

Avoid reading this._instance.id() at flush-time; capture while context is open.

   set(key: string, value: Value): void {
     this._throwIfClosed();
     if (!(this._instance as DefaultInstance<Value>).isLiveMap()) {
       throw new this._client.ErrorInfo('Cannot set a key on a non-LiveMap instance', 92007, 400);
     }
-    this._rootContext.queueMessages(async () =>
-      LiveMap.createMapSetMessage(this._realtimeObject, this._instance.id()!, key, value as Primitive),
-    );
+    const objectId = this._instance.id()!;
+    this._rootContext.queueMessages(() =>
+      LiveMap.createMapSetMessage(this._realtimeObject, objectId, key, value as Primitive),
+    );
   }

85-93: Same here: capture objectId for remove()

Pre-read objectId to keep ctors simple and robust post-close.

   remove(key: string): void {
     this._throwIfClosed();
     if (!(this._instance as DefaultInstance<Value>).isLiveMap()) {
       throw new this._client.ErrorInfo('Cannot remove a key from a non-LiveMap instance', 92007, 400);
     }
-    this._rootContext.queueMessages(async () => [
-      LiveMap.createMapRemoveMessage(this._realtimeObject, this._instance.id()!, key),
-    ]);
+    const objectId = this._instance.id()!;
+    this._rootContext.queueMessages(async () => [
+      LiveMap.createMapRemoveMessage(this._realtimeObject, objectId, key),
+    ]);
   }

95-103: And for increment(): capture objectId and delta

Keeps all dependent values stable when ctors run.

   increment(amount?: number): void {
     this._throwIfClosed();
     if (!(this._instance as DefaultInstance<Value>).isLiveCounter()) {
       throw new this._client.ErrorInfo('Cannot increment a non-LiveCounter instance', 92007, 400);
     }
-    this._rootContext.queueMessages(async () => [
-      LiveCounter.createCounterIncMessage(this._realtimeObject, this._instance.id()!, amount ?? 1),
-    ]);
+    const objectId = this._instance.id()!;
+    const delta = amount ?? 1;
+    this._rootContext.queueMessages(async () => [
+      LiveCounter.createCounterIncMessage(this._realtimeObject, objectId, delta),
+    ]);
   }
📜 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 8b57e60 and 1244065.

📒 Files selected for processing (11)
  • ably.d.ts (9 hunks)
  • scripts/moduleReport.ts (1 hunks)
  • src/plugins/objects/batchcontext.ts (1 hunks)
  • src/plugins/objects/batchcontextlivecounter.ts (0 hunks)
  • src/plugins/objects/batchcontextlivemap.ts (0 hunks)
  • src/plugins/objects/instance.ts (4 hunks)
  • src/plugins/objects/livemap.ts (3 hunks)
  • src/plugins/objects/pathobject.ts (4 hunks)
  • src/plugins/objects/realtimeobject.ts (0 hunks)
  • src/plugins/objects/rootbatchcontext.ts (1 hunks)
  • test/realtime/objects.test.js (17 hunks)
💤 Files with no reviewable changes (3)
  • src/plugins/objects/realtimeobject.ts
  • src/plugins/objects/batchcontextlivemap.ts
  • src/plugins/objects/batchcontextlivecounter.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/plugins/objects/livemap.ts
🧰 Additional context used
🧬 Code graph analysis (6)
src/plugins/objects/instance.ts (3)
ably.d.ts (5)
  • LiveMap (2377-2380)
  • BatchFunction (1672-1672)
  • LiveObject (2393-2393)
  • BatchContext (3004-3010)
  • LiveCounter (2385-2388)
src/plugins/objects/livemap.ts (1)
  • LiveMap (49-994)
src/plugins/objects/rootbatchcontext.ts (1)
  • RootBatchContext (11-78)
test/realtime/objects.test.js (2)
src/plugins/objects/livecounter.ts (1)
  • LiveCounter (15-301)
src/plugins/objects/livemap.ts (1)
  • LiveMap (49-994)
src/plugins/objects/batchcontext.ts (4)
src/plugins/objects/instance.ts (3)
  • InstanceEvent (22-25)
  • value (76-104)
  • DefaultInstance (27-217)
src/plugins/objects/rootbatchcontext.ts (2)
  • InstanceEvent (6-9)
  • RootBatchContext (11-78)
src/plugins/objects/livecounter.ts (2)
  • value (64-67)
  • LiveCounter (15-301)
src/plugins/objects/livemap.ts (1)
  • LiveMap (49-994)
src/plugins/objects/pathobject.ts (3)
ably.d.ts (3)
  • LiveMap (2377-2380)
  • BatchFunction (1672-1672)
  • BatchContext (3004-3010)
src/plugins/objects/livemap.ts (1)
  • LiveMap (49-994)
src/plugins/objects/rootbatchcontext.ts (1)
  • RootBatchContext (11-78)
src/plugins/objects/rootbatchcontext.ts (3)
src/plugins/objects/batchcontext.ts (1)
  • DefaultBatchContext (15-118)
ably.d.ts (3)
  • ObjectMessage (3614-3661)
  • Instance (3512-3518)
  • Value (2400-2400)
src/plugins/objects/realtimeobject.ts (1)
  • RealtimeObject (38-502)
ably.d.ts (3)
src/plugins/objects/batchcontext.ts (1)
  • value (35-38)
src/plugins/objects/instance.ts (1)
  • value (76-104)
src/plugins/objects/pathobject.ts (1)
  • value (145-184)
🪛 GitHub Actions: API Reference
ably.d.ts

[warning] 1-1: TypeDoc warning: member lacks documentation in ably.d.ts (e.g., set, remove, __type, __type.this, __type.args, etc.).

⏰ 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 (18.x)
  • GitHub Check: test-node (16.x)
  • GitHub Check: test-node (20.x)
🔇 Additional comments (7)
ably.d.ts (3)

1666-1672: BatchFunction is sync-only — LGTM.

Typing BatchFunction<T> = (ctx) => void correctly prevents async functions at compile time and matches the runtime batching contract.


3039-3083: Batch semantics docs are clear — LGTM.

The “added to current batch if called within batch()” behavior is well documented for LiveMapOperations.set/remove.


3088-3118: Counter ops batch semantics — LGTM.

Docs mirror the map ops; errors for wrong-type calls are explicit.

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

118-125: keys(): safe early return for non-Map — LGTM.

Returning an empty iterator when not a LiveMap avoids accidental throws.


208-216: Internal helpers are useful for narrowing — LGTM.

isLiveMap() / isLiveCounter() aid internal checks without leaking API surface.

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

237-254: keys(): tolerant resolution — LGTM.

Gracefully handling non-Map resolutions and 92005 path errors matches the rest of the API’s undefined/empty-iterator convention.

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

31-45: Flush safety verified for queued message constructors They only call this._instance.id() and static message-creator methods, avoiding any guarded BatchContext APIs.

Comment on lines +2750 to +2763
* BatchContextBase defines the set of common methods on a BatchContext
* that are present regardless of the underlying type.
*/
interface BatchContextBase {
/**
* Get the object ID of the underlying instance.
*
* If the underlying instance at runtime is not a {@link LiveObject}, returns `undefined`.
*
* @experimental
*/
id(): string | undefined;
}

Copy link

@coderabbitai coderabbitai bot Nov 7, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

PrimitiveBatchContext should also include BatchContextBase for a uniform API.

Docs say BatchContextBase methods are “present regardless of the underlying type,” but PrimitiveBatchContext doesn’t extend it, so id() is missing on primitive contexts (where it should return undefined). Align types for consistency.

Suggested diff:

-export interface PrimitiveBatchContext<T extends Primitive = Primitive> {
+export interface PrimitiveBatchContext<T extends Primitive = Primitive> extends BatchContextBase {
   /**
    * Get the underlying primitive value.
    * ...
   */
   value(): T | undefined;
   /**
    * Get a JavaScript object representation of the primitive value.
    * ...
   */
   compact(): CompactedValue<T> | undefined;
 }

Also applies to: 2866-2884

🤖 Prompt for AI Agents
In ably.d.ts around lines 2750-2763 (and similarly 2866-2884),
PrimitiveBatchContext does not extend BatchContextBase so it lacks the id()
method; update the PrimitiveBatchContext interface to extend BatchContextBase
(or include id(): string | undefined) so primitive contexts expose id()
returning undefined when not a LiveObject, preserving the uniform API across
BatchContext types.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

primitive values do not have ids, so it should not

Copy link

Choose a reason for hiding this comment

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

@VeskeR, you're absolutely right. The current design is intentional and consistent throughout the codebase.

PrimitiveBatchContext doesn't extend BatchContextBase for the same reason that PrimitiveInstance doesn't extend InstanceBase — primitive values are snapshots that don't have object IDs, so exposing an id() method (even one that returns undefined) would be semantically misleading.

Looking at the type definitions, this pattern is applied consistently:

  • Collection types (LiveMapBatchContext, LiveCounterBatchContext) expose id() because they wrap tracked objects
  • Primitive wrappers (PrimitiveBatchContext, PrimitiveInstance) omit id() because they represent values, not identifiable objects
  • Generic fallback types (AnyBatchContext, AnyInstance) include id() because they must handle all cases

This design makes the API clearer by only exposing id() where it's conceptually meaningful. The CodeRabbit suggestion appears to be a false positive.


✏️ Learnings added
Learnt from: VeskeR
Repo: ably/ably-js PR: 2107
File: ably.d.ts:2750-2763
Timestamp: 2025-11-07T17:53:25.083Z
Learning: In ably.d.ts, PrimitiveBatchContext and PrimitiveInstance intentionally do not extend BatchContextBase or InstanceBase (respectively) and do not expose an id() method. This is by design: primitive values are snapshots/values without object identity, so omitting id() makes the API semantically clearer. Only collection types (LiveMapBatchContext, LiveCounterBatchContext, LiveMapInstance, LiveCounterInstance) and generic fallback types (AnyBatchContext, AnyInstance) should expose id().

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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.

2 participants