Skip to content

Add typed-SDK LiveObjects API spec section (RTTS1-RTTS10) - #491

Open
sacOO7 wants to merge 14 commits into
mainfrom
feature/liveobjects-cross-sdk-types-spec
Open

Add typed-SDK LiveObjects API spec section (RTTS1-RTTS10)#491
sacOO7 wants to merge 14 commits into
mainfrom
feature/liveobjects-cross-sdk-types-spec

Conversation

@sacOO7

@sacOO7 sacOO7 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Introduce a new ### Typed-SDK public API (statically-typed languages) section in objects-features.md
    (RTTS1–RTTS10) that normatively partitions PathObject and Instance across a class hierarchy for SDKs whose host
    language is strongly typed (Java, Swift, Kotlin). The dynamic-SDK form (JavaScript) is unchanged; the new section is
    purely a refinement of the existing RTPO1a / RTINS1a allowance for type-specific sub-classes.
  • Add 6 minimal inbound cross-refs (RTPO1b, RTINS1b, RTPO13g, RTINS10c, RTO23f, RTINS11c) so readers of
    any existing RTPO* / RTINS* clause find the typed-SDK partition. Tighten RTINS11c so the non-null compactJson
    invariant on Instance applies universally (Instance is always bound — the existing JS behaviour already satisfies
    this).
  • Append a typed-SDK IDL block to the Interface Definition section, alongside (not replacing) the dynamic-SDK form,
    with a banner that explains the relationship.
  • Add a # Typed-SDK LiveObjects API section to api-docstrings.md with language-agnostic docstrings for every new
    type, each citing its RTTS* clause.

Key normative decisions

  • subscribe() restructure (RTTS7b). subscribe is removed from the base typed Instance and lives only on
    LiveMapInstance / LiveCounterInstance. This turns the existing runtime throw of RTINS16c on primitives into a
    compile-time error and matches the official docs which state that subscribe() is "only available on LiveMap and
    LiveCounter instances".
  • SubscriptionEvent payload (RTTS3d, RTTS10a, RTTS10b). Typed SDKs MUST deliver both object and message
    fields per RTPO19e / RTINS16e — a subscription-event type that omits message is non-conformant.
  • getType() semantics (RTTS4b, RTTS8a). PathObject#getType() returns UNKNOWN on unresolved paths (silent,
    consistent with RTPO3c1); Instance#getType never returns UNKNOWN in normal operation (Instance is bound).
  • compact deferred (RTTS3f / RTTS7d). Typed SDKs are not required to implement compact; compactJson is
    sufficient.
  • channel.objects.get() returns LiveMapPathObject (RTTS6d / RTO23f).
  • Universal Instance#compactJson non-null invariant (RTINS11c). One-line tightening of the dynamic spec;
    reflects a property that already held in practice.

@github-actions
github-actions Bot temporarily deployed to staging/pull/491 June 9, 2026 12:26 Inactive
Introduce a new `### Typed-SDK public API (strongly-typed languages)`
section in `objects-features.md` that normatively partitions the
`PathObject` and `Instance` API surfaces across a class hierarchy for
SDKs in strongly-typed host languages (Java, Swift, Kotlin). The
dynamic-SDK form (JavaScript) is unchanged.
@sacOO7
sacOO7 force-pushed the feature/liveobjects-cross-sdk-types-spec branch from eea4f9c to 454fb1c Compare June 10, 2026 09:16
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 June 10, 2026 09:16 Inactive
Base automatically changed from rename/liveobjects-map-counter-interfaces to main June 11, 2026 10:07
- Normalize RFC 2119 keywords to the file's lowercase convention
  throughout RTTS1-RTTS10 and the new hook clauses
- Remove reliance on user-facing docs as authority in RTTS7b; mark its
  rationale non-normative
- Reword RTTS3b: RTTS6e constrains the failure behaviour of `instance`
  on primitive sub-classes, it does not narrow the return type
- Restructure RTINS11c as a normative requirement plus a non-normative
  explanation of the underlying invariant
- Rewrite RTTS2a9 as a pure enum-member definition, leaving UNKNOWN
  behaviour to RTTS4b3 and RTTS8a (DRY)
