@liveblocks/server 1.2.0 → 1.2.1

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.
package/dist/index.js CHANGED
@@ -12,16 +12,139 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
12
12
  // src/decoders/ClientMsg.ts
13
13
  import { ClientMsgCode } from "@liveblocks/core";
14
14
  import {
15
- array,
15
+ array as array2,
16
16
  boolean,
17
17
  constant as constant2,
18
+ jsonObject,
19
+ nonEmptyString,
18
20
  number,
19
21
  object as object2,
20
- optional as optional2,
21
- string as string2,
22
+ optional as optional3,
23
+ string as string3,
22
24
  taggedUnion as taggedUnion2
23
25
  } from "decoders";
24
26
 
27
+ // src/protocol/feedMessages.ts
28
+ var FeedMsgCode = {
29
+ // Server → client (50x)
30
+ FEEDS_LIST: 500,
31
+ FEEDS_ADDED: 501,
32
+ FEEDS_UPDATED: 502,
33
+ FEED_DELETED: 503,
34
+ FEED_MESSAGES_LIST: 504,
35
+ FEED_MESSAGES_ADDED: 505,
36
+ FEED_MESSAGES_UPDATED: 506,
37
+ FEED_MESSAGES_DELETED: 507,
38
+ FEED_REQUEST_FAILED: 508,
39
+ // Client → server (51x)
40
+ FETCH_FEEDS: 510,
41
+ FETCH_FEED_MESSAGES: 511,
42
+ ADD_FEED: 512,
43
+ UPDATE_FEED: 513,
44
+ DELETE_FEED: 514,
45
+ ADD_FEED_MESSAGE: 515,
46
+ UPDATE_FEED_MESSAGE: 516,
47
+ DELETE_FEED_MESSAGE: 517
48
+ };
49
+ var FeedRequestErrorCode = {
50
+ INTERNAL: "INTERNAL",
51
+ FEED_ALREADY_EXISTS: "FEED_ALREADY_EXISTS",
52
+ FEED_NOT_FOUND: "FEED_NOT_FOUND",
53
+ FEED_MESSAGE_NOT_FOUND: "FEED_MESSAGE_NOT_FOUND"
54
+ };
55
+
56
+ // src/protocol/feedErrors.ts
57
+ function feedRequestFailed(requestId, code, reason) {
58
+ return {
59
+ type: FeedMsgCode.FEED_REQUEST_FAILED,
60
+ requestId: requestId ?? "",
61
+ code,
62
+ reason
63
+ };
64
+ }
65
+ function mapFeedError(err) {
66
+ if (!(err instanceof Error)) {
67
+ return { code: FeedRequestErrorCode.INTERNAL };
68
+ }
69
+ const m = err.message;
70
+ if (m.includes("already exists")) {
71
+ return { code: FeedRequestErrorCode.FEED_ALREADY_EXISTS, reason: m };
72
+ }
73
+ if (m.includes("Feed message") && m.includes("not found")) {
74
+ return { code: FeedRequestErrorCode.FEED_MESSAGE_NOT_FOUND, reason: m };
75
+ }
76
+ if (m.includes("not found")) {
77
+ return { code: FeedRequestErrorCode.FEED_NOT_FOUND, reason: m };
78
+ }
79
+ return { code: FeedRequestErrorCode.INTERNAL, reason: m };
80
+ }
81
+ function feedFailureServerMsg(requestId, err) {
82
+ const mapped = mapFeedError(err);
83
+ return feedRequestFailed(requestId, mapped.code, mapped.reason);
84
+ }
85
+
86
+ // src/protocol/ProtocolVersion.ts
87
+ import { enum_ } from "decoders";
88
+ var ProtocolVersion = /* @__PURE__ */ ((ProtocolVersion2) => {
89
+ ProtocolVersion2[ProtocolVersion2["V7"] = 7] = "V7";
90
+ ProtocolVersion2[ProtocolVersion2["V8"] = 8] = "V8";
91
+ return ProtocolVersion2;
92
+ })(ProtocolVersion || {});
93
+ var protocolVersionDecoder = enum_(ProtocolVersion).describe(
94
+ "Unsupported protocol version"
95
+ );
96
+
97
+ // src/decoders/feedMetadata.ts
98
+ import {
99
+ array,
100
+ either,
101
+ identifier,
102
+ nullable,
103
+ optional,
104
+ poja,
105
+ record,
106
+ select,
107
+ sized,
108
+ string
109
+ } from "decoders";
110
+ var MAX_METADATA_COUNT = 50;
111
+ var MAX_METADATA_VALUE_LIST_LENGTH = 50;
112
+ var feedMetadataIdDecoder = sized(identifier, { min: 1, max: 40 });
113
+ var metadataStringValue = sized(string, { max: 256 });
114
+ function createRoomMetadataValueDecoder() {
115
+ return select(
116
+ either(string, poja).describe("Must be string or string[]"),
117
+ (x) => typeof x === "string" ? metadataStringValue : array(metadataStringValue).refine(
118
+ (value) => value.length <= MAX_METADATA_VALUE_LIST_LENGTH,
119
+ `Must be at most ${MAX_METADATA_VALUE_LIST_LENGTH} items`
120
+ )
121
+ );
122
+ }
123
+ var roomMetadataValueDecoder = createRoomMetadataValueDecoder();
124
+ var feedMetadataNullableValueDecoder = nullable(roomMetadataValueDecoder);
125
+ var feedMetadataRecordForCreate = record(
126
+ feedMetadataIdDecoder,
127
+ roomMetadataValueDecoder
128
+ ).refine(
129
+ (value) => Object.keys(value).length <= MAX_METADATA_COUNT,
130
+ `Must be at most ${MAX_METADATA_COUNT} items`
131
+ );
132
+ var optionalFeedMetadataDecoder = optional(feedMetadataRecordForCreate);
133
+ var feedMetadataUpdateDecoder = record(
134
+ feedMetadataIdDecoder,
135
+ feedMetadataNullableValueDecoder
136
+ );
137
+ var feedMetadataRecordForFilter = record(
138
+ feedMetadataIdDecoder,
139
+ feedMetadataNullableValueDecoder
140
+ ).refine(
141
+ (value) => Object.keys(value).length <= MAX_METADATA_COUNT,
142
+ `Must be at most ${MAX_METADATA_COUNT} items`
143
+ );
144
+ var fetchFeedsMetadataFilterDecoder = optional(
145
+ feedMetadataRecordForFilter
146
+ );
147
+
25
148
  // src/decoders/jsonYolo.ts
