Skip to content

Commit 094cee2

Browse files
committed
Added support for optional message version and annotations
1 parent 35f63f8 commit 094cee2

5 files changed

Lines changed: 134 additions & 0 deletions

File tree

src/commands/channels/history.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ export default class ChannelsHistory extends AblyBaseCommand {
9595
indexPrefix: `[${index + 1}] ${formatTimestamp(formatMessageTimestamp(message.timestamp))}`,
9696
serial: message.serial,
9797
timestamp: message.timestamp ?? Date.now(),
98+
version: message.version,
99+
annotations: message.annotations,
98100
}),
99101
);
100102

src/commands/channels/subscribe.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ export default class ChannelsSubscribe extends AblyBaseCommand {
204204
id: message.id,
205205
serial: message.serial,
206206
timestamp: message.timestamp ?? Date.now(),
207+
version: message.version,
208+
annotations: message.annotations,
207209
...(flags["sequence-numbers"]
208210
? { sequencePrefix: `${chalk.dim(`[${this.sequenceCounter}]`)} ` }
209211
: {}),

src/utils/output.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import chalk, { type ChalkInstance } from "chalk";
2+
import type * as Ably from "ably";
23
import { formatMessageData, isJsonData } from "./json-formatter.js";
34

45
export function progress(message: string): string {
@@ -79,6 +80,8 @@ export interface MessageDisplayFields {
7980
sequencePrefix?: string;
8081
serial?: string;
8182
timestamp: number;
83+
version?: Ably.MessageVersion;
84+
annotations?: Ably.MessageAnnotations;
8285
}
8386

8487
/**
@@ -119,6 +122,36 @@ export function formatMessagesOutput(messages: MessageDisplayFields[]): string {
119122
lines.push(`${chalk.dim("Serial:")} ${msg.serial}`);
120123
}
121124

125+
if (
126+
msg.version &&
127+
Object.keys(msg.version).length > 0 &&
128+
msg.version.serial &&
129+
msg.version.serial !== msg.serial
130+
) {
131+
lines.push(`${chalk.dim("Version:")}`);
132+
if (msg.version.serial) {
133+
lines.push(` ${chalk.dim("Serial:")} ${msg.version.serial}`);
134+
}
135+
if (msg.version.timestamp !== undefined) {
136+
lines.push(` ${chalk.dim("Timestamp:")} ${msg.version.timestamp}`);
137+
}
138+
if (msg.version.clientId) {
139+
lines.push(
140+
` ${chalk.dim("Client ID:")} ${chalk.blue(msg.version.clientId)}`,
141+
);
142+
}
143+
}
144+
145+
if (msg.annotations && Object.keys(msg.annotations.summary).length > 0) {
146+
lines.push(`${chalk.dim("Annotations:")}`);
147+
for (const [type, entry] of Object.entries(msg.annotations.summary)) {
148+
lines.push(
149+
` ${chalk.dim(`${type}:`)}`,
150+
` ${formatMessageData(entry)}`,
151+
);
152+
}
153+
}
154+
122155
if (isJsonData(msg.data)) {
123156
lines.push(`${chalk.dim("Data:")}\n${formatMessageData(msg.data)}`);
124157
} else {
@@ -149,6 +182,8 @@ export function toMessageJson(
149182
...(msg.id ? { id: msg.id } : {}),
150183
...(msg.clientId ? { clientId: msg.clientId } : {}),
151184
...(msg.serial ? { serial: msg.serial } : {}),
185+
...(msg.version ? { version: msg.version } : {}),
186+
...(msg.annotations ? { annotations: msg.annotations } : {}),
152187
data: msg.data,
153188
};
154189
}

test/unit/commands/channels/history.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ describe("channels:history command", () => {
1616
timestamp: 1700000000000,
1717
clientId: "client-1",
1818
connectionId: "conn-1",
19+
version: {
20+
serial: "v1-serial",
21+
timestamp: 1700000000000,
22+
clientId: "updater-1",
23+
},
24+
annotations: {
25+
summary: {
26+
"reaction:distinct.v1": {
27+
"👍": {
28+
total: 3,
29+
clientIds: ["c1", "c2", "c3"],
30+
clipped: false,
31+
},
32+
},
33+
},
34+
},
1935
},
2036
{
2137
id: "msg-2",
@@ -94,6 +110,27 @@ describe("channels:history command", () => {
94110
expect(stdout).toContain("msg-1");
95111
});
96112

113+
it("should display version fields when present", async () => {
114+
const { stdout } = await runCommand(
115+
["channels:history", "test-channel"],
116+
import.meta.url,
117+
);
118+
119+
expect(stdout).toContain("Version:");
120+
expect(stdout).toContain("v1-serial");
121+
expect(stdout).toContain("updater-1");
122+
});
123+
124+
it("should display annotations summary when present", async () => {
125+
const { stdout } = await runCommand(
126+
["channels:history", "test-channel"],
127+
import.meta.url,
128+
);
129+
130+
expect(stdout).toContain("Annotations:");
131+
expect(stdout).toContain("reaction:distinct.v1:");
132+
});
133+
97134
it("should handle empty history", async () => {
98135
const mock = getMockAblyRest();
99136
const channel = mock.channels._getChannel("test-channel");
@@ -124,6 +161,45 @@ describe("channels:history command", () => {
124161
expect(result[0].data).toEqual({ text: "Hello world" });
125162
});
126163

164+
it("should include version in JSON output when present", async () => {
165+
const { stdout } = await runCommand(
166+
["channels:history", "test-channel", "--json"],
167+
import.meta.url,
168+
);
169+
170+
const result = JSON.parse(stdout);
171+
expect(result[0]).toHaveProperty("version");
172+
expect(result[0].version).toEqual({
173+
serial: "v1-serial",
174+
timestamp: 1700000000000,
175+
clientId: "updater-1",
176+
});
177+
// Second message has no version
178+
expect(result[1]).not.toHaveProperty("version");
179+
});
180+
181+
it("should include annotations in JSON output when present", async () => {
182+
const { stdout } = await runCommand(
183+
["channels:history", "test-channel", "--json"],
184+
import.meta.url,
185+
);
186+
187+
const result = JSON.parse(stdout);
188+
expect(result[0]).toHaveProperty("annotations");
189+
expect(result[0].annotations.summary).toHaveProperty(
190+
"reaction:distinct.v1",
191+
);
192+
expect(
193+
result[0].annotations.summary["reaction:distinct.v1"]["👍"],
194+
).toEqual({
195+
total: 3,
196+
clientIds: ["c1", "c2", "c3"],
197+
clipped: false,
198+
});
199+
// Second message has no annotations
200+
expect(result[1]).not.toHaveProperty("annotations");
201+
});
202+
127203
it("should respect --limit flag", async () => {
128204
const mock = getMockAblyRest();
129205
const channel = mock.channels._getChannel("test-channel");

test/unit/commands/channels/subscribe.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ describe("channels:subscribe command", () => {
121121
id: "msg-123",
122122
clientId: "publisher-client",
123123
connectionId: "conn-456",
124+
version: {
125+
serial: "ver-serial-1",
126+
timestamp: Date.now(),
127+
clientId: "version-client",
128+
},
129+
annotations: {
130+
summary: {
131+
"reaction:distinct.v1": {
132+
"👍": { total: 2, clientIds: ["c1", "c2"], clipped: false },
133+
},
134+
},
135+
},
124136
});
125137

126138
const { stdout } = await commandPromise;
@@ -136,6 +148,13 @@ describe("channels:subscribe command", () => {
136148
expect(stdout).toContain("publisher-client");
137149
expect(stdout).toContain("Data:");
138150
expect(stdout).toContain("hello world");
151+
// Version fields
152+
expect(stdout).toContain("Version:");
153+
expect(stdout).toContain("ver-serial-1");
154+
expect(stdout).toContain("version-client");
155+
// Annotations
156+
expect(stdout).toContain("Annotations:");
157+
expect(stdout).toContain("reaction:distinct.v1:");
139158
});
140159

141160
it("should run with --json flag without errors", async () => {

0 commit comments

Comments
 (0)