- Replace "strongly typed" with "statically typed" (the distinction
  drawn is static vs dynamic typing)
- Retitle RTTS4 to avoid "extension methods" terminology
- Remove trailing full stops from new hook clauses to match file style
- IDL: drop redundant base-Instance subscribe note; annotate subscribe
  on LiveMapInstance/LiveCounterInstance with RTTS7b
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 June 11, 2026 10:45 Inactive
Read methods return live graph objects (InternalLiveMap/
InternalLiveCounter); object-valued writes accept only the LiveCounter/
LiveMap creation value types. This is intentional (reference assignment
was removed when the value-type API replaced RTLM20e5a) but was easy to
misread after the value types took over the LiveMap/LiveCounter names.

- Add a non-normative intro to the InternalLiveMap section explaining
  the asymmetry
- Anchor RTLM20a3's LiveCounter/LiveMap to RTLCV1/RTLMV1
- Add RTLMV4c1: graph objects and their PathObject/Instance wrappers
  are not accepted as values and should be rejected with 40013
- IDL: annotate InternalLiveMap#set and the LiveCounter/LiveMap value
  type class declarations to distinguish them from graph objects

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 June 18, 2026 10:20 Inactive
…e is not present

Updated respective sub-clauses
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 June 18, 2026 10:47 Inactive
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 June 18, 2026 11:02 Inactive
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 June 18, 2026 11:29 Inactive

Copilot AI left a comment

Copy link
Copy Markdown

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 updates the LiveObjects specification to normatively define a typed (statically/strongly-typed) SDK public API surface for PathObject and Instance, including a class hierarchy partition (RTTS1–RTTS10) and supporting cross-references, while keeping the dynamic-SDK form intact.

Changes:

  • Adds a new normative “Typed-SDK public API” section (RTTS1–RTTS10) describing the typed class hierarchy and method partitioning for PathObject/Instance.
  • Introduces minimal inbound cross-references from existing RTPO* / RTINS* / RTO* clauses to the new typed partition, and tightens the universal Instance#compactJson non-null invariant (RTINS11c).
  • Extends the Interface Definition section with an appended typed-SDK IDL block and clarifies creation-value vs graph-object types for LiveMap/LiveCounter.

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

Comment thread specifications/objects-features.md
Comment thread specifications/objects-features.md Outdated
Comment thread specifications/objects-features.md Outdated
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 June 18, 2026 12:15 Inactive
@sacOO7
sacOO7 force-pushed the feature/liveobjects-cross-sdk-types-spec branch from e21cb4e to c0c80d2 Compare June 18, 2026 12:16
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 June 18, 2026 12:16 Inactive
@sacOO7
sacOO7 marked this pull request as ready for review June 18, 2026 12:19
…lue() semantics

- RTTS5d2: distinguish path-not-resolved (92005, RTPO3c2) from type-mismatch
  (92007, RTPO15e/16e/17e/18e) for typed PathObject write/terminal casts
- RTTS6b/6c: describe filtered value() semantics (return null unless the
  resolved value is of the expected category) instead of plain delegation to
  RTPO7, which also returns counter/primitive values

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 June 18, 2026 14:24 Inactive
…alue() note

- RTTS3c: clarify typed PathObject sub-classes do NOT narrow compactJson
  (best-effort cast, nullable, type not guaranteed) - unlike Instance (RTTS7a),
  whose fixed type makes covariant narrowing sound. Matches ably-java, where
  compactJson is declared only on the base PathObject
- RTTS7a: drop the stale "narrow per RTTS3c" reference; state narrowing is sound
  for Instance because its type is fixed, contrasting PathObject
- RTTS6g: add non-normative note that the type-filtered value() on
  LiveCounterPathObject/primitive PathObjects is a typed-SDK refinement with no
  RTPO7 (dynamic) equivalent

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 June 18, 2026 14:58 Inactive
// place of the monomorphic `class PathObject` / `class Instance` above.
// ==========================================================================

