@liveblocks/core 3.16.0-flow3 → 3.17.0-rc1

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
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/version.ts
8
8
  var PKG_NAME = "@liveblocks/core";
9
- var PKG_VERSION = "3.16.0-flow3";
9
+ var PKG_VERSION = "3.17.0-rc1";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -3219,10 +3219,26 @@ var ServerMsgCode = Object.freeze({
3219
3219
  COMMENT_REACTION_ADDED: 405,
3220
3220
  COMMENT_REACTION_REMOVED: 406,
3221
3221
  COMMENT_METADATA_UPDATED: 409,
3222
+ // For Feeds
3223
+ FEEDS_LIST: 500,
3224
+ FEEDS_ADDED: 501,
3225
+ FEEDS_UPDATED: 502,
3226
+ FEED_DELETED: 503,
3227
+ FEED_MESSAGES_LIST: 504,
3228
+ FEED_MESSAGES_ADDED: 505,
3229
+ FEED_MESSAGES_UPDATED: 506,
3230
+ FEED_MESSAGES_DELETED: 507,
3231
+ FEED_REQUEST_FAILED: 508,
3222
3232
  // Error codes
3223
3233
  REJECT_STORAGE_OP: 299
3224
3234
  // Sent if a mutation was not allowed on the server (i.e. due to permissions, limit exceeded, etc)
3225
3235
  });
3236
+ var FeedRequestErrorCode = {
3237
+ INTERNAL: "INTERNAL",
3238
+ FEED_ALREADY_EXISTS: "FEED_ALREADY_EXISTS",
3239
+ FEED_NOT_FOUND: "FEED_NOT_FOUND",
3240
+ FEED_MESSAGE_NOT_FOUND: "FEED_MESSAGE_NOT_FOUND"
3241
+ };
3226
3242
 
3227
3243
  // src/types/IWebSocket.ts
3228
3244
  var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
@@ -5186,6 +5202,7 @@ var Permission = /* @__PURE__ */ ((Permission2) => {
5186
5202
  Permission2["PresenceWrite"] = "room:presence:write";
5187
5203
  Permission2["CommentsWrite"] = "comments:write";
5188
5204
  Permission2["CommentsRead"] = "comments:read";
5205
+ Permission2["FeedsWrite"] = "feeds:write";
5189
5206
  return Permission2;
5190
5207
  })(Permission || {});
5191
5208
  function canWriteStorage(scopes) {
@@ -6718,7 +6735,10 @@ var LiveList = class _LiveList extends AbstractCrdt {
6718
6735
  };
6719
6736
  } else {
6720
6737
  if (indexOfItemWithSamePosition !== -1) {
6721
- nn(this.#items.removeAt(indexOfItemWithSamePosition));
6738
+ const displaced = nn(
6739
+ this.#items.removeAt(indexOfItemWithSamePosition)
6740
+ );
6741
+ this.#implicitlyDeletedItems.add(displaced);
6722
6742
  }
6723
6743
  const { newItem, newIndex } = this.#createAttachItemAndSort(
6724
6744
  op,
@@ -7866,61 +7886,7 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
7866
7886
  }
7867
7887
  };
7868
7888
 
