@openmarket/rooms-client 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,7 +24,9 @@ export declare class LibraryModel {
24
24
  refresh(): Promise<boolean>;
25
25
  private refreshSpace;
26
26
  private applyChanges;
27
- /** Upsert after our own write so the UI reflects it without waiting a poll. */
27
+ /** Upsert after our own write so the UI reflects it without waiting a poll.
28
+ * Every local write (archive included) advances the receipt: our write IS
29
+ * the head we would otherwise refetch. */
28
30
  applyLocalWrite(doc: RoomDoc): void;
29
31
  docsFor(spaceId: string): RoomDoc[];
30
32
  recentFor(spaceId: string, limit?: number): RoomDoc[];
@@ -34,6 +36,17 @@ export declare class LibraryModel {
34
36
  freshCount(spaceId: string): number;
35
37
  isFresh(spaceId: string, docId: string): boolean;
36
38
  markSeen(spaceId: string): void;
39
+ /**
40
+ * Record a server poke (DOC_CHANGED) that the space's change head moved.
41
+ * Bookkeeping only: latestSeq ratchets up (never down) so atHead() reports
42
+ * stale until a refresh catches up. Returns whether a refresh would fetch
43
+ * anything: false for untracked spaces, true for a seqless poke (an
44
+ * unconditional "something changed"), otherwise whether the poked head is
45
+ * ahead of the cursor. Callers own debounce/refresh scheduling, and must
46
+ * re-check atHead() after a refresh completes: the refresh() single-flight
47
+ * guard drops a poke that lands mid-refresh.
48
+ */
49
+ noteRemoteHead(spaceId: string, latestSeq?: number): boolean;
37
50
  /** True when the cache has provably caught up to the space's change head. */
38
51
  atHead(spaceId: string): boolean;
39
52
  syncedOnce(spaceId: string): boolean;
@@ -95,21 +95,23 @@ export class LibraryModel {
95
95
  }
96
96
  return changed;
97
97
  }
98
- /** Upsert after our own write so the UI reflects it without waiting a poll. */
98
+ /** Upsert after our own write so the UI reflects it without waiting a poll.
99
+ * Every local write (archive included) advances the receipt: our write IS
100
+ * the head we would otherwise refetch. */
99
101
  applyLocalWrite(doc) {
100
102
  const state = this.spaces.get(doc.spaceId);
101
103
  if (!state)
102
104
  return;
105
+ if (doc.spaceDocSeq > state.cursor) {
106
+ state.cursor = doc.spaceDocSeq;
107
+ state.latestSeq = Math.max(state.latestSeq, doc.spaceDocSeq);
108
+ }
103
109
  if (doc.status === "archived") {
104
110
  state.docs.delete(doc.docId);
105
111
  state.freshDocIds.delete(doc.docId);
106
112
  return;
107
113
  }
108
114
  state.docs.set(doc.docId, doc);
109
- if (doc.spaceDocSeq > state.cursor) {
110
- state.cursor = doc.spaceDocSeq;
111
- state.latestSeq = Math.max(state.latestSeq, doc.spaceDocSeq);
112
- }
113
115
  }
114
116
  docsFor(spaceId) {
115
117
  const state = this.spaces.get(spaceId);
@@ -157,6 +159,25 @@ export class LibraryModel {
157
159
  markSeen(spaceId) {
158
160
  this.spaces.get(spaceId)?.freshDocIds.clear();
159
161
  }
162
+ /**
163
+ * Record a server poke (DOC_CHANGED) that the space's change head moved.
164
+ * Bookkeeping only: latestSeq ratchets up (never down) so atHead() reports
165
+ * stale until a refresh catches up. Returns whether a refresh would fetch
166
+ * anything: false for untracked spaces, true for a seqless poke (an
167
+ * unconditional "something changed"), otherwise whether the poked head is
168
+ * ahead of the cursor. Callers own debounce/refresh scheduling, and must
169
+ * re-check atHead() after a refresh completes: the refresh() single-flight
170
+ * guard drops a poke that lands mid-refresh.
171
+ */
172
+ noteRemoteHead(spaceId, latestSeq) {
173
+ const state = this.spaces.get(spaceId);
174
+ if (!state)
175
+ return false;
176
+ if (latestSeq === undefined)
177
+ return true;
178
+ state.latestSeq = Math.max(state.latestSeq, latestSeq);
179
+ return latestSeq > state.cursor;
180
+ }
160
181
  /** True when the cache has provably caught up to the space's change head. */
161
182
  atHead(spaceId) {
162
183
  const state = this.spaces.get(spaceId);
@@ -104,6 +104,8 @@ export declare const RoomServerMessageType: {
104
104
  readonly TOPIC: "TOPIC";
105
105
  readonly TOPICS: "TOPICS";
106
106
  readonly TOPIC_UPDATED: "TOPIC_UPDATED";
107
+ readonly DOC_CHANGED: "DOC_CHANGED";
108
+ readonly FRIEND_UPDATED: "FRIEND_UPDATED";
107
109
  };
108
110
  export declare function addBounded<T>(set: Set<T>, value: T, max: number): void;
109
111
  export type RoomServerMessageType = (typeof RoomServerMessageType)[keyof typeof RoomServerMessageType];
@@ -975,6 +977,19 @@ export interface RoomTopicUpdatedPayload {
975
977
  room: string;
976
978
  topic: RoomTopic;
977
979
  }
980
+ /** Poke: a space's doc-library change head moved (create/edit/archive/restore).
981
+ * Deliberately content-free: clients refetch deltas over REST. */
982
+ export interface RoomDocChangedPayload {
983
+ spaceId: string;
984
+ /** The space's change head after the mutation, when the server knows it
985
+ * cheaply; absent still means "something changed, refetch". */
986
+ latestSeq?: number | undefined;
987
+ }
988
+ /** Poke: the recipient's friendship graph changed. Clients refetch over REST. */
989
+ export interface RoomFriendUpdatedPayload {
990
+ kind: "request" | "accepted" | "rejected" | "removed";
991
+ friendshipId?: string | undefined;
992
+ }
978
993
  export type RoomClientMessage = {
979
994
  type: typeof RoomClientMessageType.AUTHENTICATE;
980
995
  payload: {
@@ -1504,6 +1519,14 @@ export type RoomServerMessage = {
1504
1519
  type: typeof RoomServerMessageType.TOPIC_UPDATED;
1505
1520
  payload: RoomTopicUpdatedPayload;
1506
1521
  timestamp: number;
1522
+ } | {
1523
+ type: typeof RoomServerMessageType.DOC_CHANGED;
1524
+ payload: RoomDocChangedPayload;
1525
+ timestamp: number;
1526
+ } | {
1527
+ type: typeof RoomServerMessageType.FRIEND_UPDATED;
1528
+ payload: RoomFriendUpdatedPayload;
1529
+ timestamp: number;
1507
1530
  };
1508
1531
  export type RoomServerMessageOf<T extends RoomServerMessageType> = Extract<RoomServerMessage, {
1509
1532
  type: T;
@@ -103,6 +103,8 @@ export const RoomServerMessageType = {
103
103
  TOPIC: "TOPIC",
104
104
  TOPICS: "TOPICS",
105
105
  TOPIC_UPDATED: "TOPIC_UPDATED",
106
+ DOC_CHANGED: "DOC_CHANGED",
107
+ FRIEND_UPDATED: "FRIEND_UPDATED",
106
108
  };
107
109
  export function addBounded(set, value, max) {
108
110
  set.add(value);
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.7.0";
2
- export declare const RUNNER_VERSION = "0.7.0";
1
+ export declare const VERSION = "0.9.0";
2
+ export declare const RUNNER_VERSION = "0.9.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = "0.7.0";
2
- export const RUNNER_VERSION = "0.7.0";
1
+ export const VERSION = "0.9.0";
2
+ export const RUNNER_VERSION = "0.9.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmarket/rooms-client",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "OM Rooms protocol client: wire types, WebSocket + REST clients, and chat view-models. Browser-safe (no node:/bun: imports).",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -129,20 +129,22 @@ export class LibraryModel {
129
129
  return changed;
130
130
  }
131
131
 
132
- /** Upsert after our own write so the UI reflects it without waiting a poll. */
132
+ /** Upsert after our own write so the UI reflects it without waiting a poll.
133
+ * Every local write (archive included) advances the receipt: our write IS
134
+ * the head we would otherwise refetch. */
133
135
  applyLocalWrite(doc: RoomDoc): void {
134
136
  const state = this.spaces.get(doc.spaceId);
135
137
  if (!state) return;
138
+ if (doc.spaceDocSeq > state.cursor) {
139
+ state.cursor = doc.spaceDocSeq;
140
+ state.latestSeq = Math.max(state.latestSeq, doc.spaceDocSeq);
141
+ }
136
142
  if (doc.status === "archived") {
137
143
  state.docs.delete(doc.docId);
138
144
  state.freshDocIds.delete(doc.docId);
139
145
  return;
140
146
  }
141
147
  state.docs.set(doc.docId, doc);
142
- if (doc.spaceDocSeq > state.cursor) {
143
- state.cursor = doc.spaceDocSeq;
144
- state.latestSeq = Math.max(state.latestSeq, doc.spaceDocSeq);
145
- }
146
148
  }
147
149
 
148
150
  docsFor(spaceId: string): RoomDoc[] {
@@ -193,6 +195,24 @@ export class LibraryModel {
193
195
  this.spaces.get(spaceId)?.freshDocIds.clear();
194
196
  }
195
197
 
198
+ /**
199
+ * Record a server poke (DOC_CHANGED) that the space's change head moved.
200
+ * Bookkeeping only: latestSeq ratchets up (never down) so atHead() reports
201
+ * stale until a refresh catches up. Returns whether a refresh would fetch
202
+ * anything: false for untracked spaces, true for a seqless poke (an
203
+ * unconditional "something changed"), otherwise whether the poked head is
204
+ * ahead of the cursor. Callers own debounce/refresh scheduling, and must
205
+ * re-check atHead() after a refresh completes: the refresh() single-flight
206
+ * guard drops a poke that lands mid-refresh.
207
+ */
208
+ noteRemoteHead(spaceId: string, latestSeq?: number): boolean {
209
+ const state = this.spaces.get(spaceId);
210
+ if (!state) return false;
211
+ if (latestSeq === undefined) return true;
212
+ state.latestSeq = Math.max(state.latestSeq, latestSeq);
213
+ return latestSeq > state.cursor;
214
+ }
215
+
196
216
  /** True when the cache has provably caught up to the space's change head. */
197
217
  atHead(spaceId: string): boolean {
198
218
  const state = this.spaces.get(spaceId);
@@ -107,6 +107,8 @@ export const RoomServerMessageType = {
107
107
  TOPIC: "TOPIC",
108
108
  TOPICS: "TOPICS",
109
109
  TOPIC_UPDATED: "TOPIC_UPDATED",
110
+ DOC_CHANGED: "DOC_CHANGED",
111
+ FRIEND_UPDATED: "FRIEND_UPDATED",
110
112
  } as const;
111
113
 
112
114
  export function addBounded<T>(set: Set<T>, value: T, max: number): void {
@@ -1118,6 +1120,21 @@ export interface RoomTopicUpdatedPayload {
1118
1120
  topic: RoomTopic;
1119
1121
  }
1120
1122
 
1123
+ /** Poke: a space's doc-library change head moved (create/edit/archive/restore).
1124
+ * Deliberately content-free: clients refetch deltas over REST. */
1125
+ export interface RoomDocChangedPayload {
1126
+ spaceId: string;
1127
+ /** The space's change head after the mutation, when the server knows it
1128
+ * cheaply; absent still means "something changed, refetch". */
1129
+ latestSeq?: number | undefined;
1130
+ }
1131
+
1132
+ /** Poke: the recipient's friendship graph changed. Clients refetch over REST. */
1133
+ export interface RoomFriendUpdatedPayload {
1134
+ kind: "request" | "accepted" | "rejected" | "removed";
1135
+ friendshipId?: string | undefined;
1136
+ }
1137
+
1121
1138
  export type RoomClientMessage =
1122
1139
  | {
1123
1140
  type: typeof RoomClientMessageType.AUTHENTICATE;
@@ -1696,6 +1713,16 @@ export type RoomServerMessage =
1696
1713
  type: typeof RoomServerMessageType.TOPIC_UPDATED;
1697
1714
  payload: RoomTopicUpdatedPayload;
1698
1715
  timestamp: number;
1716
+ }
1717
+ | {
1718
+ type: typeof RoomServerMessageType.DOC_CHANGED;
1719
+ payload: RoomDocChangedPayload;
1720
+ timestamp: number;
1721
+ }
1722
+ | {
1723
+ type: typeof RoomServerMessageType.FRIEND_UPDATED;
1724
+ payload: RoomFriendUpdatedPayload;
1725
+ timestamp: number;
1699
1726
  };
1700
1727
 
1701
1728
  export type RoomServerMessageOf<T extends RoomServerMessageType> = Extract<
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = "0.7.0";
2
- export const RUNNER_VERSION = "0.7.0";
1
+ export const VERSION = "0.9.0";
2
+ export const RUNNER_VERSION = "0.9.0";