enum ValueType: // RTTS2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I know we discussed this name plenty already, but I don't remember whether we discussed TypeOfValue as an option? The term "value type" already has a meaning and I think especially in Swift it might be a bit confusing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair point — TypeOfValue (or similar) would avoid the "value type" collision, especially in Swift. Treating it as a separate naming decision since renaming touches RTTS2, the IDL, ValueType.java and the docs; will follow up on it independently rather than bundling it into this change.

Comment thread specifications/objects-features.md Outdated
asJsonArray: JsonArrayInstance // RTTS9c

class LiveMapInstance extends Instance: // RTTS10a
id: String // RTINS3a (narrowed to non-nullable)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Narrowed when compared to what? Instance? Instance doesn't have an id property in the IDL; if that's intentional then "narrowed" isn't the term here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in c5cc6b8 — dropped narrowed; the IDL comment now reads (non-nullable; id exists only on LiveObject sub-types), and the RTTS10a prose states the plain return type.

Comment thread specifications/objects-features.md Outdated
entries() -> [String, Instance][] // RTINS6
keys() -> String[] // RTINS7
values() -> Instance[] // RTINS8
size() -> Number // RTINS9 (narrowed to non-nullable)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Narrowed when compared to what? Instance? Instance doesn't have an id property in the IDL; if that's intentional then "narrowed" isn't the term here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, will remove narrowed from everywhere, for Instance it just exists on sub-types

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in c5cc6b8narrowed removed everywhere it described members added on sub-types (id/size/value). It is kept only for compactJson, where it is genuine covariant return-type narrowing relative to the base Instance#compactJson (RTTS7a).

Comment thread specifications/objects-features.md Outdated
subscribe((InstanceSubscriptionEvent) -> listener) -> Subscription // RTINS16, RTTS7b

class LiveCounterInstance extends Instance: // RTTS10b
id: String // RTINS3a (narrowed to non-nullable)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same q re narrowed from what

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in c5cc6b8 — same rewording as the LiveMapInstance id line.

Comment thread specifications/objects-features.md Outdated
class LiveCounterInstance extends Instance: // RTTS10b
id: String // RTINS3a (narrowed to non-nullable)
compactJson() -> JsonPrimitive // RTTS7a3 (narrowed)
value() -> Number // RTINS4 (narrowed to non-nullable)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same q re narrowed compared to what

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in c5cc6b8 — now (non-nullable), with the RTTS10b prose stating the plain Number return type.