7869
- // src/lib/Json.ts
7870
- function isJsonScalar(data) {
7871
- return data === null || typeof data === "string" || typeof data === "number" || typeof data === "boolean";
7872
- }
7873
- function isJsonArray(data) {
7874
- return Array.isArray(data);
7875
- }
7876
- function isJsonObject(data) {
7877
- return !isJsonScalar(data) && !isJsonArray(data);
7878
- }
7879
-
7880
- // src/immutable.ts
7881
- function lsonObjectToJson(obj) {
7882
- const result = {};
7883
- for (const key in obj) {
7884
- const val = obj[key];
7885
- if (val !== void 0) {
7886
- result[key] = lsonToJson(val);
7887
- }
7888
- }
7889
- return result;
7890
- }
7891
- function liveObjectToJson(liveObject) {
7892
- return lsonObjectToJson(liveObject.toObject());
7893
- }
7894
- function liveMapToJson(map) {
7895
- const result = {};
7896
- for (const [key, value] of map.entries()) {
7897
- result[key] = lsonToJson(value);
7898
- }
7899
- return result;
7900
- }
7901
- function lsonListToJson(value) {
7902
- return value.map(lsonToJson);
7903
- }
7904
- function liveListToJson(value) {
7905
- return lsonListToJson(value.toArray());
7906
- }
7907
- function lsonToJson(value) {
7908
- if (value instanceof LiveObject) {
7909
- return liveObjectToJson(value);
7910
- } else if (value instanceof LiveList) {
7911
- return liveListToJson(value);
7912
- } else if (value instanceof LiveMap) {
7913
- return liveMapToJson(value);
7914
- } else if (value instanceof LiveRegister) {
7915
- return value.data;
7916
- }
7917
- if (Array.isArray(value)) {
7918
- return lsonListToJson(value);
7919
- } else if (isPlainObject(value)) {
7920
- return lsonObjectToJson(value);
7921
- }
7922
- return value;
7923
- }
7889
+ // src/crdts/reconcile.ts
7924
7890
  function deepLiveify(value, config) {
7925
7891
  if (Array.isArray(value)) {
7926
7892
  return new LiveList(value.map((v) => deepLiveify(v, config)));
@@ -8023,492 +7989,251 @@ function reconcileLiveList(liveList, jsonArr, config) {
8023
7989
  }
8024
7990
  return liveList;
8025
7991
  }
8026
- function legacy_patchLiveList(liveList, prev, next) {
8027
- let i = 0;
8028
- let prevEnd = prev.length - 1;
8029
- let nextEnd = next.length - 1;
8030
- let prevNode = prev[0];
8031
- let nextNode = next[0];
8032
- outer: {
8033
- while (prevNode === nextNode) {
8034
- ++i;
8035
- if (i > prevEnd || i > nextEnd) {
8036
- break outer;
7992
+
7993
+ // src/crdts/LiveObject.ts
7994
+ var MAX_LIVE_OBJECT_SIZE = 128 * 1024;
7995
+ var LiveObject = class _LiveObject extends AbstractCrdt {
7996
+ #synced;
7997
+ #local = /* @__PURE__ */ new Map();
7998
+ /**
7999
+ * Tracks unacknowledged local changes per property to preserve optimistic
8000
+ * updates. Maps property keys to their pending operation IDs.
8001
+ *
8002
+ * INVARIANT: Only locally-generated opIds are ever stored here. Remote opIds
8003
+ * are only compared against (to detect ACKs), never stored.
8004
+ *
8005
+ * When a local change is made, the opId is stored here. When a remote op
8006
+ * arrives for the same key:
8007
+ * - If no entry exists → apply remote op
8008
+ * - If opId matches → it's an ACK, clear the entry
8009
+ * - If opId differs → ignore remote op to preserve optimistic update
8010
+ */
8011
+ #unackedOpsByKey;
8012
+ /**
8013
+ * Enable or disable detection of too large LiveObjects.
8014
+ * When enabled, throws an error if LiveObject static data exceeds 128KB, which
8015
+ * is the maximum value the server will be able to accept.
8016
+ * By default, this behavior is disabled to avoid the runtime performance
8017
+ * overhead on every LiveObject.set() or LiveObject.update() call.
8018
+ *
8019
+ * @experimental
8020
+ */
8021
+ static detectLargeObjects = false;
8022
+ static #buildRootAndParentToChildren(nodes) {
8023
+ const parentToChildren = /* @__PURE__ */ new Map();
8024
+ let root = null;
8025
+ for (const node of nodes) {
8026
+ if (isRootStorageNode(node)) {
8027
+ root = node[1];
8028
+ } else {
8029
+ const crdt = node[1];
8030
+ const children = parentToChildren.get(crdt.parentId);
8031
+ if (children !== void 0) {
8032
+ children.push(node);
8033
+ } else {
8034
+ parentToChildren.set(crdt.parentId, [node]);
8035
+ }
8037
8036
  }
8038
- prevNode = prev[i];
8039
- nextNode = next[i];
8040
8037
  }
8041
- prevNode = prev[prevEnd];
8042
- nextNode = next[nextEnd];
8043
- while (prevNode === nextNode) {
8044
- prevEnd--;
8045
- nextEnd--;
8046
- if (i > prevEnd || i > nextEnd) {
8047
- break outer;
8048
- }
8049
- prevNode = prev[prevEnd];
8050
- nextNode = next[nextEnd];
8038
+ if (root === null) {
8039
+ throw new Error("Root can't be null");
8051
8040
  }
8041
+ return [root, parentToChildren];
8052
8042
  }
8053
- if (i > prevEnd) {
8054
- if (i <= nextEnd) {
8055
- while (i <= nextEnd) {
8056
- liveList.insert(deepLiveify(next[i]), i);
8057
- i++;
8043
+ /** @private Do not use this API directly */
8044
+ static _fromItems(nodes, pool) {
8045
+ const [root, parentToChildren] = _LiveObject.#buildRootAndParentToChildren(nodes);
8046
+ return _LiveObject._deserialize(
8047
+ ["root", root],
8048
+ parentToChildren,
8049
+ pool
8050
+ );
8051
+ }
8052
+ constructor(obj = {}) {
8053
+ super();
8054
+ this.#unackedOpsByKey = /* @__PURE__ */ new Map();
8055
+ const o = compactObject(obj);
8056
+ for (const key of Object.keys(o)) {
8057
+ const value = o[key];
8058
+ if (isLiveNode(value)) {
8059
+ value._setParentLink(this, key);
8058
8060
  }
8059
8061
  }
8060
- } else if (i > nextEnd) {
8061
- let localI = i;
8062
- while (localI <= prevEnd) {
8063
- liveList.delete(i);
8064
- localI++;
8062
+ this.#synced = new Map(Object.entries(o));
8063
+ }
8064
+ /** @internal */
8065
+ _toOps(parentId, parentKey) {
8066
+ if (this._id === void 0) {
8067
+ throw new Error("Cannot serialize item is not attached");
8065
8068
  }
8066
- } else {
8067
- while (i <= prevEnd && i <= nextEnd) {
8068
- prevNode = prev[i];
8069
- nextNode = next[i];
8070
- const liveListNode = liveList.get(i);
8071
- if (isLiveObject(liveListNode) && isPlainObject(prevNode) && isPlainObject(nextNode)) {
8072
- legacy_patchLiveObject(liveListNode, prevNode, nextNode);
8069
+ const ops = [];
8070
+ const op = {
8071
+ type: OpCode.CREATE_OBJECT,
8072
+ id: this._id,
8073
+ parentId,
8074
+ parentKey,
8075
+ data: {}
8076
+ };
8077
+ ops.push(op);
8078
+ for (const [key, value] of this.#synced) {
8079
+ if (isLiveNode(value)) {
8080
+ for (const childOp of value._toOps(this._id, key)) {
8081
+ ops.push(childOp);
8082
+ }
8073
8083
  } else {
8074
- liveList.set(i, deepLiveify(nextNode));
8084
+ op.data[key] = value;
8075
8085
  }
8076
- i++;
8077
8086
  }
8078
- while (i <= nextEnd) {
8079
- liveList.insert(deepLiveify(next[i]), i);
8080
- i++;
8087
+ return ops;
8088
+ }
8089
+ /** @internal */
8090
+ static _deserialize([id, item], parentToChildren, pool) {
8091
+ const liveObj = new _LiveObject(item.data);
8092
+ liveObj._attach(id, pool);
8093
+ return this._deserializeChildren(liveObj, parentToChildren, pool);
8094
+ }
8095
+ /** @internal */
8096
+ static _deserializeChildren(liveObj, parentToChildren, pool) {
8097
+ const children = parentToChildren.get(nn(liveObj._id));
8098
+ if (children === void 0) {
8099
+ return liveObj;
8081
8100
  }
8082
- let localI = i;
8083
- while (localI <= prevEnd) {
8084
- liveList.delete(i);
8085
- localI++;
8101
+ for (const node of children) {
8102
+ const child = deserializeToLson(node, parentToChildren, pool);
8103
+ const crdt = node[1];
8104
+ if (isLiveStructure(child)) {
8105
+ child._setParentLink(liveObj, crdt.parentKey);
8106
+ }
8107
+ liveObj.#synced.set(crdt.parentKey, child);
8108
+ liveObj.invalidate();
8086
8109
  }
8110
+ return liveObj;
8087
8111
  }
8088
- }
8089
- function legacy_patchLiveObjectKey(liveObject, key, prev, next) {
8090
- if (process.env.NODE_ENV !== "production") {
8091
- const nonSerializableValue = findNonSerializableValue(next);
8092
- if (nonSerializableValue) {
8093
- error2(
8094
- `New state path: '${nonSerializableValue.path}' value: '${String(
8095
- nonSerializableValue.value
8096
- )}' is not serializable.
8097
- Only serializable value can be synced with Liveblocks.`
8098
- );
8099
- return;
8112
+ /** @internal */
8113
+ _attach(id, pool) {
8114
+ super._attach(id, pool);
8115
+ for (const [_key, value] of this.#synced) {
8116
+ if (isLiveNode(value)) {
8117
+ value._attach(pool.generateId(), pool);
8118
+ }
8100
8119
  }
8101
8120
  }
8102
- const value = liveObject.get(key);
8103
- if (next === void 0) {
8104
- liveObject.delete(key);
8105
- } else if (value === void 0) {
8106
- liveObject.set(key, deepLiveify(next));
8107
- } else if (prev === next) {
8108
- return;
8109
- } else if (isLiveList(value) && Array.isArray(prev) && Array.isArray(next)) {
8110
- legacy_patchLiveList(value, prev, next);
8111
- } else if (isLiveObject(value) && isPlainObject(prev) && isPlainObject(next)) {
8112
- legacy_patchLiveObject(value, prev, next);
8113
- } else {
8114
- liveObject.set(key, deepLiveify(next));
8115
- }
8116
- }
8117
- function legacy_patchLiveObject(root, prev, next) {
8118
- const updates = {};
8119
- for (const key in next) {
8120
- legacy_patchLiveObjectKey(root, key, prev[key], next[key]);
8121
- }
8122
- for (const key in prev) {
8123
- if (next[key] === void 0) {
8124
- root.delete(key);
8125
- }
8126
- }
8127
- if (Object.keys(updates).length > 0) {
8128
- root.update(updates);
8129
- }
8130
- }
8131
- function getParentsPath(node) {
8132
- const path = [];
8133
- while (node.parent.type === "HasParent") {
8134
- if (isLiveList(node.parent.node)) {
8135
- path.push(node.parent.node._indexOfPosition(node.parent.key));
8121
+ /** @internal */
8122
+ _attachChild(op, source) {
8123
+ if (this._pool === void 0) {
8124
+ throw new Error("Can't attach child if managed pool is not present");
8125
+ }
8126
+ const { id, opId, parentKey: key } = op;
8127
+ const child = creationOpToLson(op);
8128
+ if (this._pool.getNode(id) !== void 0) {
8129
+ if (this.#unackedOpsByKey.get(key) === opId) {
8130
+ this.#unackedOpsByKey.delete(key);
8131
+ }
8132
+ return { modified: false };
8133
+ }
8134
+ if (source === 0 /* LOCAL */) {
8135
+ this.#unackedOpsByKey.set(key, nn(opId));
8136
+ } else if (this.#unackedOpsByKey.get(key) === void 0) {
8137
+ } else if (this.#unackedOpsByKey.get(key) === opId) {
8138
+ this.#unackedOpsByKey.delete(key);
8139
+ return { modified: false };
8136
8140
  } else {
8137
- path.push(node.parent.key);
8141
+ return { modified: false };
8138
8142
  }
8139
- node = node.parent.node;
8140
- }
8141
- return path;
8142
- }
8143
- function legacy_patchImmutableObject(state, updates) {
8144
- return updates.reduce(
8145
- (state2, update) => legacy_patchImmutableObjectWithUpdate(state2, update),
8146
- state
8147
- );
8148
- }
8149
- function legacy_patchImmutableObjectWithUpdate(state, update) {
8150
- const path = getParentsPath(update.node);
8151
- return legacy_patchImmutableNode(state, path, update);
8152
- }
8153
- function legacy_patchImmutableNode(state, path, update) {
8154
- const pathItem = path.pop();
8155
- if (pathItem === void 0) {
8156
- switch (update.type) {
8157
- case "LiveObject": {
8158
- if (!isJsonObject(state)) {
8159
- throw new Error(
8160
- "Internal: received update on LiveObject but state was not an object"
8161
- );
8162
- }
8163
- const newState = Object.assign({}, state);
8164
- for (const key in update.updates) {
8165
- if (update.updates[key]?.type === "update") {
8166
- const val = update.node.get(key);
8167
- if (val !== void 0) {
8168
- newState[key] = lsonToJson(val);
8169
- }
8170
- } else if (update.updates[key]?.type === "delete") {
8171
- delete newState[key];
8172
- }
8143
+ const thisId = nn(this._id);
8144
+ const previousValue = this.#synced.get(key);
8145
+ let reverse;
8146
+ if (isLiveNode(previousValue)) {
8147
+ reverse = previousValue._toOps(thisId, key);
8148
+ previousValue._detach();
8149
+ } else if (previousValue === void 0) {
8150
+ reverse = [{ type: OpCode.DELETE_OBJECT_KEY, id: thisId, key }];
8151
+ } else {
8152
+ reverse = [
8153
+ {
8154
+ type: OpCode.UPDATE_OBJECT,
8155
+ id: thisId,
8156
+ data: { [key]: previousValue }
8173
8157
  }
8174
- return newState;
8158
+ ];
8159
+ }
8160
+ this.#local.delete(key);
8161
+ this.#synced.set(key, child);
8162
+ this.invalidate();
8163
+ if (isLiveStructure(child)) {
8164
+ child._setParentLink(this, key);
8165
+ child._attach(id, this._pool);
8166
+ }
8167
+ return {
8168
+ reverse,
8169
+ modified: {
8170
+ node: this,
8171
+ type: "LiveObject",
8172
+ updates: { [key]: { type: "update" } }
8175
8173
  }
8176
- case "LiveList": {
8177
- if (!Array.isArray(state)) {
8178
- throw new Error(
8179
- "Internal: received update on LiveList but state was not an array"
8180
- );
8181
- }
8182
- let newState = state.map((x) => x);
8183
- for (const listUpdate of update.updates) {
8184
- if (listUpdate.type === "set") {
8185
- newState = newState.map(
8186
- (item, index) => index === listUpdate.index ? lsonToJson(listUpdate.item) : item
8187
- );
8188
- } else if (listUpdate.type === "insert") {
8189
- if (listUpdate.index === newState.length) {
8190
- newState.push(lsonToJson(listUpdate.item));
8191
- } else {
8192
- newState = [
8193
- ...newState.slice(0, listUpdate.index),
8194
- lsonToJson(listUpdate.item),
8195
- ...newState.slice(listUpdate.index)
8196
- ];
8197
- }
8198
- } else if (listUpdate.type === "delete") {
8199
- newState.splice(listUpdate.index, 1);
8200
- } else if (listUpdate.type === "move") {
8201
- if (listUpdate.previousIndex > listUpdate.index) {
8202
- newState = [
8203
- ...newState.slice(0, listUpdate.index),
8204
- lsonToJson(listUpdate.item),
8205
- ...newState.slice(listUpdate.index, listUpdate.previousIndex),
8206
- ...newState.slice(listUpdate.previousIndex + 1)
8207
- ];
8208
- } else {
8209
- newState = [
8210
- ...newState.slice(0, listUpdate.previousIndex),
8211
- ...newState.slice(
8212
- listUpdate.previousIndex + 1,
8213
- listUpdate.index + 1
8214
- ),
8215
- lsonToJson(listUpdate.item),
8216
- ...newState.slice(listUpdate.index + 1)
8217
- ];
8218
- }
8219
- }
8174
+ };
8175
+ }
8176
+ /** @internal */
8177
+ _detachChild(child) {
8178
+ if (child) {
8179
+ const id = nn(this._id);
8180
+ const parentKey = nn(child._parentKey);
8181
+ const reverse = child._toOps(id, parentKey);
8182
+ for (const [key, value] of this.#synced) {
8183
+ if (value === child) {
8184
+ this.#synced.delete(key);
8185
+ this.invalidate();
8220
8186
  }
8221
- return newState;
8222
8187
  }
8223
- case "LiveMap": {
8224
- if (!isJsonObject(state)) {
8225
- throw new Error(
8226
- "Internal: received update on LiveMap but state was not an object"
8227
- );
8228
- }
8229
- const newState = Object.assign({}, state);
8230
- for (const key in update.updates) {
8231
- if (update.updates[key]?.type === "update") {
8232
- const value = update.node.get(key);
8233
- if (value !== void 0) {
8234
- newState[key] = lsonToJson(value);
8235
- }
8236
- } else if (update.updates[key]?.type === "delete") {
8237
- delete newState[key];
8238
- }
8188
+ child._detach();
8189
+ const storageUpdate = {
8190
+ node: this,
8191
+ type: "LiveObject",
8192
+ updates: {
8193
+ [parentKey]: { type: "delete" }
8239
8194
  }
8240
- return newState;
8195
+ };
8196
+ return { modified: storageUpdate, reverse };
8197
+ }
8198
+ return { modified: false };
8199
+ }
8200
+ /** @internal */
8201
+ _detach() {
8202
+ super._detach();
8203
+ for (const value of this.#synced.values()) {
8204
+ if (isLiveNode(value)) {
8205
+ value._detach();
8241
8206
  }
8242
8207
  }
8243
8208
  }
8244
- if (Array.isArray(state)) {
8245
- const newArray = [...state];
8246
- newArray[pathItem] = legacy_patchImmutableNode(
8247
- state[pathItem],
8248
- path,
8249
- update
8250
- );
8251
- return newArray;
8252
- } else if (isJsonObject(state)) {
8253
- const node = state[pathItem];
8254
- if (node === void 0) {
8255
- return state;
8209
+ /** @internal */
8210
+ _apply(op, isLocal) {
8211
+ if (op.type === OpCode.UPDATE_OBJECT) {
8212
+ return this.#applyUpdate(op, isLocal);
8213
+ } else if (op.type === OpCode.DELETE_OBJECT_KEY) {
8214
+ return this.#applyDeleteObjectKey(op, isLocal);
8215
+ }
8216
+ return super._apply(op, isLocal);
8217
+ }
8218
+ /** @internal */
8219
+ _serialize() {
8220
+ const data = {};
8221
+ for (const [key, value] of this.#synced) {
8222
+ if (!isLiveNode(value)) {
8223
+ data[key] = value;
8224
+ }
8225
+ }
8226
+ if (this.parent.type === "HasParent" && this.parent.node._id) {
8227
+ return {
8228
+ type: CrdtType.OBJECT,
8229
+ parentId: this.parent.node._id,
8230
+ parentKey: this.parent.key,
8231
+ data
8232
+ };
8256
8233
  } else {
8257
- const stateAsObj = state;
8258
8234
  return {
8259
- ...stateAsObj,
8260
- [pathItem]: legacy_patchImmutableNode(node, path, update)
8261
- };
8262
- }
8263
- } else {
8264
- return state;
8265
- }
8266
- }
8267
-
8268
- // src/crdts/LiveObject.ts
8269
- var MAX_LIVE_OBJECT_SIZE = 128 * 1024;
8270
- var LiveObject = class _LiveObject extends AbstractCrdt {
8271
- #synced;
8272
- #local = /* @__PURE__ */ new Map();
8273
- /**
8274
- * Tracks unacknowledged local changes per property to preserve optimistic
8275
- * updates. Maps property keys to their pending operation IDs.
8276
- *
8277
- * INVARIANT: Only locally-generated opIds are ever stored here. Remote opIds
8278
- * are only compared against (to detect ACKs), never stored.
8279
- *
8280
- * When a local change is made, the opId is stored here. When a remote op
8281
- * arrives for the same key:
8282
- * - If no entry exists → apply remote op
8283
- * - If opId matches → it's an ACK, clear the entry
8284
- * - If opId differs → ignore remote op to preserve optimistic update
8285
- */
8286
- #unackedOpsByKey;
8287
- /**
8288
- * Enable or disable detection of too large LiveObjects.
8289
- * When enabled, throws an error if LiveObject static data exceeds 128KB, which
8290
- * is the maximum value the server will be able to accept.
8291
- * By default, this behavior is disabled to avoid the runtime performance
8292
- * overhead on every LiveObject.set() or LiveObject.update() call.
8293
- *
8294
- * @experimental
8295
- */
8296
- static detectLargeObjects = false;
8297
- static #buildRootAndParentToChildren(nodes) {
8298
- const parentToChildren = /* @__PURE__ */ new Map();
8299
- let root = null;
8300
- for (const node of nodes) {
8301
- if (isRootStorageNode(node)) {
8302
- root = node[1];
8303
- } else {
8304
- const crdt = node[1];
8305
- const children = parentToChildren.get(crdt.parentId);
8306
- if (children !== void 0) {
8307
- children.push(node);
8308
- } else {
8309
- parentToChildren.set(crdt.parentId, [node]);
8310
- }
8311
- }
8312
- }
8313
- if (root === null) {
8314
- throw new Error("Root can't be null");
8315
- }
8316
- return [root, parentToChildren];
8317
- }
8318
- /** @private Do not use this API directly */
8319
- static _fromItems(nodes, pool) {
8320
- const [root, parentToChildren] = _LiveObject.#buildRootAndParentToChildren(nodes);
8321
- return _LiveObject._deserialize(
8322
- ["root", root],
8323
- parentToChildren,
8324
- pool
8325
- );
8326
- }
8327
- constructor(obj = {}) {
8328
- super();
8329
- this.#unackedOpsByKey = /* @__PURE__ */ new Map();
8330
- const o = compactObject(obj);
8331
- for (const key of Object.keys(o)) {
8332
- const value = o[key];
8333
- if (isLiveNode(value)) {
8334
- value._setParentLink(this, key);
8335
- }
8336
- }
8337
- this.#synced = new Map(Object.entries(o));
8338
- }
8339
- /** @internal */
8340
- _toOps(parentId, parentKey) {
8341
- if (this._id === void 0) {
8342
- throw new Error("Cannot serialize item is not attached");
8343
- }
8344
- const ops = [];
8345
- const op = {
8346
- type: OpCode.CREATE_OBJECT,
8347
- id: this._id,
8348
- parentId,
8349
- parentKey,
8350
- data: {}
8351
- };
8352
- ops.push(op);
8353
- for (const [key, value] of this.#synced) {
8354
- if (isLiveNode(value)) {
8355
- for (const childOp of value._toOps(this._id, key)) {
8356
- ops.push(childOp);
8357
- }
8358
- } else {
8359
- op.data[key] = value;
8360
- }
8361
- }
8362
- return ops;
8363
- }
8364
- /** @internal */
8365
- static _deserialize([id, item], parentToChildren, pool) {
8366
- const liveObj = new _LiveObject(item.data);
8367
- liveObj._attach(id, pool);
8368
- return this._deserializeChildren(liveObj, parentToChildren, pool);
8369
- }
8370
- /** @internal */
8371
- static _deserializeChildren(liveObj, parentToChildren, pool) {
8372
- const children = parentToChildren.get(nn(liveObj._id));
8373
- if (children === void 0) {
8374
- return liveObj;
8375
- }
8376
- for (const node of children) {
8377
- const child = deserializeToLson(node, parentToChildren, pool);
8378
- const crdt = node[1];
8379
- if (isLiveStructure(child)) {
8380
- child._setParentLink(liveObj, crdt.parentKey);
8381
- }
8382
- liveObj.#synced.set(crdt.parentKey, child);
8383
- liveObj.invalidate();
8384
- }
8385
- return liveObj;
8386
- }
8387
- /** @internal */
8388
- _attach(id, pool) {
8389
- super._attach(id, pool);
8390
- for (const [_key, value] of this.#synced) {
8391
- if (isLiveNode(value)) {
8392
- value._attach(pool.generateId(), pool);
8393
- }
8394
- }
8395
- }
8396
- /** @internal */
8397
- _attachChild(op, source) {
8398
- if (this._pool === void 0) {
8399
- throw new Error("Can't attach child if managed pool is not present");
8400
- }
8401
- const { id, opId, parentKey: key } = op;
8402
- const child = creationOpToLson(op);
8403
- if (this._pool.getNode(id) !== void 0) {
8404
- if (this.#unackedOpsByKey.get(key) === opId) {
8405
- this.#unackedOpsByKey.delete(key);
8406
- }
8407
- return { modified: false };
8408
- }
8409
- if (source === 0 /* LOCAL */) {
8410
- this.#unackedOpsByKey.set(key, nn(opId));
8411
- } else if (this.#unackedOpsByKey.get(key) === void 0) {
8412
- } else if (this.#unackedOpsByKey.get(key) === opId) {
8413
- this.#unackedOpsByKey.delete(key);
8414
- return { modified: false };
8415
- } else {
8416
- return { modified: false };
8417
- }
8418
- const thisId = nn(this._id);
8419
- const previousValue = this.#synced.get(key);
8420
- let reverse;
8421
- if (isLiveNode(previousValue)) {
8422
- reverse = previousValue._toOps(thisId, key);
8423
- previousValue._detach();
8424
- } else if (previousValue === void 0) {
8425
- reverse = [{ type: OpCode.DELETE_OBJECT_KEY, id: thisId, key }];
8426
- } else {
8427
- reverse = [
8428
- {
8429
- type: OpCode.UPDATE_OBJECT,
8430
- id: thisId,
8431
- data: { [key]: previousValue }
8432
- }
8433
- ];
8434
- }
8435
- this.#local.delete(key);
8436
- this.#synced.set(key, child);
8437
- this.invalidate();
8438
- if (isLiveStructure(child)) {
8439
- child._setParentLink(this, key);
8440
- child._attach(id, this._pool);
8441
- }
8442
- return {
8443
- reverse,
8444
- modified: {
8445
- node: this,
8446
- type: "LiveObject",
8447
- updates: { [key]: { type: "update" } }
8448
- }
8449
- };
8450
- }
8451
- /** @internal */
8452
- _detachChild(child) {
8453
- if (child) {
8454
- const id = nn(this._id);
8455
- const parentKey = nn(child._parentKey);
8456
- const reverse = child._toOps(id, parentKey);
8457
- for (const [key, value] of this.#synced) {
8458
- if (value === child) {
8459
- this.#synced.delete(key);
8460
- this.invalidate();
8461
- }
8462
- }
8463
- child._detach();
8464
- const storageUpdate = {
8465
- node: this,
8466
- type: "LiveObject",
8467
- updates: {
8468
- [parentKey]: { type: "delete" }
8469
- }
8470
- };
8471
- return { modified: storageUpdate, reverse };
8472
- }
8473
- return { modified: false };
8474
- }
8475
- /** @internal */
8476
- _detach() {
8477
- super._detach();
8478
- for (const value of this.#synced.values()) {
8479
- if (isLiveNode(value)) {
8480
- value._detach();
8481
- }
8482
- }
8483
- }
8484
- /** @internal */
8485
- _apply(op, isLocal) {
8486
- if (op.type === OpCode.UPDATE_OBJECT) {
8487
- return this.#applyUpdate(op, isLocal);
8488
- } else if (op.type === OpCode.DELETE_OBJECT_KEY) {
8489
- return this.#applyDeleteObjectKey(op, isLocal);
8490
- }
8491
- return super._apply(op, isLocal);
8492
- }
8493
- /** @internal */
8494
- _serialize() {
8495
- const data = {};
8496
- for (const [key, value] of this.#synced) {
8497
- if (!isLiveNode(value)) {
8498
- data[key] = value;
8499
- }
8500
- }
8501
- if (this.parent.type === "HasParent" && this.parent.node._id) {
8502
- return {
8503
- type: CrdtType.OBJECT,
8504
- parentId: this.parent.node._id,
8505
- parentKey: this.parent.key,
8506
- data
8507
- };
8508
- } else {
8509
- return {
8510
- type: CrdtType.OBJECT,
8511
- data
8235
+ type: CrdtType.OBJECT,
8236
+ data
8512
8237
  };
8513
8238
  }
8514
8239
  }
@@ -8824,6 +8549,9 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8824
8549
  continue;
8825
8550
  }
8826
8551
  const oldValue = this.#synced.get(key);
8552
+ if (oldValue === newValue) {
8553
+ continue;
8554
+ }
8827
8555
  if (isLiveNode(oldValue)) {
8828
8556
  for (const childOp of oldValue._toOps(this._id, key)) {
8829
8557
  reverseOps.push(childOp);
@@ -8871,6 +8599,9 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8871
8599
  data: updatedProps
8872
8600
  });
8873
8601
  }
8602
+ if (ops.length === 0 && reverseOps.length === 0 && Object.keys(updateDelta).length === 0) {
8603
+ return;
8604
+ }
8874
8605
  const storageUpdates = /* @__PURE__ */ new Map();
8875
8606
  storageUpdates.set(this._id, {
8876
8607
  node: this,
@@ -9249,6 +8980,17 @@ var Deque = class {
9249
8980
  }
9250
8981
  };
9251
8982
 
8983
+ // src/lib/Json.ts
8984
+ function isJsonScalar(data) {
8985
+ return data === null || typeof data === "string" || typeof data === "number" || typeof data === "boolean";
8986
+ }
8987
+ function isJsonArray(data) {
8988
+ return Array.isArray(data);
8989
+ }
8990
+ function isJsonObject(data) {
8991
+ return !isJsonScalar(data) && !isJsonArray(data);
8992
+ }
8993
+
9252
8994
  // src/lib/stopwatch.ts
9253
8995
  function makeStopWatch() {
9254
8996
  let startTime = 0;
@@ -9282,7 +9024,16 @@ var ClientMsgCode = Object.freeze({
9282
9024
  UPDATE_STORAGE: 201,
9283
9025
  // For Yjs support
9284
9026
  FETCH_YDOC: 300,
9285
- UPDATE_YDOC: 301
9027
+ UPDATE_YDOC: 301,
9028
+ // For Feeds
9029
+ FETCH_FEEDS: 510,
9030
+ FETCH_FEED_MESSAGES: 511,
9031
+ ADD_FEED: 512,
9032
+ UPDATE_FEED: 513,
9033
+ DELETE_FEED: 514,
9034
+ ADD_FEED_MESSAGE: 515,
9035
+ UPDATE_FEED_MESSAGE: 516,
9036
+ DELETE_FEED_MESSAGE: 517
9286
9037
  });
9287
9038
 
9288
9039
  // src/refs/ManagedOthers.ts
@@ -9518,12 +9269,15 @@ function defaultMessageFromContext(context) {
9518
9269
  return "Could not update notification settings";
9519
9270
  case "LARGE_MESSAGE_ERROR":
9520
9271
  return "Could not send large message";
9272
+ case "FEED_REQUEST_ERROR":
9273
+ return context.reason ?? "Feed request failed";
9521
9274
  default:
9522
9275
  return assertNever(context, "Unhandled case");
9523
9276
  }
9524
9277
  }
9525
9278
 
9526
9279
  // src/room.ts
9280
+ var FEEDS_TIMEOUT = 5e3;
9527
9281
  function makeIdFactory(connectionId) {
9528
9282
  let count = 0;
9529
9283
  return () => `${connectionId}:${count++}`;
@@ -9762,6 +9516,7 @@ function createRoom(options, config) {
9762
9516
  storageStatus: makeEventSource(),
9763
9517
  ydoc: makeEventSource(),
9764
9518
  comments: makeEventSource(),
9519
+ feeds: makeEventSource(),
9765
9520
  roomWillDestroy: makeEventSource()
9766
9521
  };
9767
9522
  async function createTextMention(mentionId, mention) {
@@ -10183,6 +9938,9 @@ function createRoom(options, config) {
10183
9938
  notify(result.updates);
10184
9939
  sendMessages(messages);
10185
9940
  }
9941
+ function isFeedRequestFailedMsg(msg) {
9942
+ return msg.type === ServerMsgCode.FEED_REQUEST_FAILED;
9943
+ }
10186
9944
  function handleServerMessage(event) {
10187
9945
  if (typeof event.data !== "string") {
10188
9946
  return;
@@ -10296,23 +10054,111 @@ function createRoom(options, config) {
10296
10054
  eventHub.comments.notify(message);
10297
10055
  break;
10298
10056
  }
10299
- case ServerMsgCode.STORAGE_STATE_V7:
10300
- // No longer used in V8
10301
- default:
10057
+ case ServerMsgCode.FEEDS_LIST: {
10058
+ const feedsListMsg = message;
10059
+ const pending = pendingFeedsRequests.get(feedsListMsg.requestId);
10060
+ if (pending) {
10061
+ pending.resolve({
10062
+ feeds: feedsListMsg.feeds,
10063
+ nextCursor: feedsListMsg.nextCursor
10064
+ });
10065
+ pendingFeedsRequests.delete(feedsListMsg.requestId);
10066
+ }
10067
+ eventHub.feeds.notify(feedsListMsg);
10302
10068
  break;
10303
- }
10304
- }
10305
- notify(updates);
10306
- }
10307
- function flushNowOrSoon() {
10308
- const storageOps = context.buffer.storageOperations;
10309
- if (storageOps.length > 0) {
10310
- for (const op of storageOps) {
10311
- context.unacknowledgedOps.set(op.opId, op);
10312
- }
10313
- notifyStorageStatus();
10314
- }
10315
- if (managedSocket.getStatus() !== "connected") {
10069
+ }
10070
+ case ServerMsgCode.FEEDS_ADDED: {
10071
+ const feedsAddedMsg = message;
10072
+ eventHub.feeds.notify(feedsAddedMsg);
10073
+ tryResolvePendingFeedMutationsFromFeedsEvent(feedsAddedMsg);
10074
+ break;
10075
+ }
10076
+ case ServerMsgCode.FEEDS_UPDATED: {
10077
+ const feedsUpdatedMsg = message;
10078
+ eventHub.feeds.notify(feedsUpdatedMsg);
10079
+ tryResolvePendingFeedMutationsFromFeedsEvent(feedsUpdatedMsg);
10080
+ break;
10081
+ }
10082
+ case ServerMsgCode.FEED_DELETED: {
10083
+ eventHub.feeds.notify(message);
10084
+ tryResolvePendingFeedMutationsFromFeedsEvent(message);
10085
+ break;
10086
+ }
10087
+ case ServerMsgCode.FEED_MESSAGES_LIST: {
10088
+ const feedMsgsListMsg = message;
10089
+ const pending = pendingFeedMessagesRequests.get(
10090
+ feedMsgsListMsg.requestId
10091
+ );
10092
+ if (pending) {
10093
+ pending.resolve({
10094
+ messages: feedMsgsListMsg.messages,
10095
+ nextCursor: feedMsgsListMsg.nextCursor
10096
+ });
10097
+ pendingFeedMessagesRequests.delete(feedMsgsListMsg.requestId);
10098
+ }
10099
+ eventHub.feeds.notify(feedMsgsListMsg);
10100
+ break;
10101
+ }
10102
+ case ServerMsgCode.FEED_MESSAGES_ADDED: {
10103
+ const feedMsgsAddedMsg = message;
10104
+ eventHub.feeds.notify(feedMsgsAddedMsg);
10105
+ tryResolvePendingFeedMutationsFromFeedsEvent(feedMsgsAddedMsg);
10106
+ break;
10107
+ }
10108
+ case ServerMsgCode.FEED_MESSAGES_UPDATED: {
10109
+ const feedMsgsUpdatedMsg = message;
10110
+ eventHub.feeds.notify(feedMsgsUpdatedMsg);
10111
+ tryResolvePendingFeedMutationsFromFeedsEvent(feedMsgsUpdatedMsg);
10112
+ break;
10113
+ }
10114
+ case ServerMsgCode.FEED_MESSAGES_DELETED: {
10115
+ eventHub.feeds.notify(message);
10116
+ tryResolvePendingFeedMutationsFromFeedsEvent(message);
10117
+ break;
10118
+ }
10119
+ case ServerMsgCode.FEED_REQUEST_FAILED: {
10120
+ if (!isFeedRequestFailedMsg(message)) {
10121
+ break;
10122
+ }
10123
+ const { requestId, code, reason } = message;
10124
+ const err = new LiveblocksError(reason ?? "Feed request failed", {
10125
+ type: "FEED_REQUEST_ERROR",
10126
+ roomId,
10127
+ requestId,
10128
+ code,
10129
+ reason
10130
+ });
10131
+ if (pendingFeedMutations.has(requestId)) {
10132
+ settleFeedMutation(requestId, "error", err);
10133
+ } else if (pendingFeedsRequests.has(requestId)) {
10134
+ const pending = pendingFeedsRequests.get(requestId);
10135
+ pendingFeedsRequests.delete(requestId);
10136
+ pending?.reject(err);
10137
+ } else if (pendingFeedMessagesRequests.has(requestId)) {
10138
+ const pending = pendingFeedMessagesRequests.get(requestId);
10139
+ pendingFeedMessagesRequests.delete(requestId);
10140
+ pending?.reject(err);
10141
+ }
10142
+ eventHub.feeds.notify(message);
10143
+ break;
10144
+ }
10145
+ case ServerMsgCode.STORAGE_STATE_V7:
10146
+ // No longer used in V8
10147
+ default:
10148
+ break;
10149
+ }
10150
+ }
10151
+ notify(updates);
10152
+ }
10153
+ function flushNowOrSoon() {
10154
+ const storageOps = context.buffer.storageOperations;
10155
+ if (storageOps.length > 0) {
10156
+ for (const op of storageOps) {
10157
+ context.unacknowledgedOps.set(op.opId, op);
10158
+ }
10159
+ notifyStorageStatus();
10160
+ }
10161
+ if (managedSocket.getStatus() !== "connected") {
10316
10162
  context.buffer.storageOperations = [];
10317
10163
  return;
10318
10164
  }
@@ -10399,6 +10245,141 @@ function createRoom(options, config) {
10399
10245
  }
10400
10246
  let _getStorage$ = null;
10401
10247
  let _resolveStoragePromise = null;
10248
+ const pendingFeedsRequests = /* @__PURE__ */ new Map();
10249
+ const pendingFeedMessagesRequests = /* @__PURE__ */ new Map();
10250
+ const pendingFeedMutations = /* @__PURE__ */ new Map();
10251
+ const pendingAddMessageFifoByFeed = /* @__PURE__ */ new Map();
10252
+ function settleFeedMutation(requestId, outcome, error3) {
10253
+ const pending = pendingFeedMutations.get(requestId);
10254
+ if (pending === void 0) {
10255
+ return;
10256
+ }
10257
+ clearTimeout(pending.timeoutId);
10258
+ pendingFeedMutations.delete(requestId);
10259
+ if (pending.kind === "add-message" && !pending.expectedClientMessageId) {
10260
+ const q = pendingAddMessageFifoByFeed.get(pending.feedId);
10261
+ if (q !== void 0) {
10262
+ const idx = q.indexOf(requestId);
10263
+ if (idx >= 0) {
10264
+ q.splice(idx, 1);
10265
+ }
10266
+ if (q.length === 0) {
10267
+ pendingAddMessageFifoByFeed.delete(pending.feedId);
10268
+ }
10269
+ }
10270
+ }
10271
+ if (outcome === "ok") {
10272
+ pending.resolve();
10273
+ } else {
10274
+ pending.reject(error3 ?? new Error("Feed mutation failed"));
10275
+ }
10276
+ }
10277
+ function registerFeedMutation(requestId, kind, feedId, options2) {
10278
+ const { promise, resolve, reject } = Promise_withResolvers();
10279
+ const timeoutId = setTimeout(() => {
10280
+ if (pendingFeedMutations.has(requestId)) {
10281
+ settleFeedMutation(
10282
+ requestId,
10283
+ "error",
10284
+ new Error("Feed mutation timeout")
10285
+ );
10286
+ }
10287
+ }, FEEDS_TIMEOUT);
10288
+ pendingFeedMutations.set(requestId, {
10289
+ resolve,
10290
+ reject,
10291
+ timeoutId,
10292
+ kind,
10293
+ feedId,
10294
+ messageId: options2?.messageId,
10295
+ expectedClientMessageId: options2?.expectedClientMessageId
10296
+ });
10297
+ if (kind === "add-message" && options2?.expectedClientMessageId === void 0) {
10298
+ const q = pendingAddMessageFifoByFeed.get(feedId) ?? [];
10299
+ q.push(requestId);
10300
+ pendingAddMessageFifoByFeed.set(feedId, q);
10301
+ }
10302
+ return promise;
10303
+ }
10304
+ function tryResolvePendingFeedMutationsFromFeedsEvent(message) {
10305
+ switch (message.type) {
10306
+ case ServerMsgCode.FEEDS_ADDED: {
10307
+ for (const feed of message.feeds) {
10308
+ for (const [requestId, pending] of [...pendingFeedMutations]) {
10309
+ if (pending.kind === "add-feed" && pending.feedId === feed.feedId) {
10310
+ settleFeedMutation(requestId, "ok");
10311
+ break;
10312
+ }
10313
+ }
10314
+ }
10315
+ break;
10316
+ }
10317
+ case ServerMsgCode.FEEDS_UPDATED: {
10318
+ for (const feed of message.feeds) {
10319
+ for (const [requestId, pending] of [...pendingFeedMutations]) {
10320
+ if (pending.kind === "update-feed" && pending.feedId === feed.feedId) {
10321
+ settleFeedMutation(requestId, "ok");
10322
+ }
10323
+ }
10324
+ }
10325
+ break;
10326
+ }
10327
+ case ServerMsgCode.FEED_DELETED: {
10328
+ for (const [requestId, pending] of [...pendingFeedMutations]) {
10329
+ if (pending.kind === "delete-feed" && pending.feedId === message.feedId) {
10330
+ settleFeedMutation(requestId, "ok");
10331
+ break;
10332
+ }
10333
+ }
10334
+ break;
10335
+ }
10336
+ case ServerMsgCode.FEED_MESSAGES_ADDED: {
10337
+ for (const m of message.messages) {
10338
+ let matched = false;
10339
+ for (const [requestId, pending] of [...pendingFeedMutations]) {
10340
+ if (pending.kind === "add-message" && pending.feedId === message.feedId && pending.expectedClientMessageId === m.id) {
10341
+ settleFeedMutation(requestId, "ok");
10342
+ matched = true;
10343
+ break;
10344
+ }
10345
+ }
10346
+ if (!matched) {
10347
+ const q = pendingAddMessageFifoByFeed.get(message.feedId);
10348
+ const headId = q?.[0];
10349
+ if (headId !== void 0) {
10350
+ const pending = pendingFeedMutations.get(headId);
10351
+ if (pending?.kind === "add-message" && pending.expectedClientMessageId === void 0) {
10352
+ settleFeedMutation(headId, "ok");
10353
+ }
10354
+ }
10355
+ }
10356
+ }
10357
+ break;
10358
+ }
10359
+ case ServerMsgCode.FEED_MESSAGES_UPDATED: {
10360
+ for (const m of message.messages) {
10361
+ for (const [requestId, pending] of [...pendingFeedMutations]) {
10362
+ if (pending.kind === "update-message" && pending.feedId === message.feedId && pending.messageId === m.id) {
10363
+ settleFeedMutation(requestId, "ok");
10364
+ }
10365
+ }
10366
+ }
10367
+ break;
10368
+ }
10369
+ case ServerMsgCode.FEED_MESSAGES_DELETED: {
10370
+ for (const mid of message.messageIds) {
10371
+ for (const [requestId, pending] of [...pendingFeedMutations]) {
10372
+ if (pending.kind === "delete-message" && pending.feedId === message.feedId && pending.messageId === mid) {
10373
+ settleFeedMutation(requestId, "ok");
10374
+ }
10375
+ }
10376
+ }
10377
+ break;
10378
+ }
10379
+ default:
10380
+ break;
10381
+ }
10382
+ }
10402
10383
  function processInitialStorage(nodes) {
10403
10384
  const unacknowledgedOps = new Map(context.unacknowledgedOps);
10404
10385
  createOrUpdateRootFromMessage(nodes);
@@ -10470,6 +10451,138 @@ function createRoom(options, config) {
10470
10451
  }
10471
10452
  flushNowOrSoon();
10472
10453
  }
10454
+ async function fetchFeeds(options2) {
10455
+ const requestId = nanoid();
10456
+ const { promise, resolve, reject } = Promise_withResolvers();
10457
+ pendingFeedsRequests.set(requestId, { resolve, reject });
10458
+ const message = {
10459
+ type: ClientMsgCode.FETCH_FEEDS,
10460
+ requestId,
10461
+ cursor: options2?.cursor,
10462
+ since: options2?.since,
10463
+ limit: options2?.limit,
10464
+ metadata: options2?.metadata
10465
+ };
10466
+ context.buffer.messages.push(message);
10467
+ flushNowOrSoon();
10468
+ setTimeout(() => {
10469
+ if (pendingFeedsRequests.has(requestId)) {
10470
+ pendingFeedsRequests.delete(requestId);
10471
+ reject(new Error("Feeds fetch timeout"));
10472
+ }
10473
+ }, FEEDS_TIMEOUT);
10474
+ return promise;
10475
+ }
10476
+ async function fetchFeedMessages(feedId, options2) {
10477
+ const requestId = nanoid();
10478
+ const { promise, resolve, reject } = Promise_withResolvers();
10479
+ pendingFeedMessagesRequests.set(requestId, { resolve, reject });
10480
+ const message = {
10481
+ type: ClientMsgCode.FETCH_FEED_MESSAGES,
10482
+ requestId,
10483
+ feedId,
10484
+ cursor: options2?.cursor,
10485
+ since: options2?.since,
10486
+ limit: options2?.limit
10487
+ };
10488
+ context.buffer.messages.push(message);
10489
+ flushNowOrSoon();
10490
+ setTimeout(() => {
10491
+ if (pendingFeedMessagesRequests.has(requestId)) {
10492
+ pendingFeedMessagesRequests.delete(requestId);
10493
+ reject(new Error("Feed messages fetch timeout"));
10494
+ }
10495
+ }, FEEDS_TIMEOUT);
10496
+ return promise;
10497
+ }
10498
+ function addFeed(feedId, options2) {
10499
+ const requestId = nanoid();
10500
+ const promise = registerFeedMutation(requestId, "add-feed", feedId);
10501
+ const message = {
10502
+ type: ClientMsgCode.ADD_FEED,
10503
+ requestId,
10504
+ feedId,
10505
+ metadata: options2?.metadata,
10506
+ createdAt: options2?.createdAt
10507
+ };
10508
+ context.buffer.messages.push(message);
10509
+ flushNowOrSoon();
10510
+ return promise;
10511
+ }
10512
+ function updateFeed(feedId, metadata) {
10513
+ const requestId = nanoid();
10514
+ const promise = registerFeedMutation(requestId, "update-feed", feedId);
10515
+ const message = {
10516
+ type: ClientMsgCode.UPDATE_FEED,
10517
+ requestId,
10518
+ feedId,
10519
+ metadata
10520
+ };
10521
+ context.buffer.messages.push(message);
10522
+ flushNowOrSoon();
10523
+ return promise;
10524
+ }
10525
+ function deleteFeed(feedId) {
10526
+ const requestId = nanoid();
10527
+ const promise = registerFeedMutation(requestId, "delete-feed", feedId);
10528
+ const message = {
10529
+ type: ClientMsgCode.DELETE_FEED,
10530
+ requestId,
10531
+ feedId
10532
+ };
10533
+ context.buffer.messages.push(message);
10534
+ flushNowOrSoon();
10535
+ return promise;
10536
+ }
10537
+ function addFeedMessage(feedId, data, options2) {
10538
+ const requestId = nanoid();
10539
+ const promise = registerFeedMutation(requestId, "add-message", feedId, {
10540
+ expectedClientMessageId: options2?.id
10541
+ });
10542
+ const message = {
10543
+ type: ClientMsgCode.ADD_FEED_MESSAGE,
10544
+ requestId,
10545
+ feedId,
10546
+ data,
10547
+ id: options2?.id,
10548
+ createdAt: options2?.createdAt
10549
+ };
10550
+ context.buffer.messages.push(message);
10551
+ flushNowOrSoon();
10552
+ return promise;
10553
+ }
10554
+ function updateFeedMessage(feedId, messageId, data, options2) {
10555
+ const requestId = nanoid();
10556
+ const promise = registerFeedMutation(requestId, "update-message", feedId, {
10557
+ messageId
10558
+ });
10559
+ const message = {
10560
+ type: ClientMsgCode.UPDATE_FEED_MESSAGE,
10561
+ requestId,
10562
+ feedId,
10563
+ messageId,
10564
+ data,
10565
+ updatedAt: options2?.updatedAt
10566
+ };
10567
+ context.buffer.messages.push(message);
10568
+ flushNowOrSoon();
10569
+ return promise;
10570
+ }
10571
+ function deleteFeedMessage(feedId, messageId) {
10572
+ const requestId = nanoid();
10573
+ const promise = registerFeedMutation(requestId, "delete-message", feedId, {
10574
+ messageId
10575
+ });
10576
+ const message = {
10577
+ type: ClientMsgCode.DELETE_FEED_MESSAGE,
10578
+ requestId,
10579
+ feedId,
10580
+ messageId
10581
+ };
10582
+ context.buffer.messages.push(message);
10583
+ flushNowOrSoon();
10584
+ return promise;
10585
+ }
10473
10586
  function undo() {
10474
10587
  if (context.activeBatch) {
10475
10588
  throw new Error("undo is not allowed during a batch");
@@ -10522,7 +10635,8 @@ function createRoom(options, config) {
10522
10635
  presence: false,
10523
10636
  others: []
10524
10637
  },
10525
- reverseOps: new Deque()
10638
+ reverseOps: new Deque(),
10639
+ scheduleHistoryResume: false
10526
10640
  };
10527
10641
  try {
10528
10642
  returnValue = callback();
@@ -10532,6 +10646,9 @@ function createRoom(options, config) {
10532
10646
  if (currentBatch.reverseOps.length > 0) {
10533
10647
  addToUndoStack(Array.from(currentBatch.reverseOps));
10534
10648
  }
10649
+ if (currentBatch.scheduleHistoryResume) {
10650
+ commitPausedHistoryToUndoStack();
10651
+ }
10535
10652
  if (currentBatch.ops.length > 0) {
10536
10653
  context.redoStack.length = 0;
10537
10654
  }
@@ -10548,13 +10665,20 @@ function createRoom(options, config) {
10548
10665
  context.pausedHistory = new Deque();
10549
10666
  }
10550
10667
  }
10551
- function resumeHistory() {
10668
+ function commitPausedHistoryToUndoStack() {
10552
10669
  const frames = context.pausedHistory;
10553
10670
  context.pausedHistory = null;
10554
10671
  if (frames !== null && frames.length > 0) {
10555
10672
  _addToRealUndoStack(Array.from(frames));
10556
10673
  }
10557
10674
  }
10675
+ function resumeHistory() {
10676
+ if (context.activeBatch !== null) {
10677
+ context.activeBatch.scheduleHistoryResume = true;
10678
+ return;
10679
+ }
10680
+ commitPausedHistoryToUndoStack();
10681
+ }
10558
10682
  function withoutHistory(fn) {
10559
10683
  const undoBefore = context.undoStack.length;
10560
10684
  const redoBefore = context.redoStack.length;
@@ -10622,6 +10746,7 @@ function createRoom(options, config) {
10622
10746
  storageStatus: eventHub.storageStatus.observable,
10623
10747
  ydoc: eventHub.ydoc.observable,
10624
10748
  comments: eventHub.comments.observable,
10749
+ feeds: eventHub.feeds.observable,
10625
10750
  roomWillDestroy: eventHub.roomWillDestroy.observable
10626
10751
  };
10627
10752
  async function getThreadsSince(options2) {
@@ -10831,13 +10956,19 @@ function createRoom(options, config) {
10831
10956
  id: roomId,
10832
10957
  subscribe: makeClassicSubscribeFn(
10833
10958
  roomId,
10834
- events,
10959
+ eventHub,
10835
10960
  config.errorEventSource
10836
10961
  ),
10837
10962
  connect: () => managedSocket.connect(),
10838
10963
  reconnect: () => managedSocket.reconnect(),
10839
10964
  disconnect: () => managedSocket.disconnect(),
10840
10965
  destroy: () => {
10966
+ pendingFeedsRequests.forEach(
10967
+ (request) => request.reject(new Error("Room destroyed"))
10968
+ );
10969
+ pendingFeedMessagesRequests.forEach(
10970
+ (request) => request.reject(new Error("Room destroyed"))
10971
+ );
10841
10972
  const { roomWillDestroy, ...eventsExceptDestroy } = eventHub;
10842
10973
  for (const source of Object.values(eventsExceptDestroy)) {
10843
10974
  source.dispose();
@@ -10869,6 +11000,14 @@ function createRoom(options, config) {
10869
11000
  }
10870
11001
  },
10871
11002
  fetchYDoc,
11003
+ fetchFeeds,
11004
+ fetchFeedMessages,
11005
+ addFeed,
11006
+ updateFeed,
11007
+ deleteFeed,
11008
+ addFeedMessage,
11009
+ updateFeedMessage,
11010
+ deleteFeedMessage,
10872
11011
  getStorage,
10873
11012
  getStorageSnapshot,
10874
11013
  getStorageStatus,
@@ -11776,6 +11915,292 @@ function toPlainLson(lson) {
11776
11915
  }
11777
11916
  }
11778
11917
 
11918
+ // src/immutable.ts
11919
+ function lsonObjectToJson(obj) {
11920
+ const result = {};
11921
+ for (const key in obj) {
11922
+ const val = obj[key];
11923
+ if (val !== void 0) {
11924
+ result[key] = lsonToJson(val);
11925
+ }
11926
+ }
11927
+ return result;
11928
+ }
11929
+ function liveObjectToJson(liveObject) {
11930
+ return lsonObjectToJson(liveObject.toObject());
11931
+ }
11932
+ function liveMapToJson(map) {
11933
+ const result = {};
11934
+ for (const [key, value] of map.entries()) {
11935
+ result[key] = lsonToJson(value);
11936
+ }
11937
+ return result;
11938
+ }
11939
+ function lsonListToJson(value) {
11940
+ return value.map(lsonToJson);
11941
+ }
11942
+ function liveListToJson(value) {
11943
+ return lsonListToJson(value.toArray());
11944
+ }
11945
+ function lsonToJson(value) {
11946
+ if (value instanceof LiveObject) {
11947
+ return liveObjectToJson(value);
11948
+ } else if (value instanceof LiveList) {
11949
+ return liveListToJson(value);
11950
+ } else if (value instanceof LiveMap) {
11951
+ return liveMapToJson(value);
11952
+ } else if (value instanceof LiveRegister) {
11953
+ return value.data;
11954
+ }
11955
+ if (Array.isArray(value)) {
11956
+ return lsonListToJson(value);
11957
+ } else if (isPlainObject(value)) {
11958
+ return lsonObjectToJson(value);
11959
+ }
11960
+ return value;
11961
+ }
11962
+ function legacy_patchLiveList(liveList, prev, next) {
11963
+ let i = 0;
11964
+ let prevEnd = prev.length - 1;
11965
+ let nextEnd = next.length - 1;
11966
+ let prevNode = prev[0];
11967
+ let nextNode = next[0];
11968
+ outer: {
11969
+ while (prevNode === nextNode) {
11970
+ ++i;
11971
+ if (i > prevEnd || i > nextEnd) {
11972
+ break outer;
11973
+ }
11974
+ prevNode = prev[i];
11975
+ nextNode = next[i];
11976
+ }
11977
+ prevNode = prev[prevEnd];
11978
+ nextNode = next[nextEnd];
11979
+ while (prevNode === nextNode) {
11980
+ prevEnd--;
11981
+ nextEnd--;
11982
+ if (i > prevEnd || i > nextEnd) {
11983
+ break outer;
11984
+ }
11985
+ prevNode = prev[prevEnd];
11986
+ nextNode = next[nextEnd];
11987
+ }
11988
+ }
11989
+ if (i > prevEnd) {
11990
+ if (i <= nextEnd) {
11991
+ while (i <= nextEnd) {
11992
+ liveList.insert(deepLiveify(next[i]), i);
11993
+ i++;
11994
+ }
11995
+ }
11996
+ } else if (i > nextEnd) {
11997
+ let localI = i;
11998
+ while (localI <= prevEnd) {
11999
+ liveList.delete(i);
12000
+ localI++;
12001
+ }
12002
+ } else {
12003
+ while (i <= prevEnd && i <= nextEnd) {
12004
+ prevNode = prev[i];
12005
+ nextNode = next[i];
12006
+ const liveListNode = liveList.get(i);
12007
+ if (isLiveObject(liveListNode) && isPlainObject(prevNode) && isPlainObject(nextNode)) {
12008
+ legacy_patchLiveObject(liveListNode, prevNode, nextNode);
12009
+ } else {
12010
+ liveList.set(i, deepLiveify(nextNode));
12011
+ }
12012
+ i++;
12013
+ }
12014
+ while (i <= nextEnd) {
12015
+ liveList.insert(deepLiveify(next[i]), i);
12016
+ i++;
12017
+ }
12018
+ let localI = i;
12019
+ while (localI <= prevEnd) {
12020
+ liveList.delete(i);
12021
+ localI++;
12022
+ }
12023
+ }
12024
+ }
12025
+ function legacy_patchLiveObjectKey(liveObject, key, prev, next) {
12026
+ if (process.env.NODE_ENV !== "production") {
12027
+ const nonSerializableValue = findNonSerializableValue(next);
12028
+ if (nonSerializableValue) {
12029
+ error2(
12030
+ `New state path: '${nonSerializableValue.path}' value: '${String(
12031
+ nonSerializableValue.value
12032
+ )}' is not serializable.
12033
+ Only serializable value can be synced with Liveblocks.`
12034
+ );
12035
+ return;
12036
+ }
12037
+ }
12038
+ const value = liveObject.get(key);
12039
+ if (next === void 0) {
12040
+ liveObject.delete(key);
12041
+ } else if (value === void 0) {
12042
+ liveObject.set(key, deepLiveify(next));
12043
+ } else if (prev === next) {
12044
+ return;
12045
+ } else if (isLiveList(value) && Array.isArray(prev) && Array.isArray(next)) {
12046
+ legacy_patchLiveList(value, prev, next);
12047
+ } else if (isLiveObject(value) && isPlainObject(prev) && isPlainObject(next)) {
12048
+ legacy_patchLiveObject(value, prev, next);
12049
+ } else {
12050
+ liveObject.set(key, deepLiveify(next));
12051
+ }
12052
+ }
12053
+ function legacy_patchLiveObject(root, prev, next) {
12054
+ const updates = {};
12055
+ for (const key in next) {
12056
+ legacy_patchLiveObjectKey(root, key, prev[key], next[key]);
12057
+ }
12058
+ for (const key in prev) {
12059
+ if (next[key] === void 0) {
12060
+ root.delete(key);
12061
+ }
12062
+ }
12063
+ if (Object.keys(updates).length > 0) {
12064
+ root.update(updates);
12065
+ }
12066
+ }
12067
+ function getParentsPath(node) {
12068
+ const path = [];
12069
+ while (node.parent.type === "HasParent") {
12070
+ if (isLiveList(node.parent.node)) {
12071
+ path.push(node.parent.node._indexOfPosition(node.parent.key));
12072
+ } else {
12073
+ path.push(node.parent.key);
12074
+ }
12075
+ node = node.parent.node;
12076
+ }
12077
+ return path;
12078
+ }
12079
+ function legacy_patchImmutableObject(state, updates) {
12080
+ return updates.reduce(
12081
+ (state2, update) => legacy_patchImmutableObjectWithUpdate(state2, update),
12082
+ state
12083
+ );
12084
+ }
12085
+ function legacy_patchImmutableObjectWithUpdate(state, update) {
12086
+ const path = getParentsPath(update.node);
12087
+ return legacy_patchImmutableNode(state, path, update);
12088
+ }
12089
+ function legacy_patchImmutableNode(state, path, update) {
12090
+ const pathItem = path.pop();
12091
+ if (pathItem === void 0) {
12092
+ switch (update.type) {
12093
+ case "LiveObject": {
12094
+ if (!isJsonObject(state)) {
12095
+ throw new Error(
12096
+ "Internal: received update on LiveObject but state was not an object"
12097
+ );
12098
+ }
12099
+ const newState = Object.assign({}, state);
12100
+ for (const key in update.updates) {
12101
+ if (update.updates[key]?.type === "update") {
12102
+ const val = update.node.get(key);
12103
+ if (val !== void 0) {
12104
+ newState[key] = lsonToJson(val);
12105
+ }
12106
+ } else if (update.updates[key]?.type === "delete") {
12107
+ delete newState[key];
12108
+ }
12109
+ }
12110
+ return newState;
12111
+ }
12112
+ case "LiveList": {
12113
+ if (!Array.isArray(state)) {
12114
+ throw new Error(
12115
+ "Internal: received update on LiveList but state was not an array"
12116
+ );
12117
+ }
12118
+ let newState = state.map((x) => x);
12119
+ for (const listUpdate of update.updates) {
12120
+ if (listUpdate.type === "set") {
12121
+ newState = newState.map(
12122
+ (item, index) => index === listUpdate.index ? lsonToJson(listUpdate.item) : item
12123
+ );
12124
+ } else if (listUpdate.type === "insert") {
12125
+ if (listUpdate.index === newState.length) {
12126
+ newState.push(lsonToJson(listUpdate.item));
12127
+ } else {
12128
+ newState = [
12129
+ ...newState.slice(0, listUpdate.index),
12130
+ lsonToJson(listUpdate.item),
12131
+ ...newState.slice(listUpdate.index)
12132
+ ];
12133
+ }
12134
+ } else if (listUpdate.type === "delete") {
12135
+ newState.splice(listUpdate.index, 1);
12136
+ } else if (listUpdate.type === "move") {
12137
+ if (listUpdate.previousIndex > listUpdate.index) {
12138
+ newState = [
12139
+ ...newState.slice(0, listUpdate.index),
12140
+ lsonToJson(listUpdate.item),
12141
+ ...newState.slice(listUpdate.index, listUpdate.previousIndex),
12142
+ ...newState.slice(listUpdate.previousIndex + 1)
12143
+ ];
12144
+ } else {
12145
+ newState = [
12146
+ ...newState.slice(0, listUpdate.previousIndex),
12147
+ ...newState.slice(
12148
+ listUpdate.previousIndex + 1,
12149
+ listUpdate.index + 1
12150
+ ),
12151
+ lsonToJson(listUpdate.item),
12152
+ ...newState.slice(listUpdate.index + 1)
12153
+ ];
12154
+ }
12155
+ }
12156
+ }
12157
+ return newState;
12158
+ }
12159
+ case "LiveMap": {
12160
+ if (!isJsonObject(state)) {
12161
+ throw new Error(
12162
+ "Internal: received update on LiveMap but state was not an object"
12163
+ );
12164
+ }
12165
+ const newState = Object.assign({}, state);
12166
+ for (const key in update.updates) {
12167
+ if (update.updates[key]?.type === "update") {
12168
+ const value = update.node.get(key);
12169
+ if (value !== void 0) {
12170
+ newState[key] = lsonToJson(value);
12171
+ }
12172
+ } else if (update.updates[key]?.type === "delete") {
12173
+ delete newState[key];
12174
+ }
12175
+ }
12176
+ return newState;
12177
+ }
12178
+ }
12179
+ }
12180
+ if (Array.isArray(state)) {
12181
+ const newArray = [...state];
12182
+ newArray[pathItem] = legacy_patchImmutableNode(
12183
+ state[pathItem],
12184
+ path,
12185
+ update
12186
+ );
12187
+ return newArray;
12188
+ } else if (isJsonObject(state)) {
12189
+ const node = state[pathItem];
12190
+ if (node === void 0) {
12191
+ return state;
12192
+ } else {
12193
+ const stateAsObj = state;
12194
+ return {
12195
+ ...stateAsObj,
12196
+ [pathItem]: legacy_patchImmutableNode(node, path, update)
12197
+ };
12198
+ }
12199
+ } else {
12200
+ return state;
12201
+ }
12202
+ }
12203
+
11779
12204
  // src/lib/abortController.ts
11780
12205
  function makeAbortController(externalSignal) {
11781
12206
  const ctl = new AbortController();
@@ -11947,6 +12372,7 @@ export {
11947
12372
  DefaultMap,
11948
12373
  Deque,
11949
12374
  DerivedSignal,
12375
+ FeedRequestErrorCode,
11950
12376
  HttpError,
11951
12377
  LiveList,
11952
12378
  LiveMap,