26
149
  import { unknown } from "decoders";
27
150
  var jsonYolo = unknown;
@@ -32,67 +155,67 @@ var jsonObjectYolo = jsonYolo.refine(
32
155
 
33
156
  // src/decoders/Op.ts
34
157
  import { OpCode } from "@liveblocks/core";
35
- import { constant, object, optional, string, taggedUnion } from "decoders";
158
+ import { constant, object, optional as optional2, string as string2, taggedUnion } from "decoders";
36
159
  var updateObjectOp = object({
37
160
  type: constant(OpCode.UPDATE_OBJECT),
38
- opId: string,
39
- id: string,
161
+ opId: string2,
162
+ id: string2,
40
163
  data: jsonObjectYolo
41
164
  });
42
165
  var createObjectOp = object({
43
166
  type: constant(OpCode.CREATE_OBJECT),
44
- opId: string,
45
- id: string,
46
- parentId: string,
47
- parentKey: string,
167
+ opId: string2,
168
+ id: string2,
169
+ parentId: string2,
170
+ parentKey: string2,
48
171
  data: jsonObjectYolo,
49
- intent: optional(constant("set")),
50
- deletedId: optional(string)
172
+ intent: optional2(constant("set")),
173
+ deletedId: optional2(string2)
51
174
  });
52
175
  var createListOp = object({
53
176
  type: constant(OpCode.CREATE_LIST),
54
- opId: string,
55
- id: string,
56
- parentId: string,
57
- parentKey: string,
58
- intent: optional(constant("set")),
59
- deletedId: optional(string)
177
+ opId: string2,
178
+ id: string2,
179
+ parentId: string2,
180
+ parentKey: string2,
181
+ intent: optional2(constant("set")),
182
+ deletedId: optional2(string2)
60
183
  });
61
184
  var createMapOp = object({
62
185
  type: constant(OpCode.CREATE_MAP),
63
- opId: string,
64
- id: string,
65
- parentId: string,
66
- parentKey: string,
67
- intent: optional(constant("set")),
68
- deletedId: optional(string)
186
+ opId: string2,
187
+ id: string2,
188
+ parentId: string2,
189
+ parentKey: string2,
190
+ intent: optional2(constant("set")),
191
+ deletedId: optional2(string2)
69
192
  });
70
193
  var createRegisterOp = object({
71
194
  type: constant(OpCode.CREATE_REGISTER),
72
- opId: string,
73
- id: string,
74
- parentId: string,
75
- parentKey: string,
195
+ opId: string2,
196
+ id: string2,
197
+ parentId: string2,
198
+ parentKey: string2,
76
199
  data: jsonYolo,
77
- intent: optional(constant("set")),
78
- deletedId: optional(string)
200
+ intent: optional2(constant("set")),
201
+ deletedId: optional2(string2)
79
202
  });
80
203
  var deleteCrdtOp = object({
81
204
  type: constant(OpCode.DELETE_CRDT),
82
- opId: string,
83
- id: string
205
+ opId: string2,
206
+ id: string2
84
207
  });
85
208
  var setParentKeyOp = object({
86
209
  type: constant(OpCode.SET_PARENT_KEY),
87
- opId: string,
88
- id: string,
89
- parentKey: string
210
+ opId: string2,
211
+ id: string2,
212
+ parentKey: string2
90
213
  });
91
214
  var deleteObjectKeyOp = object({
92
215
  type: constant(OpCode.DELETE_OBJECT_KEY),
93
- opId: string,
94
- id: string,
95
- key: string
216
+ opId: string2,
217
+ id: string2,
218
+ key: string2
96
219
  });
97
220
  var op = taggedUnion("type", {
98
221
  [OpCode.UPDATE_OBJECT]: updateObjectOp,
@@ -114,7 +237,7 @@ var ROOT_YDOC_ID = "root";
114
237
  var updatePresenceClientMsg = object2({
115
238
  type: constant2(ClientMsgCode.UPDATE_PRESENCE),
116
239
  data: jsonObjectYolo,
117
- targetActor: optional2(number)
240
+ targetActor: optional3(number)
118
241
  });
119
242
  var broadcastEventClientMsg = object2({
120
243
  type: constant2(ClientMsgCode.BROADCAST_EVENT),
@@ -125,21 +248,77 @@ var fetchStorageClientMsg = object2({
125
248
  });
126
249
  var updateStorageClientMsg = object2({
127
250
  type: constant2(ClientMsgCode.UPDATE_STORAGE),
128
- ops: array(op)
251
+ ops: array2(op)
129
252
  });
130
253
  var fetchYDocClientMsg = object2({
131
254
  type: constant2(ClientMsgCode.FETCH_YDOC),
132
- vector: string2.refineType(),
133
- guid: optional2(guidDecoder),
255
+ vector: string3.refineType(),
256
+ guid: optional3(guidDecoder),
134
257
  // Don't specify to update the root doc
135
- v2: optional2(boolean)
258
+ v2: optional3(boolean)
136
259
  });
137
260
  var updateYDocClientMsg = object2({
138
261
  type: constant2(ClientMsgCode.UPDATE_YDOC),
139
- update: string2.refineType(),
140
- guid: optional2(guidDecoder),
262
+ update: string3.refineType(),
263
+ guid: optional3(guidDecoder),
141
264
  // Don't specify to update the root doc
142
- v2: optional2(boolean)
265
+ v2: optional3(boolean)
266
+ });
267
+ var fetchFeedsClientMsg = object2({
268
+ type: constant2(FeedMsgCode.FETCH_FEEDS),
269
+ requestId: string3,
270
+ cursor: optional3(string3),
271
+ since: optional3(number),
272
+ limit: optional3(number),
273
+ metadata: fetchFeedsMetadataFilterDecoder
274
+ });
275
+ var fetchFeedMessagesClientMsg = object2({
276
+ type: constant2(FeedMsgCode.FETCH_FEED_MESSAGES),
277
+ requestId: string3,
278
+ feedId: nonEmptyString,
279
+ cursor: optional3(string3),
280
+ since: optional3(number),
281
+ limit: optional3(number)
282
+ });
283
+ var addFeedClientMsg = object2({
284
+ type: constant2(FeedMsgCode.ADD_FEED),
285
+ feedId: string3,
286
+ metadata: optionalFeedMetadataDecoder,
287
+ timestamp: optional3(number),
288
+ requestId: optional3(string3)
289
+ });
290
+ var updateFeedClientMsg = object2({
291
+ type: constant2(FeedMsgCode.UPDATE_FEED),
292
+ feedId: string3,
293
+ metadata: feedMetadataUpdateDecoder,
294
+ requestId: optional3(string3)
295
+ });
296
+ var deleteFeedClientMsg = object2({
297
+ type: constant2(FeedMsgCode.DELETE_FEED),
298
+ feedId: string3,
299
+ requestId: optional3(string3)
300
+ });
301
+ var addFeedMessageClientMsg = object2({
302
+ type: constant2(FeedMsgCode.ADD_FEED_MESSAGE),
303
+ feedId: string3,
304
+ data: jsonObject,
305
+ id: optional3(string3),
306
+ timestamp: optional3(number),
307
+ requestId: optional3(string3)
308
+ });
309
+ var updateFeedMessageClientMsg = object2({
310
+ type: constant2(FeedMsgCode.UPDATE_FEED_MESSAGE),
311
+ feedId: string3,
312
+ messageId: string3,
313
+ data: jsonObject,
314
+ timestamp: optional3(number),
315
+ requestId: optional3(string3)
316
+ });
317
+ var deleteFeedMessageClientMsg = object2({
318
+ type: constant2(FeedMsgCode.DELETE_FEED_MESSAGE),
319
+ feedId: string3,
320
+ messageId: string3,
321
+ requestId: optional3(string3)
143
322
  });
144
323
  var clientMsgDecoder = taggedUnion2("type", {
145
324
  [ClientMsgCode.UPDATE_PRESENCE]: updatePresenceClientMsg,
@@ -147,7 +326,15 @@ var clientMsgDecoder = taggedUnion2("type", {
147
326
  [ClientMsgCode.FETCH_STORAGE]: fetchStorageClientMsg,
148
327
  [ClientMsgCode.UPDATE_STORAGE]: updateStorageClientMsg,
149
328
  [ClientMsgCode.FETCH_YDOC]: fetchYDocClientMsg,
150
- [ClientMsgCode.UPDATE_YDOC]: updateYDocClientMsg
329
+ [ClientMsgCode.UPDATE_YDOC]: updateYDocClientMsg,
330
+ [FeedMsgCode.FETCH_FEEDS]: fetchFeedsClientMsg,
331
+ [FeedMsgCode.FETCH_FEED_MESSAGES]: fetchFeedMessagesClientMsg,
332
+ [FeedMsgCode.ADD_FEED]: addFeedClientMsg,
333
+ [FeedMsgCode.UPDATE_FEED]: updateFeedClientMsg,
334
+ [FeedMsgCode.DELETE_FEED]: deleteFeedClientMsg,
335
+ [FeedMsgCode.ADD_FEED_MESSAGE]: addFeedMessageClientMsg,
336
+ [FeedMsgCode.UPDATE_FEED_MESSAGE]: updateFeedMessageClientMsg,
337
+ [FeedMsgCode.DELETE_FEED_MESSAGE]: deleteFeedMessageClientMsg
151
338
  }).describe("Must be a valid client message");
152
339
  var transientClientMsgDecoder = taggedUnion2("type", {
153
340
  // [ClientMsgCode.UPDATE_PRESENCE]: updatePresenceClientMsg,
@@ -628,17 +815,6 @@ function makeMetadataDB(driver) {
628
815
  };
629
816
  }
630
817
 
631
- // src/protocol/ProtocolVersion.ts
632
- import { enum_ } from "decoders";
633
- var ProtocolVersion = /* @__PURE__ */ ((ProtocolVersion2) => {
634
- ProtocolVersion2[ProtocolVersion2["V7"] = 7] = "V7";
635
- ProtocolVersion2[ProtocolVersion2["V8"] = 8] = "V8";
636
- return ProtocolVersion2;
637
- })(ProtocolVersion || {});
638
- var protocolVersionDecoder = enum_(ProtocolVersion).describe(
639
- "Unsupported protocol version"
640
- );
641
-
642
818
  // src/Room.ts
643
819
  import {
644
820
  assertNever as assertNever3,
@@ -646,12 +822,12 @@ import {
646
822
  nodeStreamToCompactNodes,
647
823
  OpCode as OpCode3,
648
824
  raise as raise3,
649
- ServerMsgCode as ServerMsgCode2,
825
+ ServerMsgCode as CoreServerMsgCode,
650
826
  tryParseJson,
651
827
  WebsocketCloseCodes as CloseCode
652
828
  } from "@liveblocks/core";
653
829
  import { Mutex, tryAcquire } from "async-mutex";
654
- import { array as array2, formatInline } from "decoders";
830
+ import { array as array3, formatInline } from "decoders";
655
831
  import { chunked } from "itertools";
656
832
  import { nanoid as nanoid2 } from "nanoid";
657
833
 
@@ -845,16 +1021,21 @@ function hasStaticDataAt(node, key) {
845
1021
  return node.type === CrdtType4.OBJECT && Object.prototype.hasOwnProperty.call(node.data, key) && node.data[key] !== void 0;
846
1022
  }
847
1023
  var InMemoryDriver = class {
1024
+ // Key: `${feedId}:${messageId}`
848
1025
  constructor(options) {
849
1026
  __publicField(this, "_nextActor");
850
1027
  __publicField(this, "_nodes");
851
1028
  __publicField(this, "_metadb");
852
1029
  __publicField(this, "_ydb");
853
1030
  __publicField(this, "_leasedSessions");
1031
+ __publicField(this, "_feeds");
1032
+ __publicField(this, "_feedMessages");
854
1033
  this._nodes = /* @__PURE__ */ new Map();
855
1034
  this._metadb = /* @__PURE__ */ new Map();
856
1035
  this._ydb = /* @__PURE__ */ new Map();
857
1036
  this._leasedSessions = /* @__PURE__ */ new Map();
1037
+ this._feeds = /* @__PURE__ */ new Map();
1038
+ this._feedMessages = /* @__PURE__ */ new Map();
858
1039
  this._nextActor = options?.initialActor ?? -1;
859
1040
  for (const [key, value] of options?.initialNodes ?? []) {
860
1041
  this._nodes.set(key, value);
@@ -894,6 +1075,180 @@ var InMemoryDriver = class {
894
1075
  takeRowsWritten() {
895
1076
  return 0;
896
1077
  }
1078
+ // ---------------------------------------------------------------------------
1079
+ // Feed APIs
1080
+ // ---------------------------------------------------------------------------
1081
+ async list_feeds(options) {
1082
+ const limit = Math.min(options?.limit ?? 20, 100);
1083
+ const since = options?.since;
1084
+ const cursor = options?.cursor;
1085
+ const metadata = options?.metadata;
1086
+ let feeds = [];
1087
+ for (const [_, feed] of this._feeds.entries()) {
1088
+ if (metadata !== void 0) {
1089
+ const feedMetadata = feed.metadata;
1090
+ let matches = true;
1091
+ for (const [key, value] of Object.entries(metadata)) {
1092
+ if (feedMetadata[key] !== value) {
1093
+ matches = false;
1094
+ break;
1095
+ }
1096
+ }
1097
+ if (!matches) continue;
1098
+ }
1099
+ if (since !== void 0 && feed.createdAt < since) {
1100
+ continue;
1101
+ }
1102
+ feeds.push(feed);
1103
+ }
1104
+ feeds.sort((a, b) => {
1105
+ if (b.createdAt !== a.createdAt) {
1106
+ return b.createdAt - a.createdAt;
1107
+ }
1108
+ return b.feedId.localeCompare(a.feedId);
1109
+ });
1110
+ if (cursor !== void 0) {
1111
+ try {
1112
+ const decoded = JSON.parse(
1113
+ decodeURIComponent(
1114
+ escape(atob(cursor.replace(/-/g, "+").replace(/_/g, "/")))
1115
+ )
1116
+ );
1117
+ const [cursorFeedId, cursorCreatedAt] = decoded;
1118
+ feeds = feeds.filter((s) => {
1119
+ return s.createdAt < cursorCreatedAt || s.createdAt === cursorCreatedAt && s.feedId < cursorFeedId;
1120
+ });
1121
+ } catch {
1122
+ }
1123
+ }
1124
+ let nextCursor;
1125
+ if (feeds.length > limit) {
1126
+ feeds = feeds.slice(0, limit);
1127
+ const last = feeds[feeds.length - 1];
1128
+ if (last) {
1129
+ const cursorData = [last.feedId, last.createdAt];
1130
+ nextCursor = btoa(
1131
+ unescape(encodeURIComponent(JSON.stringify(cursorData)))
1132
+ ).replace("/", "_").replace("+", "-").replace(/=+$/, "");
1133
+ }
1134
+ }
1135
+ return { feeds, nextCursor };
1136
+ }
1137
+ async get_feed(feedId) {
1138
+ const feed = this._feeds.get(feedId);
1139
+ if (feed === void 0) {
1140
+ return void 0;
1141
+ }
1142
+ return feed;
1143
+ }
1144
+ async create_feed(feed) {
1145
+ if (this._feeds.has(feed.feedId)) {
1146
+ throw new Error(`Feed ${feed.feedId} already exists`);
1147
+ }
1148
+ this._feeds.set(feed.feedId, {
1149
+ feedId: feed.feedId,
1150
+ metadata: feed.metadata,
1151
+ createdAt: feed.createdAt,
1152
+ updatedAt: feed.updatedAt
1153
+ });
1154
+ }
1155
+ async update_feed_metadata(feedId, metadata) {
1156
+ const existing = this._feeds.get(feedId);
1157
+ if (existing === void 0) {
1158
+ throw new Error(`Feed ${feedId} not found`);
1159
+ }
1160
+ this._feeds.set(feedId, {
1161
+ ...existing,
1162
+ metadata
1163
+ });
1164
+ }
1165
+ async delete_feed(feedId) {
1166
+ const messageKeys = [];
1167
+ for (const [key] of this._feedMessages.entries()) {
1168
+ if (key.startsWith(`${feedId}:`)) {
1169
+ messageKeys.push(key);
1170
+ }
1171
+ }
1172
+ for (const key of messageKeys) {
1173
+ this._feedMessages.delete(key);
1174
+ }
1175
+ this._feeds.delete(feedId);
1176
+ }
1177
+ async list_feed_messages(feedId, options) {
1178
+ const limit = Math.min(options?.limit ?? 20, 100);
1179
+ const since = options?.since;
1180
+ const cursor = options?.cursor;
1181
+ let messages = [];
1182
+ const prefix = `${feedId}:`;
1183
+ for (const [key, message] of this._feedMessages.entries()) {
1184
+ if (key.startsWith(prefix)) {
1185
+ if (since !== void 0 && message.createdAt < since) {
1186
+ continue;
1187
+ }
1188
+ messages.push(message);
1189
+ }
1190
+ }
1191
+ messages.sort((a, b) => {
1192
+ if (b.createdAt !== a.createdAt) {
1193
+ return b.createdAt - a.createdAt;
1194
+ }
1195
+ return b.id.localeCompare(a.id);
1196
+ });
1197
+ if (cursor !== void 0) {
1198
+ try {
1199
+ const decoded = JSON.parse(
1200
+ decodeURIComponent(
1201
+ escape(atob(cursor.replace(/-/g, "+").replace(/_/g, "/")))
1202
+ )
1203
+ );
1204
+ const [cursorMessageId, cursorCreatedAt] = decoded;
1205
+ messages = messages.filter((m) => {
1206
+ return m.createdAt < cursorCreatedAt || m.createdAt === cursorCreatedAt && m.id < cursorMessageId;
1207
+ });
1208
+ } catch {
1209
+ }
1210
+ }
1211
+ let nextCursor;
1212
+ if (messages.length > limit) {
1213
+ messages = messages.slice(0, limit);
1214
+ const last = messages[messages.length - 1];
1215
+ if (last) {
1216
+ const cursorData = [last.id, last.createdAt];
1217
+ nextCursor = btoa(
1218
+ unescape(encodeURIComponent(JSON.stringify(cursorData)))
1219
+ ).replace("/", "_").replace("+", "-").replace(/=+$/, "");
1220
+ }
1221
+ }
1222
+ return { messages, nextCursor };
1223
+ }
1224
+ async add_feed_message(feedId, message) {
1225
+ const feed = this._feeds.get(feedId);
1226
+ if (feed === void 0) {
1227
+ throw new Error(`Feed ${feedId} not found`);
1228
+ }
1229
+ this._feedMessages.set(`${feedId}:${message.id}`, message);
1230
+ }
1231
+ async update_feed_message(feedId, messageId, data, timestamp) {
1232
+ const key = `${feedId}:${messageId}`;
1233
+ const message = this._feedMessages.get(key);
1234
+ if (message === void 0) {
1235
+ throw new Error(`Feed message ${messageId} not found in feed ${feedId}`);
1236
+ }
1237
+ const effectiveTimestamp = timestamp ?? Date.now();
1238
+ if (effectiveTimestamp < message.updatedAt) {
1239
+ return message;
1240
+ }
1241
+ const updatedMessage = {
1242
+ ...message,
1243
+ updatedAt: effectiveTimestamp,
1244
+ data
1245
+ };
1246
+ this._feedMessages.set(key, updatedMessage);
1247
+ return updatedMessage;
1248
+ }
1249
+ async delete_feed_message(feedId, messageId) {
1250
+ this._feedMessages.delete(`${feedId}:${messageId}`);
1251
+ }
897
1252
  next_actor() {
898
1253
  return ++this._nextActor;
899
1254
  }
@@ -1672,7 +2027,8 @@ function isLeasedSessionExpired(leasedSession) {
1672
2027
  }
1673
2028
 
1674
2029
  // src/Room.ts
1675
- var messagesDecoder = array2(clientMsgDecoder);
2030
+ var messagesDecoder = array3(clientMsgDecoder);
2031
+ var ServerMsgCode2 = { ...CoreServerMsgCode, ...FeedMsgCode };
1676
2032
  var HIGHEST_PROTOCOL_VERSION = Math.max(
1677
2033
  ...Object.values(ProtocolVersion).filter(
1678
2034
  (v) => typeof v === "number"
@@ -2368,6 +2724,87 @@ var Room = class {
2368
2724
  await this.deleteLeasedSession(session, ctx, defer);
2369
2725
  }
2370
2726
  }
2727
+ // ---------------------------------------------------------------------------
2728
+ // Feed APIs
2729
+ // ---------------------------------------------------------------------------
2730
+ /**
2731
+ * List feeds with pagination and filtering.
2732
+ */
2733
+ async listFeeds(options) {
2734
+ return await this.driver.list_feeds(options);
2735
+ }
2736
+ /**
2737
+ * Get a specific feed by feed ID.
2738
+ */
2739
+ async getFeed(feedId) {
2740
+ return await this.driver.get_feed(feedId);
2741
+ }
2742
+ /**
2743
+ * Create a new feed.
2744
+ * If timestamp is not provided, current server time is used.
2745
+ */
2746
+ async createFeed(feed) {
2747
+ const now = feed.timestamp ?? Date.now();
2748
+ const fullFeed = {
2749
+ ...feed,
2750
+ createdAt: now,
2751
+ updatedAt: now
2752
+ };
2753
+ await this.driver.create_feed(fullFeed);
2754
+ return fullFeed;
2755
+ }
2756
+ /**
2757
+ * Update a feed's metadata.
2758
+ */
2759
+ async updateFeedMetadata(feedId, metadata) {
2760
+ await this.driver.update_feed_metadata(feedId, metadata);
2761
+ }
2762
+ /**
2763
+ * Delete a feed.
2764
+ */
2765
+ async deleteFeed(feedId) {
2766
+ await this.driver.delete_feed(feedId);
2767
+ }
2768
+ /**
2769
+ * List feed messages for a feed with pagination.
2770
+ */
2771
+ async listFeedMessages(feedId, options) {
2772
+ return await this.driver.list_feed_messages(feedId, options);
2773
+ }
2774
+ /**
2775
+ * Add a message to a feed.
2776
+ * If message id is not provided, a unique ID is automatically generated.
2777
+ * If timestamp is not provided, current server time is used.
2778
+ */
2779
+ async addFeedMessage(feedId, message) {
2780
+ const now = message.timestamp ?? Date.now();
2781
+ const fullMessage = {
2782
+ id: message.id ?? nanoid2(),
2783
+ createdAt: now,
2784
+ updatedAt: now,
2785
+ data: message.data
2786
+ };
2787
+ await this.driver.add_feed_message(feedId, fullMessage);
2788
+ return fullMessage;
2789
+ }
2790
+ /**
2791
+ * Update a feed message's data.
2792
+ * Returns the updated message.
2793
+ */
2794
+ async updateFeedMessage(feedId, messageId, data, timestamp) {
2795
+ return await this.driver.update_feed_message(
2796
+ feedId,
2797
+ messageId,
2798
+ data,
2799
+ timestamp ?? Date.now()
2800
+ );
2801
+ }
2802
+ /**
2803
+ * Delete a feed message.
2804
+ */
2805
+ async deleteFeedMessage(feedId, messageId) {
2806
+ await this.driver.delete_feed_message(feedId, messageId);
2807
+ }
2371
2808
  /**
2372
2809
  * Will send the given ServerMsg through all Session, except the Session
2373
2810
  * where the message originates from.
@@ -2704,6 +3141,176 @@ var Room = class {
2704
3141
  }
2705
3142
  break;
2706
3143
  }
3144
+ case FeedMsgCode.FETCH_FEEDS: {
3145
+ const fetchMsg = msg;
3146
+ const [result, err] = await tryCatch(
3147
+ this.listFeeds({
3148
+ cursor: fetchMsg.cursor,
3149
+ since: fetchMsg.since,
3150
+ limit: fetchMsg.limit,
3151
+ metadata: fetchMsg.metadata
3152
+ })
3153
+ );
3154
+ if (err) {
3155
+ replyImmediately(feedFailureServerMsg(fetchMsg.requestId, err));
3156
+ break;
3157
+ }
3158
+ replyImmediately({
3159
+ type: FeedMsgCode.FEEDS_LIST,
3160
+ requestId: fetchMsg.requestId,
3161
+ feeds: result.feeds,
3162
+ nextCursor: result.nextCursor
3163
+ });
3164
+ break;
3165
+ }
3166
+ case FeedMsgCode.FETCH_FEED_MESSAGES: {
3167
+ const fetchMsg = msg;
3168
+ const [result, err] = await tryCatch(
3169
+ this.listFeedMessages(fetchMsg.feedId, {
3170
+ cursor: fetchMsg.cursor,
3171
+ since: fetchMsg.since,
3172
+ limit: fetchMsg.limit
3173
+ })
3174
+ );
3175
+ if (err) {
3176
+ replyImmediately(feedFailureServerMsg(fetchMsg.requestId, err));
3177
+ break;
3178
+ }
3179
+ replyImmediately({
3180
+ type: FeedMsgCode.FEED_MESSAGES_LIST,
3181
+ requestId: fetchMsg.requestId,
3182
+ feedId: fetchMsg.feedId,
3183
+ messages: result.messages,
3184
+ nextCursor: result.nextCursor
3185
+ });
3186
+ break;
3187
+ }
3188
+ case FeedMsgCode.ADD_FEED: {
3189
+ const addMsg = msg;
3190
+ const [feed, err] = await tryCatch(
3191
+ this.createFeed({
3192
+ feedId: addMsg.feedId,
3193
+ metadata: addMsg.metadata ?? {},
3194
+ timestamp: addMsg.timestamp
3195
+ })
3196
+ );
3197
+ if (err) {
3198
+ replyImmediately(feedFailureServerMsg(addMsg.requestId, err));
3199
+ break;
3200
+ }
3201
+ const feedsMsg = {
3202
+ type: FeedMsgCode.FEEDS_ADDED,
3203
+ feeds: [feed]
3204
+ };
3205
+ replyImmediately(feedsMsg);
3206
+ scheduleFanOut(feedsMsg);
3207
+ break;
3208
+ }
3209
+ case FeedMsgCode.UPDATE_FEED: {
3210
+ const updateMsg = msg;
3211
+ const [, metaErr] = await tryCatch(
3212
+ this.updateFeedMetadata(updateMsg.feedId, updateMsg.metadata)
3213
+ );
3214
+ if (metaErr) {
3215
+ replyImmediately(feedFailureServerMsg(updateMsg.requestId, metaErr));
3216
+ break;
3217
+ }
3218
+ const feed = await this.getFeed(updateMsg.feedId);
3219
+ if (!feed) {
3220
+ replyImmediately(
3221
+ feedRequestFailed(
3222
+ updateMsg.requestId,
3223
+ FeedRequestErrorCode.FEED_NOT_FOUND
3224
+ )
3225
+ );
3226
+ break;
3227
+ }
3228
+ const feedsMsg = {
3229
+ type: FeedMsgCode.FEEDS_UPDATED,
3230
+ feeds: [feed]
3231
+ };
3232
+ replyImmediately(feedsMsg);
3233
+ scheduleFanOut(feedsMsg);
3234
+ break;
3235
+ }
3236
+ case FeedMsgCode.DELETE_FEED: {
3237
+ const deleteMsg = msg;
3238
+ const [, err] = await tryCatch(this.deleteFeed(deleteMsg.feedId));
3239
+ if (err) {
3240
+ replyImmediately(feedFailureServerMsg(deleteMsg.requestId, err));
3241
+ break;
3242
+ }
3243
+ const feedDeletedMsg = {
3244
+ type: FeedMsgCode.FEED_DELETED,
3245
+ feedId: deleteMsg.feedId
3246
+ };
3247
+ replyImmediately(feedDeletedMsg);
3248
+ scheduleFanOut(feedDeletedMsg);
3249
+ break;
3250
+ }
3251
+ case FeedMsgCode.ADD_FEED_MESSAGE: {
3252
+ const addMsg = msg;
3253
+ const [message, err] = await tryCatch(
3254
+ this.addFeedMessage(addMsg.feedId, {
3255
+ data: addMsg.data,
3256
+ id: addMsg.id,
3257
+ timestamp: addMsg.timestamp
3258
+ })
3259
+ );
3260
+ if (err) {
3261
+ replyImmediately(feedFailureServerMsg(addMsg.requestId, err));
3262
+ break;
3263
+ }
3264
+ const feedMessagesMsg = {
3265
+ type: FeedMsgCode.FEED_MESSAGES_ADDED,
3266
+ feedId: addMsg.feedId,
3267
+ messages: [message]
3268
+ };
3269
+ replyImmediately(feedMessagesMsg);
3270
+ scheduleFanOut(feedMessagesMsg);
3271
+ break;
3272
+ }
3273
+ case FeedMsgCode.UPDATE_FEED_MESSAGE: {
3274
+ const updateMsg = msg;
3275
+ const [message, err] = await tryCatch(
3276
+ this.updateFeedMessage(
3277
+ updateMsg.feedId,
3278
+ updateMsg.messageId,
3279
+ updateMsg.data,
3280
+ updateMsg.timestamp
3281
+ )
3282
+ );
3283
+ if (err) {
3284
+ replyImmediately(feedFailureServerMsg(updateMsg.requestId, err));
3285
+ break;
3286
+ }
3287
+ const feedMessagesMsg = {
3288
+ type: FeedMsgCode.FEED_MESSAGES_UPDATED,
3289
+ feedId: updateMsg.feedId,
3290
+ messages: [message]
3291
+ };
3292
+ replyImmediately(feedMessagesMsg);
3293
+ scheduleFanOut(feedMessagesMsg);
3294
+ break;
3295
+ }
3296
+ case FeedMsgCode.DELETE_FEED_MESSAGE: {
3297
+ const deleteMsg = msg;
3298
+ const [, err] = await tryCatch(
3299
+ this.deleteFeedMessage(deleteMsg.feedId, deleteMsg.messageId)
3300
+ );
3301
+ if (err) {
3302
+ replyImmediately(feedFailureServerMsg(deleteMsg.requestId, err));
3303
+ break;
3304
+ }
3305
+ const feedMessagesMsg = {
3306
+ type: FeedMsgCode.FEED_MESSAGES_DELETED,
3307
+ feedId: deleteMsg.feedId,
3308
+ messageIds: [deleteMsg.messageId]
3309
+ };
3310
+ replyImmediately(feedMessagesMsg);
3311
+ scheduleFanOut(feedMessagesMsg);
3312
+ break;
3313
+ }
2707
3314
  default: {
2708
3315
  try {
2709
3316
  return assertNever3(msg, "Unrecognized client msg");
@@ -2720,6 +3327,8 @@ export {
2720
3327
  BrowserSession,
2721
3328
  ConsoleTarget,
2722
3329
  DefaultMap,
3330
+ FeedMsgCode,
3331
+ FeedRequestErrorCode,
2723
3332
  InMemoryDriver,
2724
3333
  LogLevel,
2725
3334
  LogTarget,
@@ -2732,12 +3341,19 @@ export {
2732
3341
  ackIgnoredOp,
2733
3342
  clientMsgDecoder,
2734
3343
  concatUint8Arrays,
3344
+ feedFailureServerMsg,
3345
+ feedMetadataIdDecoder,
3346
+ feedMetadataUpdateDecoder,
3347
+ feedRequestFailed,
3348
+ fetchFeedsMetadataFilterDecoder,
2735
3349
  guidDecoder,
2736
3350
  isLeasedSessionExpired,
2737
3351
  jsonObjectYolo,
2738
3352
  jsonYolo,
2739
3353
  makeInMemorySnapshot,
2740
3354
  makeMetadataDB,
3355
+ mapFeedError,
3356
+ optionalFeedMetadataDecoder,
2741
3357
  plainLsonToNodeStream,
2742
3358
  protocolVersionDecoder,
2743
3359
  quote,