Comment thread specifications/objects-features.md Outdated
- `(RTTS8)` Typed `Instance` extension property - best-effort helper introduced by the typed-SDK variant. Unlike [RTTS4](#RTTS4) on `PathObject`, an `Instance` is bound to a resolved value at construction time, so the helper has O(1) complexity and is therefore exposed as a property in host languages that distinguish properties from methods
- `(RTTS8a)` `getType` - returns the `ValueType` ([RTTS2](#RTTS2)) of the wrapped value. Returns the matching `ValueType` member per [RTTS2a](#RTTS2a). Must not return `UNKNOWN` in normal operation because an `Instance` is always constructed from a resolved value (see e.g. [RTPO8c](#RTPO8c), [RTINS5c](#RTINS5c)); see [RTTS2a9](#RTTS2a9) for the future-compatibility caveat
- `(RTTS8b)` There is no `exists` helper on `Instance`, because an `Instance` is only ever constructed when it wraps a resolved value; the existence question is unambiguous
- `(RTTS9)` Typed `Instance` `as*` cast helpers - type-checked accessors. Unlike the best-effort `PathObject` casts ([RTTS5](#RTTS5)), an `Instance` wraps an already-resolved value of a known, fixed type ([RTTS8a](#RTTS8a)), so a mismatched cast is a programming error and these helpers throw rather than re-wrap ([RTTS9d](#RTTS9d)). Exposed as properties in host languages that distinguish properties from methods, since the wrapped value is already resolved

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not all languages have the concept of a throwing property, I think — Swift didn't until recently. In that case they'll have to use a method

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — addressed in c5cc6b8. RTTS9 now reads: "Exposed as properties in host languages that distinguish properties from methods and support throwing property accessors; host languages without throwing property accessors may expose these as methods instead".

Comment thread specifications/objects-features.md Outdated
- `(RTTS3)` Typed `PathObject` base class - the typed SDK's public root of the `PathObject` hierarchy. The base class exposes only those methods of [RTPO1](#RTPO1) whose return type and semantics are identical regardless of the resolved underlying type
- `(RTTS3a)` Exposes `path` per [RTPO4](#RTPO4)
- `(RTTS3b)` Exposes `instance` per [RTPO8](#RTPO8). On sub-classes other than `LiveMapPathObject` ([RTTS6a](#RTTS6a)) and `LiveCounterPathObject` ([RTTS6b](#RTTS6b)), the behaviour of `instance` is constrained by [RTTS6e](#RTTS6e)
- `(RTTS3c)` Exposes `compactJson` per [RTPO14](#RTPO14), with the SDK's generic, nullable JSON element type as its return type. Unlike the typed `Instance` sub-classes ([RTTS7a](#RTTS7a)), the typed `PathObject` sub-classes do not narrow this return type: a `PathObject` cast is best-effort and `compactJson` may resolve to a value of any type, or fail to resolve and return null (per [RTPO3c1](#RTPO3c1)), so no narrower type can be guaranteed for any sub-class

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I thought we'd previously said we wouldn't use the word "cast" for anything related to PathObject since it's not really doing a cast? And "best-effort" isn't really correct either; who is making a best effort to do what exactly? It's just a view that represents the type of value that a user expects to find at a given path

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — reworded in c5cc6b8. The as* helpers are now view helpers throughout, which also matches the ably-java Javadoc (it consistently calls what they return "a ... view of this path/instance"). PathObject views are described as unchecked accessors "that return a sub-class view representing the type the caller expects at this path, without performing any validation", and Instance views as checked accessors that throw on mismatch. best-effort is gone from the as* prose ("unchecked" carries the meaning), and the exists/getType path helpers are now described as point-in-time instead.

…rface and RTTS deviations

Records the AblyLiveObjects public surface (declaration listing incl.
LiveMapValue, LiveMap/LiveCounter creation blueprints and JSONValue)
and a 13-row table of deviations from the RTTS partition, each
verifiable against the shipped source. Also notes the Swift-additive
events() AsyncStream contract (stream termination auto-unsubscribes).
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 August 6, 2026 14:58 Inactive
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 August 6, 2026 15:36 Inactive
…d-SDK corrections

The per-language declaration listing re-declared mostly-conforming surface
and had no precedent in the spec suite. Cross-SDK verification against
ably-js and ably-java showed most recorded deviations were already licensed
by the normative text, so the appendix is removed in favour of:

- RTTS6e replaced by RTPO8f (no SDK implements the null/92007 narrowing;
  primitive views wrap and return per RTPO8f), with the RTTS3b cross-ref
  updated to match
- RTO19 softened: off/offAll may be omitted when on() returns the RTO18f
  subscription object (JS and Swift rely on per-token off)
- New RTTS11/RTTS11a: LiveMapValue as the typed-SDK carrier of the map
  set-value union (Java abstract class + of() factories; Swift enum)
- New non-normative notes for the genuinely Swift-only shapes: RTTS9e
  (exhaustive sum-type discrimination), RTTS6h (single asPrimitive()
  primitive collapse), RTPO19h (idiomatic streaming subscribe forms,
  e.g. subscribeIterator()/events())
@github-actions
github-actions Bot temporarily deployed to staging/pull/491 August 6, 2026 16:02 Inactive
…-error "narrowed"

RTTS5/RTTS9: rename "as* cast helpers" -> "as* view helpers"; describe
PathObject views as unchecked/expectation-based and Instance views as
checked/fail-fast. Drop "cast"/"best-effort" from the as* prose
(addresses review comment 3519566527).

Replace category-error "narrowed to non-nullable" on sub-type-added
members id/size/value with plain non-nullable typing; keep the genuine
covariant-return narrowing wording for compactJson (RTTS7a) unchanged
(addresses 3519416077/3519418317/3519422611/3519424137; per 3519771736).

RTTS9: note that host languages without throwing property accessors may
expose the as* helpers as methods (addresses 3519461990).

Out of scope: ValueType->TypeOfValue rename (3519333807); public API
member names unchanged; ably-docs narrative alignment to follow.

Refs #491
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