Skip to content

Conversation

@AndyTWF
Copy link
Contributor

@AndyTWF AndyTWF commented Sep 23, 2025

This change makes it so that underlying Chat types are exposed (especially when in JSON mode). This means that we don't hide important information / have to update things every time a Chat update is made.

Also updated a small number of non-JSON areas (e.g. having the serial returned on message send).

CHA-1163

Summary by CodeRabbit

  • New Features

    • Occupancy subscription now supports enriched occupancy data and prevents incorrect status fall-through.
    • Message send output now includes the sent message’s serial in non-JSON mode.
  • Refactor

    • Standardized JSON outputs:
      • Messages history now returns raw messages without per-item transformation.
      • Presence enter/subscribe now emit a simplified event object (event, roomId, timestamp, success).
    • Message sending now returns the actual sent message object in results.
    • Internal client and occupancy types aligned to the standard SDK types; no public command signatures changed.

When doing JSON representation, use the underlying Chat types rather than CLI wrappers.
@vercel
Copy link

vercel bot commented Sep 23, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
cli-web-cli Ready Ready Preview Comment Sep 23, 2025 10:01pm

@coderabbitai
Copy link

coderabbitai bot commented Sep 23, 2025

Walkthrough

Updates adjust message history to output raw items, change message send to return the sent Message object (including serial), align subscription types, broaden occupancy typing to OccupancyData with switch case breaks, and refactor presence enter/subscribe to emit a new eventData shape encapsulating the full event plus roomId, timestamp, and success.

Changes

