Skip to content

Upgrade to protocol v5: message update/delete/append, PublishResult, Stats rewrite - #450

Open
matt423 wants to merge 29 commits into
integration/v2from
protocal-v5
Open

matt423 wants to merge 29 commits into
integration/v2from
protocal-v5

Conversation

@matt423

@matt423 matt423 commented Mar 2, 2026

Copy link
Copy Markdown

Summary

Upgrades the SDK from protocol version 2 to 5, adding message lifecycle operations and bringing the Stats model in line with the current Ably specification.

  • Update, delete, append messages on REST (channel.update_message, channel.delete_message, channel.append_message) and Realtime channels (RSL15, RTL32)
  • PublishResult returned from publish with message serials (RSL1n, PBR2a)
  • UpdateDeleteResult returned from update/delete/append with version serial (UDR2a)
  • MessageOperation model for operation metadata on update/delete/append (MOP)
  • Complete Message::ACTION enum with all TM5 values (message_create, message_update, message_delete, meta, message_summary, message_append)
  • Stats model rewritten to match spec v2.1+ (TS12): flat entries hash replaces nested accessors; all Stats sub-types removed (TS4–TS9 deleted from spec)

Breaking changes

  • Rest::Channel#publish returns PublishResult instead of Boolean
  • Realtime::Channel#publish deferrable yields PublishResult instead of Message/Array<Message>
  • Stats nested accessors (all, inbound, outbound, persisted, connections, channels, api_requests, token_requests) replaced by flat entries hash (TS12r) — use stat.entries['messages.all.all.count'] instead of stat.all.all.count
  • Stats::MessageTraffic, Stats::MessageTypes, Stats::MessageCount, Stats::ConnectionTypes, Stats::RequestCount, Stats::ResourceCount removed
  • Stats#interval_granularity replaced by Stats#unit (TS12c)

New models

Model Spec Purpose
PublishResult RSL1n, PBR2a Wraps publish response with serials array
UpdateDeleteResult UDR2a Wraps update/delete/append response with version_serial
MessageOperation MOP Operation metadata (client_id, description, metadata)

REST examples

client = Ably::Rest::Client.new(key: 'key')
channel = client.channels.get('chat')

# Publish — now returns PublishResult with serials
result = channel.publish('greeting', 'Hello!')
serial = result.serials.first  #=> "01826232781:0"

# Batch publish
result = channel.publish([
  { name: 'chat', data: 'Hello' },
  { name: 'chat', data: 'World' }
])
result.serials  #=> ["01826232781:0", "01826232781:1"]

# Update
channel.update_message({ serial: serial, data: 'Edited' })

# Delete
channel.delete_message({ serial: serial })

# Append
channel.append_message({ serial: serial, data: { reaction: 'heart' } })

# With operation metadata
channel.update_message(
  { serial: serial, data: 'Fixed' },
  { description: 'Typo fix', metadata: { 'reason' => 'spelling' } }
)

Realtime examples

EventMachine.run do
  client = Ably::Realtime::Client.new(key: 'key')
  channel = client.channels.get('chat')

  channel.attach do
    channel.publish('greeting', 'Hello!') do |result|
      serial = result.serials.first

      channel.update_message({ serial: serial, data: 'Edited' }) do |update_result|
        puts update_result.version_serial
        client.connection.close { EventMachine.stop }
      end
    end
  end
end

Test plan

  • All 1261 unit tests pass
  • All 2516 acceptance tests pass (0 failures, 5 pending)
  • SPEC.md regenerated with zero failure markers

@lmars

lmars commented Mar 24, 2026

Copy link
Copy Markdown
Member

@matt423 I'm going to review this, but just based on the description:

Breaking changes

I don't think we can make this change in a patch release if it includes breaking changes, were you intending this to be a major version bump? If not, then I think we need to do it in a backwards compatible way similar to how we're doing it for Go in ably/ably-go#697 (i.e. by adding a new PublishWithResult method which returns PublishResult whilst leaving Publish the same, and keeping stats the same by sticking with protocol v2 for stats).

umair-ably and others added 17 commits September 1, 2026 16:18
Implements the PubSub package split (PDR-091b) for the Ruby SDK:

- Move the implementation into core/ as the ably-pubsub-core gem, an
  internal package that only Ably packages depend on. The ably-common
  submodule moves with it.
- Add server/ as the ably-pubsub-server gem, whose factory functions
  are the only recommended entry points:
  Ably::PubSub::Server.create_http_client and .create_realtime_client.
  Ruby is a server-only SDK under PDR-091b2, so there is no device gem.
