Skip to content

[AIT-30] BatchContext and batch API on PathObject and Instance#471

Open
lawrence-forooghian wants to merge 1 commit into
AIT-30/liveobjects-path-based-api-specfrom
AIT-30/liveobjects-batch-api
Open

[AIT-30] BatchContext and batch API on PathObject and Instance#471
lawrence-forooghian wants to merge 1 commit into
AIT-30/liveobjects-path-based-api-specfrom
AIT-30/liveobjects-batch-api

Conversation

@lawrence-forooghian
Copy link
Copy Markdown
Collaborator

Note: This PR is based on #427.

Split from #427 to reduce the review burden of that PR (since we don't need to do the batch API for the initial implementation of the path-based API).

@lawrence-forooghian lawrence-forooghian added the live-objects Related to LiveObjects functionality. label May 12, 2026
@lawrence-forooghian lawrence-forooghian changed the title BatchContext and batch API on PathObject and Instance [AIT-30] BatchContext and batch API on PathObject and Instance May 12, 2026
@lawrence-forooghian lawrence-forooghian force-pushed the AIT-30/liveobjects-path-based-api-spec branch 2 times, most recently from 035aef9 to babc296 Compare May 12, 2026 20:09
@sacOO7 sacOO7 requested a review from Copilot May 13, 2026 10:44
- `(RTPO21b)` Returns a stream or iterable that yields `PathObjectSubscriptionEvent` objects, using the idiomatic construct for the language (e.g. async iterators, channels, flows, or async sequences)
- `(RTPO21c)` Internally wraps `PathObject#subscribe` ([RTPO19](#RTPO19)), converting the callback-based subscription into the appropriate streaming or iterable pattern
- `(RTPO22)` `PathObject#batch` function:
- `(RTPO22a)` Expects a synchronous function `fn` that receives a `BatchContext` as its argument
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
- `(RTPO22a)` Expects a synchronous function `fn` that receives a `BatchContext` as its argument
- `(RTPO22a)` Accepts a synchronous anonymous function as a argument. This anonymous function is called with `BatchContext` as its argument internally.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

https://ably.com/docs/liveobjects/concepts/path-object#batch-multiple-updates
Similarly, other spec points might not be explicit, will do a full review soon

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the LiveObjects path-based API specification by introducing a batching mechanism for object mutations, enabling multiple map/counter operations to be queued and then published atomically as a single channel message.

Changes:

  • Adds PathObject#batch and Instance#batch spec points, describing lifecycle and permission requirements.
  • Introduces the BatchContext / internal RootBatchContext specification for queuing and flushing batched operations.
  • Reserves the new RTBC spec point prefix in the main features index.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
specifications/objects-features.md Specifies batch APIs for PathObject/Instance and defines BatchContext + internal batching flush mechanics.
specifications/features.md Updates reserved spec point prefixes to include RTBC.
Comments suppressed due to low confidence (1)

specifications/objects-features.md:1004

  • RTINS19 similarly specifies flush/close only after fn returns. Please define behavior when fn throws (and, where applicable, when an async function is passed): queued operations should not be flushed/published, but the RootBatchContext should still be closed so the BatchContext and any children become unusable and consistently throw the “batch is closed” error.
  - `(RTINS19d)` Creates a `RootBatchContext` ([RTBC16](#RTBC16)) wrapping this `Instance`
  - `(RTINS19e)` Executes `fn`, passing the `BatchContext` as argument
  - `(RTINS19f)` After `fn` returns, flushes the `RootBatchContext` ([RTBC16d](#RTBC16d)) to publish all queued operations atomically
  - `(RTINS19g)` The `RootBatchContext` is closed after flush completes, regardless of success or failure

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- `(RTPO22)` `PathObject#batch` function:
- `(RTPO22a)` Expects a synchronous function `fn` that receives a `BatchContext` as its argument
- `(RTPO22b)` Requires the `OBJECT_PUBLISH` channel mode to be granted per [RTO2](#RTO2)
- `(RTPO22c)` Resolves the path to a `LiveObject` using the internal path resolution procedure. If the path does not resolve to a `LiveObject`, the library must throw an `ErrorInfo` error with `statusCode` 400 and `code` 92007
Comment on lines +921 to +922
- `(RTPO22f)` After `fn` returns, flushes the `RootBatchContext` ([RTBC16d](#RTBC16d)) to publish all queued operations atomically
- `(RTPO22g)` The `RootBatchContext` is closed after flush completes, regardless of success or failure
- `(RTBC4)` `BatchContext#get` function:
- `(RTBC4a)` Expects a `key` `String` argument
- `(RTBC4b)` If the batch is closed, the library must throw an `ErrorInfo` error with `statusCode` 400 and `code` 40000
- `(RTBC4c)` Delegates to `Instance#get` ([RTINS5](#RTINS5)) on the underlying `Instance`. If the result is undefined, returns undefined
- `(RTBC14a1)` `amount` `Number` (optional) - the amount by which to increment the counter value. Defaults to 1
- `(RTBC14b)` If the batch is closed, the library must throw an `ErrorInfo` error with `statusCode` 400 and `code` 40000
- `(RTBC14c)` If the wrapped value is not a `LiveCounter`, the library must throw an `ErrorInfo` error with `statusCode` 400 and `code` 92007
- `(RTBC14d)` Queues a message constructor on the `RootBatchContext` that, when executed, creates an `ObjectMessage` for a `COUNTER_INC` operation in the same manner as `LiveCounter#increment` ([RTLC12](#RTLC12))
- `(RTBC16a)` Maintains an internal `wrappedInstances` map that memoizes `BatchContext` wrappers by `objectId`
- `(RTBC16b)` Maintains an internal `queuedMessageConstructors` list of deferred message constructor functions. Some `ObjectMessages` require asynchronous I/O during construction (e.g. generating an `objectId` for nested value types), so message constructors are queued during synchronous batch method calls and executed on flush
- `(RTBC16c)` `wrapInstance` function: wraps an `Instance` in a `BatchContext`. If the `Instance` has an `objectId` and a wrapper for that `objectId` already exists in `wrappedInstances`, the existing wrapper is returned. Otherwise, a new `BatchContext` is created and stored in `wrappedInstances`
- `(RTBC16d)` `flush` function: closes the batch context, executes all queued message constructors, flattens the resulting `ObjectMessages` into a single array, and publishes them using `RealtimeObject#publish` ([RTO15](#RTO15)). If there are no queued messages, no publish is performed
- `(RTBC15a1)` `amount` `Number` (optional) - the amount by which to decrement the counter value. Defaults to 1
- `(RTBC15b)` If the batch is closed, the library must throw an `ErrorInfo` error with `statusCode` 400 and `code` 40000
- `(RTBC15c)` If the wrapped value is not a `LiveCounter`, the library must throw an `ErrorInfo` error with `statusCode` 400 and `code` 92007
- `(RTBC15d)` Delegates to `BatchContext#increment` ([RTBC14](#RTBC14)) with the negated `amount`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

live-objects Related to LiveObjects functionality.

Development

Successfully merging this pull request may close these issues.

4 participants