Cohort / File(s) Summary of Changes
Messages — History output shape
src/commands/rooms/messages/history.ts
Switched to multi-line call style; JSON output now assigns raw items directly to messages, removing per-item mapping/transform.
Messages — Send returns sent Message
src/commands/rooms/messages/send.ts
Import Message from @ably/chat; MessageResult.message type updated to Message; both async and awaited paths now use the returned sent Message; non-JSON log prints sent message serial.
Messages — Subscribe type alignment
src/commands/rooms/messages/subscribe.ts
Public field type changed from custom ChatClientType to ChatClient; internal imports/types aligned.
Occupancy — Broadened data model
src/commands/rooms/occupancy/subscribe.ts
Replaced custom OccupancyMetrics with OccupancyData from @ably/chat; displayOccupancyMetrics now accepts `OccupancyData
Presence — Enter payload refactor
src/commands/rooms/presence/enter.ts
JSON/log payload restructured to an eventData containing full event object, roomId, timestamp, success; removed prior flattened fields.
Presence — Subscribe payload refactor
src/commands/rooms/presence/subscribe.ts
Subscription now emits simplified eventData with event, roomId, timestamp, success; logging/JSON paths updated; member detail printing retained for non-JSON.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant CLI as CLI (rooms/messages/send)
  participant RC as RoomClient
  participant S as Chat Service

  U->>CLI: run send with payload
  CLI->>RC: room.messages.send(messageToSend)
  RC->>S: send(payload)
  S-->>RC: Message (includes serial)
  RC-->>CLI: Message
  CLI-->>U: Output (JSON or text) using returned Message (serial logged in text)
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant CLI as CLI (rooms/messages/history)
  participant RC as RoomClient

  U->>CLI: request history (limit)
  CLI->>RC: room.messages.history({ limit })
  RC-->>CLI: { items: [Message...] }
  CLI-->>U: JSON with messages = raw items (no per-item mapping)
Loading
sequenceDiagram
  autonumber
  participant S as Presence Stream
  participant CLI as CLI (presence subscribe/enter)
  participant U as User

  S-->>CLI: Presence event
  CLI->>CLI: Build eventData { event, roomId, timestamp, success }
  CLI-->>U: Log/JSON using eventData (text logs still show member details)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I thump my paws: a serial appears!
Messages hop with their genuine gears.
History’s raw, no trimming the hay,
Presence events bundled—neat bales today.
Occupancy counts in a carrot-bright view,
I twitch my whiskers—CLI feels new.
Hop, send, subscribe—bun-approved through and through!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The changes include edits to presence handlers (enter/subscribe) and occupancy subscribe that alter payload shapes and replace a local OccupancyMetrics type with OccupancyData from @ably/chat; these edits affect presence and occupancy flows rather than the message-subscription/history focus of CHA-1163. While these modifications align with the PR's broader stated goal of exposing underlying types, they are not part of the linked issue's narrow scope and therefore represent changes outside the specific linked issue objectives. Either split the presence and occupancy payload/type changes into a separate PR or update the PR description and linked issues to explicitly include these broader type-exposure changes, and confirm that downstream consumers and tests have been reviewed/updated for the modified payload shapes.
Linked Issues Check ❓ Inconclusive The PR satisfies key objectives from CHA-1163 in the provided summaries: history now returns raw items instead of a per-item mapped representation, and send returns and surfaces the sent Message (including logging the message serial), which makes serials available for downstream features. However, the messages subscription summary only notes a type change for chatClient and does not show the removal of a custom per-message mapping in the subscription handler, so I cannot confirm that subscription output now exposes the full Message shape. Because subscription behavior is not clearly reflected in the summaries, compliance with the linked issue is not fully verifiable. Please confirm or update the PR (or its summary) to show that src/commands/rooms/messages/subscribe.ts now emits raw Message objects (i.e., that any per-item mapping was removed), or include the missing change so the subscription handler clearly matches the history/send behavior described.
✅ 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 "chat: expose underlying types more clearly in output" is concise, directly reflects the primary intent of the changeset (exposing underlying chat types in CLI output), and is specific enough for a reviewer to understand the main change without extraneous details.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chat-message-types

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.

@AndyTWF AndyTWF requested a review from m-hulbert September 24, 2025 08:54
@AndyTWF AndyTWF changed the title Chat message types chat: expose underlying types more clearly in output Sep 24, 2025
@AndyTWF AndyTWF marked this pull request as ready for review September 24, 2025 09:08
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 (12)
src/commands/rooms/messages/subscribe.ts (5)

3-10: Remove unused import and prefer enum for status.

  • Room is unused; drop it to satisfy ESLint.
  • While here, import RoomStatus to avoid stringly-typed status checks below.
 import {
   ChatClient,
   Subscription,
   StatusSubscription,
   ChatMessageEvent,
-  Room,
+  RoomStatus,
   RoomStatusChange,
 } from "@ably/chat";

241-272: Use RoomStatus enum instead of string literals.

Avoids typos and aligns with other files using RoomStatus.

-          if (statusChange.current === "attached") {
+          if (statusChange.current === RoomStatus.Attached) {
             this.logCliEvent(
               flags,
               "room",
               "statusAttached",
               "Room status is ATTACHED.",
             );
           ...
-          } else if (statusChange.current === "failed") {
+          } else if (statusChange.current === RoomStatus.Failed) {
             const errorMsg = room.error?.message || "Unknown error";

62-66: Prefer CLI logging over console.warn in cleanup timeout.

Keeps logs consistent and structured.

-        console.warn("Ably client cleanup timed out after 2 seconds");
+        this.logCliEvent({}, "connection", "cleanupTimeout", "Ably client cleanup timed out after 2 seconds");

306-312: Guard against non-positive/NaN duration from env.

Match waitUntilInterruptedOrTimeout’s semantics and avoid passing NaN.

-      const effectiveDuration =
-        typeof flags.duration === "number" && flags.duration > 0
-          ? flags.duration
-          : process.env.ABLY_CLI_DEFAULT_DURATION
-            ? Number(process.env.ABLY_CLI_DEFAULT_DURATION)
-            : undefined;
+      const envDuration = process.env.ABLY_CLI_DEFAULT_DURATION
+        ? Number(process.env.ABLY_CLI_DEFAULT_DURATION)
+        : undefined;
+      const effectiveDuration =
+        typeof flags.duration === "number" && flags.duration > 0
+          ? flags.duration
+          : envDuration && envDuration > 0
+          ? envDuration
+          : undefined;

114-118: Align forced-exit gating with ABLY_CLI_TEST_MODE (and consider removing).

You already trigger an exit on timeout inside waitUntilInterruptedOrTimeout; this extra exit can duplicate behavior. If you keep it, gate consistently on ABLY_CLI_TEST_MODE.

-    if (process.env.NODE_ENV !== "test") {
+    if (process.env.ABLY_CLI_TEST_MODE !== "true") {
       setTimeout(() => {
         process.exit(0);
       }, 100);
     }
src/commands/rooms/presence/subscribe.ts (1)

251-257: Guard env duration like other commands/util.

Prevents NaN/zero durations slipping through.

-      const effectiveDuration =
-        typeof flags.duration === "number" && flags.duration > 0
-          ? flags.duration
-          : process.env.ABLY_CLI_DEFAULT_DURATION
-          ? Number(process.env.ABLY_CLI_DEFAULT_DURATION)
-          : undefined;
+      const envDuration = process.env.ABLY_CLI_DEFAULT_DURATION
+        ? Number(process.env.ABLY_CLI_DEFAULT_DURATION)
+        : undefined;
+      const effectiveDuration =
+        typeof flags.duration === "number" && flags.duration > 0
+          ? flags.duration
+          : envDuration && envDuration > 0
+          ? envDuration
+          : undefined;
src/commands/rooms/presence/enter.ts (1)

193-198: Guard env duration consistently.

Same rationale as other commands.

-      const effectiveDuration =
-        typeof flags.duration === "number" && flags.duration > 0
-          ? flags.duration
-          : process.env.ABLY_CLI_DEFAULT_DURATION
-          ? Number(process.env.ABLY_CLI_DEFAULT_DURATION)
-          : undefined;
+      const envDuration = process.env.ABLY_CLI_DEFAULT_DURATION
+        ? Number(process.env.ABLY_CLI_DEFAULT_DURATION)
+        : undefined;
+      const effectiveDuration =
+        typeof flags.duration === "number" && flags.duration > 0
+          ? flags.duration
+          : envDuration && envDuration > 0
+          ? envDuration
+          : undefined;
src/commands/rooms/occupancy/subscribe.ts (3)

43-64: Toughen cleanup: handle failed state and avoid console.warn.

  • Also skip close when state is failed (consistent with other files).
  • Prefer this.warn or logCliEvent over console.warn.
-  private async properlyCloseAblyClient(): Promise<void> {
-    if (!this.ablyClient || this.ablyClient.connection.state === "closed") {
+  private async properlyCloseAblyClient(): Promise<void> {
+    if (
+      !this.ablyClient ||
+      this.ablyClient.connection.state === "closed" ||
+      this.ablyClient.connection.state === "failed"
+    ) {
       return;
     }
@@
-      const timeout = setTimeout(() => {
-        console.warn("Ably client cleanup timed out after 3 seconds");
+      const timeout = setTimeout(() => {
+        this.warn("Ably client cleanup timed out after 3 seconds");
         resolve();
       }, 3000);
@@
-      this.ablyClient!.connection.once("closed", onClosed);
-      this.ablyClient!.connection.once("failed", onClosed);
+      this.ablyClient!.connection.once("closed", onClosed);
+      this.ablyClient!.connection.once("failed", onClosed);

453-465: Prefer single cleanup path; reuse properlyCloseAblyClient.

You close here and again in the class-level finally(). Consider centralizing to avoid duplicate handling.

-      if (this.ablyClient && this.ablyClient.connection.state !== "closed") {
-        this.logCliEvent(
-          flags || {},
-          "connection",
-          "finalCloseAttempt",
-          "Ensuring connection is closed in finally block.",
-        );
-        this.ablyClient.connection.off();
-        this.ablyClient.close();
-      }
+      await this.properlyCloseAblyClient();

469-473: Narrow displayOccupancyMetrics to OccupancyData.

You only pass OccupancyData; simplifying the type and body reduces branching.

-  private displayOccupancyMetrics(
-    occupancyMetrics: OccupancyData | OccupancyEvent,
+  private displayOccupancyMetrics(
+    occupancyMetrics: OccupancyData,
     roomId: string | null,
     flags: Record<string, unknown>,
     isInitial = false,
   ): void {
@@
-      // Type guard to handle both OccupancyData and OccupancyEvent
-      const connections =
-        "connections" in occupancyMetrics ? occupancyMetrics.connections : 0;
-      const presenceMembers =
-        "presenceMembers" in occupancyMetrics
-          ? occupancyMetrics.presenceMembers
-          : undefined;
+      const { connections = 0, presenceMembers } = occupancyMetrics as OccupancyData;

Also applies to: 497-504

src/commands/rooms/messages/send.ts (2)

461-474: Avoid double room release (run() and finally()).

You release in run() and again in finally(). Centralize in finally() to keep behavior consistent and avoid redundant calls.

Outside the shown range, consider removing the release block in run() and rely solely on the finally() override for releasing/closing.


8-12: Optional: reuse SDK type for message-to-send (if exported).

If @ably/chat exports a MessageToSend type, prefer importing it instead of redefining.

Would you like me to check the SDK typings and update accordingly?

Also applies to: 14-21

📜 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 7aaf483 and d1cab67.

📒 Files selected for processing (6)
  • src/commands/rooms/messages/history.ts (1 hunks)
  • src/commands/rooms/messages/send.ts (5 hunks)
  • src/commands/rooms/messages/subscribe.ts (15 hunks)
  • src/commands/rooms/occupancy/subscribe.ts (9 hunks)
  • src/commands/rooms/presence/enter.ts (1 hunks)
  • src/commands/rooms/presence/subscribe.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/commands/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/AI-Assistance.mdc)

src/commands/**/*.ts: Commands should follow the oclif structure: export default class MyCommand extends Command { ... }
Command should support auth via the standard auth helper (import { getAuth } from '../../helpers/auth') and use it in the run method.
Use try/catch for error handling in commands, and provide user-friendly error messages (e.g., handle error.code === 40100 for authentication failures).
Do not use direct HTTP requests (e.g., fetch) for data plane APIs; use the Ably SDK instead.
Do not use hardcoded endpoint URLs; use configuration values (e.g., flags.controlHost, config.controlHost) when constructing URLs.
Do not use non-standard command structures (e.g., export default class { async execute(args) { ... } }); always use the oclif Command class.

Files:

  • src/commands/rooms/messages/subscribe.ts
  • src/commands/rooms/presence/enter.ts
  • src/commands/rooms/messages/history.ts
  • src/commands/rooms/messages/send.ts
  • src/commands/rooms/occupancy/subscribe.ts
  • src/commands/rooms/presence/subscribe.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/Development.mdc)

**/*.{ts,tsx}: Use TypeScript and follow best practice naming conventions
ESLint is used to ensure all code adheres best practices; ensure you check that all code passes the linter before concluding your work.

Files:

  • src/commands/rooms/messages/subscribe.ts
  • src/commands/rooms/presence/enter.ts
  • src/commands/rooms/messages/history.ts
  • src/commands/rooms/messages/send.ts
  • src/commands/rooms/occupancy/subscribe.ts
  • src/commands/rooms/presence/subscribe.ts
src/commands/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/Development.mdc)

Follow oclif framework best practices as described in the oclif documentation.

Files:

  • src/commands/rooms/messages/subscribe.ts
  • src/commands/rooms/presence/enter.ts
  • src/commands/rooms/messages/history.ts
  • src/commands/rooms/messages/send.ts
  • src/commands/rooms/occupancy/subscribe.ts
  • src/commands/rooms/presence/subscribe.ts
🧬 Code graph analysis (1)
src/commands/rooms/messages/subscribe.ts (1)
src/utils/long-running.ts (1)
  • waitUntilInterruptedOrTimeout (11-77)
🪛 GitHub Check: setup
src/commands/rooms/messages/subscribe.ts

[warning] 8-8:
'Room' is defined but never used. Allowed unused vars must match /^_/u

🪛 GitHub Check: test
src/commands/rooms/messages/subscribe.ts

[warning] 8-8:
'Room' is defined but never used. Allowed unused vars must match /^_/u

🪛 GitHub Check: e2e-cli
src/commands/rooms/messages/subscribe.ts

[warning] 8-8:
'Room' is defined but never used. Allowed unused vars must match /^_/u

🔇 Additional comments (7)
src/commands/rooms/messages/history.ts (2)

83-86: LGTM: Param object style for history call.


92-96: Expose raw Message items in JSON output (breaking shape).

Good move toward underlying types. Flagging that this changes JSON shape; ensure downstream consumers/docs are updated.

src/commands/rooms/presence/subscribe.ts (1)

178-242: Event payload now surfaces the full PresenceEvent — thumbs up.

Consistent with PR goal to expose underlying types; JSON/non-JSON paths look correct.

src/commands/rooms/presence/enter.ts (1)

145-160: EventData shape: good alignment with subscribe.ts.

Emitting the full event meets the “expose underlying types” objective.

src/commands/rooms/occupancy/subscribe.ts (2)

173-203: Nice: added breaks to prevent switch fall‑through.


250-256: Initial occupancy display path looks correct.

Passes OccupancyData, matches new typing.

src/commands/rooms/messages/send.ts (1)

3-3: Great: return the actual sent Message and surface serial.

  • Importing Message and threading the sent object through results meets CHA-1163.
  • Non-JSON path now prints the serial — perfect.

Please confirm the @ably/chat version in this repo guarantees that room.messages.send returns a Message (not void). If older versions exist in supported environments, we may need a defensive fallback.

Also applies to: 16-16, 292-297, 416-420, 435-435

Copy link
Contributor

@splindsay-92 splindsay-92 left a comment

Choose a reason for hiding this comment

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

LGTM

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