- The factories stamp the side-declaring agent entry
  ably-pubsub-server/<version> via a new additive :agents client
  option; the caller's own agent entries are preserved and the side
  entry wins a collision on its own identifier. The -server suffix is
  what MAU classification reads, so specs assert it loudly.
- Core and server are versioned in lockstep at 2.0.0, with the server
  gem pinning the core dependency to the exact version.
- The old ably gemspec is removed; the ably gem is maintained from a
  maintenance branch per the 1-year support window, not from here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ruby 3.3.11 and similar patch versions no longer match the previous
single-digit \d.\d.\d pattern, which made this spec fail on current
CI runners regardless of this branch's changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Matches the entry's registration in the ably-common agents registry
(ably/ably-common#361): the side entry is a pure flag, like browser.
Under lockstep versioning a version on the flag always duplicates the
ably-ruby entry beside it, which keeps carrying identity, version and
support status. Wire shape:

  ably-ruby/2.0.0 ruby/3.3.0 ably-pubsub-server

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- release.yml publishes ably-pubsub-core and ably-pubsub-server at the
  same version via RubyGems trusted publishing (OIDC, no long-lived
  keys). A pre-flight fails before anything is pushed if the version
  input, either gem's version constant, or the server gem's exact core
  pin disagree. Partial releases fail reversibly: re-running with the
  same version skips already-published gems.
- CONTRIBUTING.md documents the two-gem layout and the new release
  process; the manual rake release and the ably-ruby-rest step are
  gone (both legacy gems are maintenance-only per PDR-091b).
- UPDATING.md gains the 1.x -> 2.0 migration section with the
  old-name -> new-name mapping table, marked draft pending PDR-091d.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runs the same version-consistency assertions as release.yml's
pre-flight and builds both gems on every PR, so a version-file or
gemspec regression surfaces immediately instead of at release time.
Publishes nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The family identifier follows the package split, per the agent
identifier convention proposed on PDR-091b2 and registered in
ably/ably-common#361: because it flips exactly at the split and the
maintenance branch is never touched, the identifier alone partitions
the fleet - ably-ruby/* is legacy-gem traffic, ably-pubsub-ruby/* is
new-package traffic. It names the family rather than any one published
gem; the side a client declares travels as the separate versionless
entry stamped by the server factories:

  ably-pubsub-ruby/2.0.0 ruby/3.3.0 ably-pubsub-server

Requires the ably-pubsub-ruby registry entry (ably/ably-common#361)
before any release from this branch. Mirrors ably/ably-pubsub-js#2297 and
ably/ably-pubsub-java#1233.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Asserts the two gemspecs' built file lists directly: no load-path file
ships in both gems, core does not ship the server subtree or the
ably-common submodule, server ships only its own subtree under lib, and
the versions and server->core pin agree. The release pre-flight checks
version agreement, but nothing else asserts gem contents, so a
packaging mistake would otherwise surface only after publish.

Adapted from the packaging specs in #452.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ably.com/docs links only the newest package's API reference, so users
still on the maintenance-only ably gem land here; the front-page notice
points them at the frozen old reference and the migration guide.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Split Pub/Sub SDK into core and server gems
Add lockstep release workflow and migration docs
Ably::Rest::* becomes Ably::PubSub::Http::* and Ably::Realtime::*
becomes Ably::PubSub::Realtime::*, so the namespace matches the
Ably::PubSub::Server.create_http_client factory that constructs these
clients. Files move to lib/ably/pubsub/{http,realtime} to match.

- Add lib/ably/pubsub.rb to open the namespace. It is required before
  the client files because 22 of them declare into it with the compact
  `module Ably::PubSub::Http` form, which needs Ably::PubSub to exist.
- Rename Ably::Internal.create_rest_client to create_http_client, and
  the spec/{unit,acceptance}/rest directories to http.
- Narrow the packaging assertion that core ships nothing under
  lib/ably/pubsub to the server subtree alone. Both gems now write into
  that prefix, which makes the no-overlap rule load-bearing rather than
  incidental.
- Fix five YARD links to Ably::Models::Rest::Channel, a namespace that
  never existed.
- Rewrite the 2.0 section of UPDATING.md around before/after examples
  and drop its draft status.

Ably::Auth, Ably::Models::*, Ably::Exceptions::*, Ably::Logger and every
client option, including :rest_host, are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ttypic and others added 5 commits September 22, 2026 08:56
Spec 6.3.0 replaces RTS4a, under which Channels#release detaches the
channel before removing it, with RTS4c-e: release now removes a channel
only if it is initialized, detached or failed, and otherwise raises
error 90011 (status 400) and leaves the channel alone, so that a channel
is never released while it could still receive messages from Ably.

1.x logs a deprecation warning for this case (RTS4b); this is the
breaking change for 2.0, documented in UPDATING.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Raise when releasing a realtime channel that isn't detached (RTS4c-e)
Version 2 adopts the endpoint routing model from the spec (REC1/REC2),
so clients connect to *.realtime.ably.net, with fallbacks on
*.fallback.ably-realtime.com, instead of the rest/realtime.ably.io
domains.

- Add the :endpoint option (default 'main'). It accepts a routing
  policy name ('acme'), a non-production policy ('nonprod:sandbox') or
  a hostname. REST and realtime now share one primary domain
- Remove :environment, :rest_host, :realtime_host, :ws_host and
  :fallback_hosts_use_default
- Derive default fallback hosts from the endpoint: none for a
  hostname, and explicit :fallback_hosts still override them. A custom
  port no longer disables fallbacks
- client.endpoint now returns the option value, the connection URI
  moves to client.uri, and client.primary_domain replaces
  environment/custom_host and the DOMAIN constants
- Move endpoint resolution into Ably::Internal
- Connect the WebSocket using the unbracketed hostname so IPv6 literal
  endpoints work
- Point specs at ABLY_ENDPOINT (default nonprod:sandbox) and cover the
  endpoint and fallback resolution cases
- Document the migration in UPDATING.md

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
replace host options with endpoint client option
@ttypic
ttypic changed the base branch from main to integration/v2 October 13, 2026 15:14
matt423 and others added 7 commits October 13, 2026 16:17
Implements message update functionality following the Ably specification
(RSL15, RTL32, TM2, TM5, MOP, UDR). Bumps protocol version from 2 to 5
to enable ACK responses with publish result serials.

- Add Message ACTION enum (message_create, message_update) and new
  accessors (action, serial, version, created_at, updated_at)
- Add MessageOperation and UpdateDeleteResult model types
- Add REST channel#update_message via PATCH /channels/{name}/messages/{serial}
- Add Realtime channel#update_message via MESSAGE ProtocolMessage
- Add ProtocolMessage#res accessor for protocol v5 ACK results
- Update ACK handling to route UpdateDeleteResult to update deferrables
- Add REST client#patch convenience method
publish now returns PublishResult containing a serials array that maps
1:1 to the published messages, per spec RSL1n / PBR2a.

REST: publish returns PublishResult instead of Boolean. This is
backward-compatible since PublishResult is truthy.

Realtime: publish callbacks now receive PublishResult instead of the
Message object. This is a breaking change for callers that access
message properties (name, data, etc.) in publish callbacks. This
aligns with the spec and matches the JS SDK behavior.

The ACK handler extracts serials from the protocol v5 res field
(TR4s) and constructs per-message PublishResults, which are
aggregated for multi-message publishes in the Publisher module.
Extract shared send_message_action helper from update_message and add
delete_message using MESSAGE_DELETE action. Extend ACTION enum with all
TM5 values (message_delete, meta, message_summary, message_append).
Update ACK handler to return UpdateDeleteResult for delete/append actions.
Add append_message using MESSAGE_APPEND action via the shared
send_message_action helper. Same signature and behavior as
update_message and delete_message.
The protocol version change from 2 to 5 changed the stats API response
format from nested objects to a flat entries dictionary (TS12r). Update
the Stats model to be spec-compliant:

- Replace nested accessors (all, inbound, outbound, etc.) with flat
  entries hash
- Add unit, in_progress, schema, app_id accessors (TS12c/q/s/t)
- Remove deleted sub-types: MessageTraffic, MessageTypes, MessageCount,
  ConnectionTypes, RequestCount, ResourceCount (TS4-TS9)
- Delete stats_types.rb
Protocol version 5 changes what publish returns (PublishResult with
serials, on both HTTP and realtime channels) and the Stats model shape
(flat entries hash, TS12). Both are breaking for 1.x users, so describe
them in UPDATING.md alongside the rest of the 2.0 migration.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Add after(:example) hook to clear stale realtime_clients and stop
  EM reactor between tests, preventing leaked state from crashed or
  timed-out tests poisoning subsequent EM-dependent tests
- Switch paginated result specs from manual include + run_reactor to
  :event_machine metadata tag so they get proper before/around/after
  lifecycle hooks

This branch was successfully deployed

2 active deployments
staging/pull/450/docs — d8937baf Deployed Oct 13, 2026 by github-actions[bot]
staging/pull/450/features — d8937baf Deployed Oct 13, 2026 by github-actions[bot]
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.

5 participants