@liveblocks/core 3.16.0-flow1 → 3.16.0-flow3

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-flow1";
9
+ var PKG_VERSION = "3.16.0-flow3";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -6302,7 +6302,6 @@ var AbstractCrdt = class {
6302
6302
  }
6303
6303
  /**
6304
6304
  * @internal
6305
- *
6306
6305
  * Return an snapshot of this Live tree for use in DevTools.
6307
6306
  */
6308
6307
  toTreeNode(key) {
@@ -6312,6 +6311,14 @@ var AbstractCrdt = class {
6312
6311
  }
6313
6312
  return this.#cachedTreeNode;
6314
6313
  }
6314
+ /**
6315
+ * @private
6316
+ * Returns true if the cached immutable snapshot exists and is
6317
+ * reference-equal to the given value. Does not trigger a recompute.
6318
+ */
6319
+ immutableIs(value) {
6320
+ return this.#cachedImmutable !== void 0 && this.#cachedImmutable === value;
6321
+ }
6315
6322
  /**
6316
6323
  * Return an immutable snapshot of this Live node and its children.
6317
6324
  */
@@ -7859,272 +7866,673 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
7859
7866
  }
7860
7867
  };
7861
7868
 
7862
- // src/crdts/LiveObject.ts
7863
- var MAX_LIVE_OBJECT_SIZE = 128 * 1024;
7864
- var LiveObject = class _LiveObject extends AbstractCrdt {
7865
- #map;
7866
- /**
7867
- * Tracks unacknowledged local changes per property to preserve optimistic
7868
- * updates. Maps property keys to their pending operation IDs.
7869
- *
7870
- * INVARIANT: Only locally-generated opIds are ever stored here. Remote opIds
7871
- * are only compared against (to detect ACKs), never stored.
7872
- *
7873
- * When a local change is made, the opId is stored here. When a remote op
7874
- * arrives for the same key:
7875
- * - If no entry exists → apply remote op
7876
- * - If opId matches → it's an ACK, clear the entry
7877
- * - If opId differs → ignore remote op to preserve optimistic update
7878
- */
7879
- #unackedOpsByKey;
7880
- /**
7881
- * Enable or disable detection of too large LiveObjects.
7882
- * When enabled, throws an error if LiveObject static data exceeds 128KB, which
7883
- * is the maximum value the server will be able to accept.
7884
- * By default, this behavior is disabled to avoid the runtime performance
7885
- * overhead on every LiveObject.set() or LiveObject.update() call.
7886
- *
7887
- * @experimental
7888
- */
7889
- static detectLargeObjects = false;
7890
- static #buildRootAndParentToChildren(nodes) {
7891
- const parentToChildren = /* @__PURE__ */ new Map();
7892
- let root = null;
7893
- for (const node of nodes) {
7894
- if (isRootStorageNode(node)) {
7895
- root = node[1];
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
+ }
7924
+ function deepLiveify(value, config) {
7925
+ if (Array.isArray(value)) {
7926
+ return new LiveList(value.map((v) => deepLiveify(v, config)));
7927
+ } else if (isPlainObject(value)) {
7928
+ const init = {};
7929
+ const locals = {};
7930
+ for (const key in value) {
7931
+ const val = value[key];
7932
+ if (val === void 0) {
7933
+ continue;
7934
+ }
7935
+ const subConfig = isPlainObject(config) ? config[key] : config;
7936
+ if (subConfig === false) {
7937
+ locals[key] = val;
7938
+ } else if (subConfig === "atomic") {
7939
+ init[key] = val;
7896
7940
  } else {
7897
- const crdt = node[1];
7898
- const children = parentToChildren.get(crdt.parentId);
7899
- if (children !== void 0) {
7900
- children.push(node);
7901
- } else {
7902
- parentToChildren.set(crdt.parentId, [node]);
7903
- }
7941
+ init[key] = deepLiveify(val, subConfig);
7904
7942
  }
7905
7943
  }
7906
- if (root === null) {
7907
- throw new Error("Root can't be null");
7944
+ const lo = new LiveObject(init);
7945
+ for (const key in locals) {
7946
+ lo.setLocal(key, locals[key]);
7908
7947
  }
7909
- return [root, parentToChildren];
7948
+ return lo;
7949
+ } else {
7950
+ return value;
7910
7951
  }
7911
- /** @private Do not use this API directly */
7912
- static _fromItems(nodes, pool) {
7913
- const [root, parentToChildren] = _LiveObject.#buildRootAndParentToChildren(nodes);
7914
- return _LiveObject._deserialize(
7915
- ["root", root],
7916
- parentToChildren,
7917
- pool
7918
- );
7952
+ }
7953
+ function reconcile(live, json, config) {
7954
+ if (isLiveObject(live) && isPlainObject(json)) {
7955
+ return reconcileLiveObject(live, json, config);
7956
+ } else if (isLiveList(live) && Array.isArray(json)) {
7957
+ return reconcileLiveList(live, json, config);
7958
+ } else if (isLiveMap(live) && isPlainObject(json)) {
7959
+ return reconcileLiveMap(live, config);
7960
+ } else {
7961
+ return deepLiveify(json, config);
7919
7962
  }
7920
- constructor(obj = {}) {
7921
- super();
7922
- this.#unackedOpsByKey = /* @__PURE__ */ new Map();
7923
- const o = compactObject(obj);
7924
- for (const key of Object.keys(o)) {
7925
- const value = o[key];
7926
- if (isLiveNode(value)) {
7927
- value._setParentLink(this, key);
7963
+ }
7964
+ function reconcileLiveMap(_liveMap, _config) {
7965
+ throw new Error("Reconciling a LiveMap is not supported yet");
7966
+ }
7967
+ function reconcileLiveObject(liveObj, jsonObj, config) {
7968
+ const currentKeys = liveObj.keys();
7969
+ for (const key in jsonObj) {
7970
+ currentKeys.delete(key);
7971
+ const newVal = jsonObj[key];
7972
+ if (newVal === void 0) {
7973
+ liveObj.delete(key);
7974
+ continue;
7975
+ }
7976
+ const subConfig = isPlainObject(config) ? config[key] : config;
7977
+ if (subConfig === false) {
7978
+ liveObj.setLocal(key, newVal);
7979
+ } else if (subConfig === "atomic") {
7980
+ const curVal = liveObj.get(key);
7981
+ if (curVal !== newVal) {
7982
+ liveObj.set(key, newVal);
7983
+ }
7984
+ } else {
7985
+ const curVal = liveObj.get(key);
7986
+ if (curVal === void 0) {
7987
+ liveObj.set(key, deepLiveify(newVal, subConfig));
7988
+ } else if (isLiveStructure(curVal)) {
7989
+ const next = reconcile(curVal, newVal, subConfig);
7990
+ if (next !== curVal) {
7991
+ liveObj.set(key, next);
7992
+ }
7993
+ } else if (curVal !== newVal) {
7994
+ liveObj.set(key, deepLiveify(newVal, subConfig));
7928
7995
  }
7929
7996
  }
7930
- this.#map = new Map(Object.entries(o));
7931
7997
  }
7932
- /** @internal */
7933
- _toOps(parentId, parentKey) {
7934
- if (this._id === void 0) {
7935
- throw new Error("Cannot serialize item is not attached");
7936
- }
7937
- const ops = [];
7938
- const op = {
7939
- type: OpCode.CREATE_OBJECT,
7940
- id: this._id,
7941
- parentId,
7942
- parentKey,
7943
- data: {}
7944
- };
7945
- ops.push(op);
7946
- for (const [key, value] of this.#map) {
7947
- if (isLiveNode(value)) {
7948
- for (const childOp of value._toOps(this._id, key)) {
7949
- ops.push(childOp);
7950
- }
7951
- } else {
7952
- op.data[key] = value;
7998
+ for (const key of currentKeys) {
7999
+ liveObj.delete(key);
8000
+ }
8001
+ return liveObj;
8002
+ }
8003
+ function reconcileLiveList(liveList, jsonArr, config) {
8004
+ const curLen = liveList.length;
8005
+ const newLen = jsonArr.length;
8006
+ for (let i = 0; i < Math.min(curLen, newLen); i++) {
8007
+ const curVal = liveList.get(i);
8008
+ const newVal = jsonArr[i];
8009
+ if (isLiveStructure(curVal)) {
8010
+ const next = reconcile(curVal, newVal, config);
8011
+ if (next !== curVal) {
8012
+ liveList.set(i, next);
7953
8013
  }
8014
+ } else if (curVal !== newVal) {
8015
+ liveList.set(i, deepLiveify(newVal, config));
7954
8016
  }
7955
- return ops;
7956
8017
  }
7957
- /** @internal */
7958
- static _deserialize([id, item], parentToChildren, pool) {
7959
- const liveObj = new _LiveObject(item.data);
7960
- liveObj._attach(id, pool);
7961
- return this._deserializeChildren(liveObj, parentToChildren, pool);
8018
+ for (let i = curLen; i < newLen; i++) {
8019
+ liveList.push(deepLiveify(jsonArr[i], config));
7962
8020
  }
7963
- /** @internal */
7964
- static _deserializeChildren(liveObj, parentToChildren, pool) {
7965
- const children = parentToChildren.get(nn(liveObj._id));
7966
- if (children === void 0) {
7967
- return liveObj;
8021
+ for (let i = curLen - 1; i >= newLen; i--) {
8022
+ liveList.delete(i);
8023
+ }
8024
+ return liveList;
8025
+ }
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;
8037
+ }
8038
+ prevNode = prev[i];
8039
+ nextNode = next[i];
7968
8040
  }
7969
- for (const node of children) {
7970
- const child = deserializeToLson(node, parentToChildren, pool);
7971
- const crdt = node[1];
7972
- if (isLiveStructure(child)) {
7973
- child._setParentLink(liveObj, crdt.parentKey);
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;
7974
8048
  }
7975
- liveObj.#map.set(crdt.parentKey, child);
7976
- liveObj.invalidate();
8049
+ prevNode = prev[prevEnd];
8050
+ nextNode = next[nextEnd];
7977
8051
  }
7978
- return liveObj;
7979
8052
  }
7980
- /** @internal */
7981
- _attach(id, pool) {
7982
- super._attach(id, pool);
7983
- for (const [_key, value] of this.#map) {
7984
- if (isLiveNode(value)) {
7985
- value._attach(pool.generateId(), pool);
8053
+ if (i > prevEnd) {
8054
+ if (i <= nextEnd) {
8055
+ while (i <= nextEnd) {
8056
+ liveList.insert(deepLiveify(next[i]), i);
8057
+ i++;
7986
8058
  }
7987
8059
  }
7988
- }
7989
- /** @internal */
7990
- _attachChild(op, source) {
7991
- if (this._pool === void 0) {
7992
- throw new Error("Can't attach child if managed pool is not present");
8060
+ } else if (i > nextEnd) {
8061
+ let localI = i;
8062
+ while (localI <= prevEnd) {
8063
+ liveList.delete(i);
8064
+ localI++;
7993
8065
  }
7994
- const { id, opId, parentKey: key } = op;
7995
- const child = creationOpToLson(op);
7996
- if (this._pool.getNode(id) !== void 0) {
7997
- if (this.#unackedOpsByKey.get(key) === opId) {
7998
- this.#unackedOpsByKey.delete(key);
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);
8073
+ } else {
8074
+ liveList.set(i, deepLiveify(nextNode));
7999
8075
  }
8000
- return { modified: false };
8076
+ i++;
8001
8077
  }
8002
- if (source === 0 /* LOCAL */) {
8003
- this.#unackedOpsByKey.set(key, nn(opId));
8004
- } else if (this.#unackedOpsByKey.get(key) === void 0) {
8005
- } else if (this.#unackedOpsByKey.get(key) === opId) {
8006
- this.#unackedOpsByKey.delete(key);
8007
- return { modified: false };
8008
- } else {
8009
- return { modified: false };
8078
+ while (i <= nextEnd) {
8079
+ liveList.insert(deepLiveify(next[i]), i);
8080
+ i++;
8010
8081
  }
8011
- const thisId = nn(this._id);
8012
- const previousValue = this.#map.get(key);
8013
- let reverse;
8014
- if (isLiveNode(previousValue)) {
8015
- reverse = previousValue._toOps(thisId, key);
8016
- previousValue._detach();
8017
- } else if (previousValue === void 0) {
8018
- reverse = [{ type: OpCode.DELETE_OBJECT_KEY, id: thisId, key }];
8019
- } else {
8020
- reverse = [
8021
- {
8022
- type: OpCode.UPDATE_OBJECT,
8023
- id: thisId,
8024
- data: { [key]: previousValue }
8025
- }
8026
- ];
8027
- }
8028
- this.#map.set(key, child);
8029
- this.invalidate();
8030
- if (isLiveStructure(child)) {
8031
- child._setParentLink(this, key);
8032
- child._attach(id, this._pool);
8082
+ let localI = i;
8083
+ while (localI <= prevEnd) {
8084
+ liveList.delete(i);
8085
+ localI++;
8033
8086
  }
8034
- return {
8035
- reverse,
8036
- modified: {
8037
- node: this,
8038
- type: "LiveObject",
8039
- updates: { [key]: { type: "update" } }
8040
- }
8041
- };
8042
8087
  }
8043
- /** @internal */
8044
- _detachChild(child) {
8045
- if (child) {
8046
- const id = nn(this._id);
8047
- const parentKey = nn(child._parentKey);
8048
- const reverse = child._toOps(id, parentKey);
8049
- for (const [key, value] of this.#map) {
8050
- if (value === child) {
8051
- this.#map.delete(key);
8052
- this.invalidate();
8053
- }
8054
- }
8055
- child._detach();
8056
- const storageUpdate = {
8057
- node: this,
8058
- type: "LiveObject",
8059
- updates: {
8060
- [parentKey]: { type: "delete" }
8061
- }
8062
- };
8063
- return { modified: storageUpdate, reverse };
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;
8064
8100
  }
8065
- return { modified: false };
8066
8101
  }
8067
- /** @internal */
8068
- _detach() {
8069
- super._detach();
8070
- for (const value of this.#map.values()) {
8071
- if (isLiveNode(value)) {
8072
- value._detach();
8073
- }
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);
8074
8125
  }
8075
8126
  }
8076
- /** @internal */
8077
- _apply(op, isLocal) {
8078
- if (op.type === OpCode.UPDATE_OBJECT) {
8079
- return this.#applyUpdate(op, isLocal);
8080
- } else if (op.type === OpCode.DELETE_OBJECT_KEY) {
8081
- return this.#applyDeleteObjectKey(op, isLocal);
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));
8136
+ } else {
8137
+ path.push(node.parent.key);
8082
8138
  }
8083
- return super._apply(op, isLocal);
8139
+ node = node.parent.node;
8084
8140
  }
8085
- /** @internal */
8086
- _serialize() {
8087
- const data = {};
8088
- for (const [key, value] of this.#map) {
8089
- if (!isLiveNode(value)) {
8090
- data[key] = value;
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
+ }
8173
+ }
8174
+ return newState;
8175
+ }
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
+ }
8220
+ }
8221
+ return newState;
8222
+ }
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
+ }
8239
+ }
8240
+ return newState;
8091
8241
  }
8092
8242
  }
8093
- if (this.parent.type === "HasParent" && this.parent.node._id) {
8094
- return {
8095
- type: CrdtType.OBJECT,
8096
- parentId: this.parent.node._id,
8097
- parentKey: this.parent.key,
8098
- data
8099
- };
8243
+ }
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;
8100
8256
  } else {
8257
+ const stateAsObj = state;
8101
8258
  return {
8102
- type: CrdtType.OBJECT,
8103
- data
8259
+ ...stateAsObj,
8260
+ [pathItem]: legacy_patchImmutableNode(node, path, update)
8104
8261
  };
8105
8262
  }
8263
+ } else {
8264
+ return state;
8106
8265
  }
8107
- #applyUpdate(op, isLocal) {
8108
- let isModified = false;
8109
- const id = nn(this._id);
8110
- const reverse = [];
8111
- const reverseUpdate = {
8112
- type: OpCode.UPDATE_OBJECT,
8113
- id,
8114
- data: {}
8115
- };
8116
- for (const key in op.data) {
8117
- const oldValue = this.#map.get(key);
8118
- if (isLiveNode(oldValue)) {
8119
- for (const childOp of oldValue._toOps(id, key)) {
8120
- reverse.push(childOp);
8121
- }
8122
- oldValue._detach();
8123
- } else if (oldValue !== void 0) {
8124
- reverseUpdate.data[key] = oldValue;
8125
- } else if (oldValue === void 0) {
8126
- reverse.push({ type: OpCode.DELETE_OBJECT_KEY, id, key });
8127
- }
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
8512
+ };
8513
+ }
8514
+ }
8515
+ #applyUpdate(op, isLocal) {
8516
+ let isModified = false;
8517
+ const id = nn(this._id);
8518
+ const reverse = [];
8519
+ const reverseUpdate = {
8520
+ type: OpCode.UPDATE_OBJECT,
8521
+ id,
8522
+ data: {}
8523
+ };
8524
+ for (const key in op.data) {
8525
+ const oldValue = this.#synced.get(key);
8526
+ if (isLiveNode(oldValue)) {
8527
+ for (const childOp of oldValue._toOps(id, key)) {
8528
+ reverse.push(childOp);
8529
+ }
8530
+ oldValue._detach();
8531
+ } else if (oldValue !== void 0) {
8532
+ reverseUpdate.data[key] = oldValue;
8533
+ } else if (oldValue === void 0) {
8534
+ reverse.push({ type: OpCode.DELETE_OBJECT_KEY, id, key });
8535
+ }
8128
8536
  }
8129
8537
  const updateDelta = {};
8130
8538
  for (const key in op.data) {
@@ -8142,13 +8550,14 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8142
8550
  } else {
8143
8551
  continue;
8144
8552
  }
8145
- const oldValue = this.#map.get(key);
8553
+ const oldValue = this.#synced.get(key);
8146
8554
  if (isLiveNode(oldValue)) {
8147
8555
  oldValue._detach();
8148
8556
  }
8149
8557
  isModified = true;
8150
8558
  updateDelta[key] = { type: "update" };
8151
- this.#map.set(key, value);
8559
+ this.#local.delete(key);
8560
+ this.#synced.set(key, value);
8152
8561
  this.invalidate();
8153
8562
  }
8154
8563
  if (Object.keys(reverseUpdate.data).length !== 0) {
@@ -8165,7 +8574,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8165
8574
  }
8166
8575
  #applyDeleteObjectKey(op, isLocal) {
8167
8576
  const key = op.key;
8168
- const oldValue = this.#map.get(key);
8577
+ const oldValue = this.#synced.get(key);
8169
8578
  if (oldValue === void 0) {
8170
8579
  return { modified: false };
8171
8580
  }
@@ -8186,7 +8595,8 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8186
8595
  }
8187
8596
  ];
8188
8597
  }
8189
- this.#map.delete(key);
8598
+ this.#local.delete(key);
8599
+ this.#synced.delete(key);
8190
8600
  this.invalidate();
8191
8601
  return {
8192
8602
  modified: {
@@ -8199,11 +8609,23 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8199
8609
  reverse
8200
8610
  };
8201
8611
  }
8612
+ /** @private */
8613
+ keys() {
8614
+ const result = new Set(this.#synced.keys());
8615
+ for (const key of this.#local.keys()) {
8616
+ result.add(key);
8617
+ }
8618
+ return result;
8619
+ }
8202
8620
  /**
8203
8621
  * Transform the LiveObject into a javascript object
8204
8622
  */
8205
8623
  toObject() {
8206
- return Object.fromEntries(this.#map);
8624
+ const result = Object.fromEntries(this.#synced);
8625
+ for (const [key, value] of this.#local) {
8626
+ result[key] = value;
8627
+ }
8628
+ return result;
8207
8629
  }
8208
8630
  /**
8209
8631
  * Adds or updates a property with a specified key and a value.
@@ -8211,49 +8633,109 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8211
8633
  * @param value The value of the property to add
8212
8634
  */
8213
8635
  set(key, value) {
8214
- this._pool?.assertStorageIsWritable();
8215
8636
  this.update({ [key]: value });
8216
8637
  }
8638
+ /**
8639
+ * @experimental
8640
+ *
8641
+ * Sets a local-only property that is not synchronized over the wire.
8642
+ * The value will be visible via get(), toObject(), and toImmutable() on
8643
+ * this client only. Other clients and the server will see `undefined`
8644
+ * for this key.
8645
+ *
8646
+ * Caveat: this method will not add changes to the undo/redo stack.
8647
+ */
8648
+ setLocal(key, value) {
8649
+ this._pool?.assertStorageIsWritable();
8650
+ const deleteResult = this.#prepareDelete(key);
8651
+ this.#local.set(key, value);
8652
+ this.invalidate();
8653
+ if (this._pool !== void 0 && this._id !== void 0) {
8654
+ const ops = deleteResult?.[0] ?? [];
8655
+ const reverse = deleteResult?.[1] ?? [];
8656
+ const storageUpdates = deleteResult?.[2] ?? /* @__PURE__ */ new Map();
8657
+ const existing = storageUpdates.get(this._id);
8658
+ storageUpdates.set(this._id, {
8659
+ node: this,
8660
+ type: "LiveObject",
8661
+ updates: {
8662
+ ...existing?.updates,
8663
+ [key]: { type: "update" }
8664
+ }
8665
+ });
8666
+ this._pool.dispatch(ops, reverse, storageUpdates);
8667
+ }
8668
+ }
8217
8669
  /**
8218
8670
  * Returns a specified property from the LiveObject.
8219
8671
  * @param key The key of the property to get
8220
8672
  */
8221
8673
  get(key) {
8222
- return this.#map.get(key);
8674
+ return this.#local.has(key) ? this.#local.get(key) : this.#synced.get(key);
8223
8675
  }
8224
8676
  /**
8225
- * Deletes a key from the LiveObject
8226
- * @param key The key of the property to delete
8677
+ * Removes a synced key, returning the ops, reverse ops, and storage updates
8678
+ * needed to notify the pool. Returns null if the key doesn't exist in
8679
+ * #synced or pool/id are unavailable. Does NOT dispatch.
8227
8680
  */
8228
- delete(key) {
8681
+ #prepareDelete(key) {
8229
8682
  this._pool?.assertStorageIsWritable();
8230
- const keyAsString = key;
8231
- const oldValue = this.#map.get(keyAsString);
8683
+ const k = key;
8684
+ if (this.#local.has(k) && !this.#synced.has(k)) {
8685
+ const oldValue2 = this.#local.get(k);
8686
+ this.#local.delete(k);
8687
+ this.invalidate();
8688
+ if (this._pool !== void 0 && this._id !== void 0) {
8689
+ const storageUpdates2 = /* @__PURE__ */ new Map();
8690
+ storageUpdates2.set(this._id, {
8691
+ node: this,
8692
+ type: "LiveObject",
8693
+ updates: {
8694
+ [k]: {
8695
+ type: "delete",
8696
+ deletedItem: oldValue2
8697
+ }
8698
+ }
8699
+ });
8700
+ return [[], [], storageUpdates2];
8701
+ }
8702
+ return null;
8703
+ }
8704
+ this.#local.delete(k);
8705
+ const oldValue = this.#synced.get(k);
8232
8706
  if (oldValue === void 0) {
8233
- return;
8707
+ return null;
8234
8708
  }
8235
8709
  if (this._pool === void 0 || this._id === void 0) {
8236
8710
  if (isLiveNode(oldValue)) {
8237
8711
  oldValue._detach();
8238
8712
  }
8239
- this.#map.delete(keyAsString);
8713
+ this.#synced.delete(k);
8240
8714
  this.invalidate();
8241
- return;
8715
+ return null;
8242
8716
  }
8717
+ const ops = [
8718
+ {
8719
+ type: OpCode.DELETE_OBJECT_KEY,
8720
+ key: k,
8721
+ id: this._id,
8722
+ opId: this._pool.generateOpId()
8723
+ }
8724
+ ];
8243
8725
  let reverse;
8244
8726
  if (isLiveNode(oldValue)) {
8245
8727
  oldValue._detach();
8246
- reverse = oldValue._toOps(this._id, keyAsString);
8728
+ reverse = oldValue._toOps(this._id, k);
8247
8729
  } else {
8248
8730
  reverse = [
8249
8731
  {
8250
8732
  type: OpCode.UPDATE_OBJECT,
8251
- data: { [keyAsString]: oldValue },
8733
+ data: { [k]: oldValue },
8252
8734
  id: this._id
8253
8735
  }
8254
8736
  ];
8255
8737
  }
8256
- this.#map.delete(keyAsString);
8738
+ this.#synced.delete(k);
8257
8739
  this.invalidate();
8258
8740
  const storageUpdates = /* @__PURE__ */ new Map();
8259
8741
  storageUpdates.set(this._id, {
@@ -8263,18 +8745,18 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8263
8745
  [key]: { type: "delete", deletedItem: oldValue }
8264
8746
  }
8265
8747
  });
8266
- this._pool.dispatch(
8267
- [
8268
- {
8269
- type: OpCode.DELETE_OBJECT_KEY,
8270
- key: keyAsString,
8271
- id: this._id,
8272
- opId: this._pool.generateOpId()
8273
- }
8274
- ],
8275
- reverse,
8276
- storageUpdates
8277
- );
8748
+ return [ops, reverse, storageUpdates];
8749
+ }
8750
+ /**
8751
+ * Deletes a key from the LiveObject
8752
+ * @param key The key of the property to delete
8753
+ */
8754
+ delete(key) {
8755
+ const result = this.#prepareDelete(key);
8756
+ if (result) {
8757
+ const [ops, reverse, storageUpdates] = result;
8758
+ this._pool?.dispatch(ops, reverse, storageUpdates);
8759
+ }
8278
8760
  }
8279
8761
  /**
8280
8762
  * Adds or updates multiple properties at once with an object.
@@ -8284,7 +8766,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8284
8766
  this._pool?.assertStorageIsWritable();
8285
8767
  if (_LiveObject.detectLargeObjects) {
8286
8768
  const data = {};
8287
- for (const [key, value] of this.#map) {
8769
+ for (const [key, value] of this.#synced) {
8288
8770
  if (!isLiveNode(value)) {
8289
8771
  data[key] = value;
8290
8772
  }
@@ -8313,14 +8795,15 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8313
8795
  if (newValue === void 0) {
8314
8796
  continue;
8315
8797
  }
8316
- const oldValue = this.#map.get(key);
8798
+ const oldValue = this.#synced.get(key);
8317
8799
  if (isLiveNode(oldValue)) {
8318
8800
  oldValue._detach();
8319
8801
  }
8320
8802
  if (isLiveNode(newValue)) {
8321
8803
  newValue._setParentLink(this, key);
8322
8804
  }
8323
- this.#map.set(key, newValue);
8805
+ this.#local.delete(key);
8806
+ this.#synced.set(key, newValue);
8324
8807
  this.invalidate();
8325
8808
  }
8326
8809
  return;
@@ -8340,7 +8823,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8340
8823
  if (newValue === void 0) {
8341
8824
  continue;
8342
8825
  }
8343
- const oldValue = this.#map.get(key);
8826
+ const oldValue = this.#synced.get(key);
8344
8827
  if (isLiveNode(oldValue)) {
8345
8828
  for (const childOp of oldValue._toOps(this._id, key)) {
8346
8829
  reverseOps.push(childOp);
@@ -8372,7 +8855,8 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8372
8855
  updatedProps[key] = newValue;
8373
8856
  this.#unackedOpsByKey.set(key, opId);
8374
8857
  }
8375
- this.#map.set(key, newValue);
8858
+ this.#local.delete(key);
8859
+ this.#synced.set(key, newValue);
8376
8860
  this.invalidate();
8377
8861
  updateDelta[key] = { type: "update" };
8378
8862
  }
@@ -8395,6 +8879,34 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8395
8879
  });
8396
8880
  this._pool.dispatch(ops, reverseOps, storageUpdates);
8397
8881
  }
8882
+ /**
8883
+ * Creates a new LiveObject from a plain JSON object, recursively converting
8884
+ * nested objects to LiveObjects and arrays to LiveLists. An optional
8885
+ * SyncConfig controls per-key behavior (local-only, atomic, or deep).
8886
+ *
8887
+ * @private
8888
+ */
8889
+ static from(obj, config) {
8890
+ if (!isPlainObject(obj)) throw new Error("Expected a JSON object");
8891
+ const liveObj = new _LiveObject({});
8892
+ liveObj.reconcile(obj, config);
8893
+ return liveObj;
8894
+ }
8895
+ /**
8896
+ * Reconciles a LiveObject tree to match the given JSON object and sync
8897
+ * config. Only mutates keys that actually changed. Recursively reconciles
8898
+ * the entire Live tree below it.
8899
+ *
8900
+ * @private
8901
+ */
8902
+ reconcile(jsonObj, config) {
8903
+ if (this.immutableIs(jsonObj)) return;
8904
+ if (!isPlainObject(jsonObj))
8905
+ throw new Error(
8906
+ "Reconciling the document root expects a plain object value"
8907
+ );
8908
+ reconcileLiveObject(this, jsonObj, config);
8909
+ }
8398
8910
  toImmutable() {
8399
8911
  return super.toImmutable();
8400
8912
  }
@@ -8409,7 +8921,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8409
8921
  type: "LiveObject",
8410
8922
  id: nodeId,
8411
8923
  key,
8412
- payload: Array.from(this.#map.entries()).map(
8924
+ payload: Array.from(this.#synced.entries()).map(
8413
8925
  ([key2, value]) => isLiveNode(value) ? value.toTreeNode(key2) : { type: "Json", id: `${nodeId}:${key2}`, key: key2, payload: value }
8414
8926
  )
8415
8927
  };
@@ -8417,20 +8929,27 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8417
8929
  /** @internal */
8418
8930
  _toImmutable() {
8419
8931
  const result = {};
8420
- for (const [key, val] of this.#map) {
8932
+ for (const [key, val] of this.#synced) {
8421
8933
  result[key] = isLiveStructure(val) ? val.toImmutable() : val;
8422
8934
  }
8935
+ for (const [key, val] of this.#local) {
8936
+ result[key] = val;
8937
+ }
8423
8938
  return process.env.NODE_ENV === "production" ? result : Object.freeze(result);
8424
8939
  }
8425
8940
  clone() {
8426
- return new _LiveObject(
8941
+ const cloned = new _LiveObject(
8427
8942
  Object.fromEntries(
8428
- Array.from(this.#map).map(([key, value]) => [
8943
+ Array.from(this.#synced).map(([key, value]) => [
8429
8944
  key,
8430
8945
  isLiveStructure(value) ? value.clone() : deepClone(value)
8431
8946
  ])
8432
8947
  )
8433
8948
  );
8949
+ for (const [key, value] of this.#local) {
8950
+ cloned.#local.set(key, deepClone(value));
8951
+ }
8952
+ return cloned;
8434
8953
  }
8435
8954
  };
8436
8955
 
@@ -8730,17 +9249,6 @@ var Deque = class {
8730
9249
  }
8731
9250
  };
8732
9251
 
8733
- // src/lib/Json.ts
8734
- function isJsonScalar(data) {
8735
- return data === null || typeof data === "string" || typeof data === "number" || typeof data === "boolean";
8736
- }
8737
- function isJsonArray(data) {
8738
- return Array.isArray(data);
8739
- }
8740
- function isJsonObject(data) {
8741
- return !isJsonScalar(data) && !isJsonArray(data);
8742
- }
8743
-
8744
9252
  // src/lib/stopwatch.ts
8745
9253
  function makeStopWatch() {
8746
9254
  let startTime = 0;
@@ -9226,9 +9734,13 @@ function createRoom(options, config) {
9226
9734
  }
9227
9735
  context.activeBatch.reverseOps.pushLeft(reverse);
9228
9736
  } else {
9229
- addToUndoStack(reverse);
9230
- context.redoStack.length = 0;
9231
- dispatchOps(ops);
9737
+ if (reverse.length > 0) {
9738
+ addToUndoStack(reverse);
9739
+ }
9740
+ if (ops.length > 0) {
9741
+ context.redoStack.length = 0;
9742
+ dispatchOps(ops);
9743
+ }
9232
9744
  notify({ storageUpdates });
9233
9745
  }
9234
9746
  }
@@ -9337,19 +9849,20 @@ function createRoom(options, config) {
9337
9849
  );
9338
9850
  }
9339
9851
  const canWrite = self.get()?.canWrite ?? true;
9340
- const stackSizeBefore = context.undoStack.length;
9341
- for (const key in context.initialStorage) {
9342
- if (context.root.get(key) === void 0) {
9343
- if (canWrite) {
9344
- context.root.set(key, cloneLson(context.initialStorage[key]));
9345
- } else {
9346
- warn(
9347
- `Attempted to populate missing storage key '${key}', but current user has no write access`
9348
- );
9852
+ const root = context.root;
9853
+ withoutHistory(() => {
9854
+ for (const key in context.initialStorage) {
9855
+ if (root.get(key) === void 0) {
9856
+ if (canWrite) {
9857
+ root.set(key, cloneLson(context.initialStorage[key]));
9858
+ } else {
9859
+ warn(
9860
+ `Attempted to populate missing storage key '${key}', but current user has no write access`
9861
+ );
9862
+ }
9349
9863
  }
9350
9864
  }
9351
- }
9352
- context.undoStack.length = stackSizeBefore;
9865
+ });
9353
9866
  }
9354
9867
  function _addToRealUndoStack(frames) {
9355
9868
  if (context.undoStack.length >= 50) {
@@ -10042,6 +10555,16 @@ function createRoom(options, config) {
10042
10555
  _addToRealUndoStack(Array.from(frames));
10043
10556
  }
10044
10557
  }
10558
+ function withoutHistory(fn) {
10559
+ const undoBefore = context.undoStack.length;
10560
+ const redoBefore = context.redoStack.length;
10561
+ try {
10562
+ return fn();
10563
+ } finally {
10564
+ context.undoStack.length = undoBefore;
10565
+ context.redoStack.length = redoBefore;
10566
+ }
10567
+ }
10045
10568
  const syncSourceForStorage = config.createSyncSource();
10046
10569
  function getStorageStatus() {
10047
10570
  if (context.root === void 0) {
@@ -10340,7 +10863,10 @@ function createRoom(options, config) {
10340
10863
  canRedo,
10341
10864
  clear,
10342
10865
  pause: pauseHistory,
10343
- resume: resumeHistory
10866
+ resume: resumeHistory,
10867
+ [kInternal]: {
10868
+ withoutHistory
10869
+ }
10344
10870
  },
10345
10871
  fetchYDoc,
10346
10872
  getStorage,
@@ -11250,309 +11776,6 @@ function toPlainLson(lson) {
11250
11776
  }
11251
11777
  }
11252
11778
 
11253
- // src/immutable.ts
11254
- function lsonObjectToJson(obj) {
11255
- const result = {};
11256
- for (const key in obj) {
11257
- const val = obj[key];
11258
- if (val !== void 0) {
11259
- result[key] = lsonToJson(val);
11260
- }
11261
- }
11262
- return result;
11263
- }
11264
- function liveObjectToJson(liveObject) {
11265
- return lsonObjectToJson(liveObject.toObject());
11266
- }
11267
- function liveMapToJson(map) {
11268
- const result = {};
11269
- for (const [key, value] of map.entries()) {
11270
- result[key] = lsonToJson(value);
11271
- }
11272
- return result;
11273
- }
11274
- function lsonListToJson(value) {
11275
- return value.map(lsonToJson);
11276
- }
11277
- function liveListToJson(value) {
11278
- return lsonListToJson(value.toArray());
11279
- }
11280
- function lsonToJson(value) {
11281
- if (value instanceof LiveObject) {
11282
- return liveObjectToJson(value);
11283
- } else if (value instanceof LiveList) {
11284
- return liveListToJson(value);
11285
- } else if (value instanceof LiveMap) {
11286
- return liveMapToJson(value);
11287
- } else if (value instanceof LiveRegister) {
11288
- return value.data;
11289
- }
11290
- if (Array.isArray(value)) {
11291
- return lsonListToJson(value);
11292
- } else if (isPlainObject(value)) {
11293
- return lsonObjectToJson(value);
11294
- }
11295
- return value;
11296
- }
11297
- function deepLiveify(value) {
11298
- if (Array.isArray(value)) {
11299
- return new LiveList(value.map(deepLiveify));
11300
- } else if (isPlainObject(value)) {
11301
- const init = {};
11302
- for (const key in value) {
11303
- const val = value[key];
11304
- if (val === void 0) {
11305
- continue;
11306
- }
11307
- init[key] = deepLiveify(val);
11308
- }
11309
- return new LiveObject(init);
11310
- } else {
11311
- return value;
11312
- }
11313
- }
11314
- function patchLiveList(liveList, prev, next) {
11315
- let i = 0;
11316
- let prevEnd = prev.length - 1;
11317
- let nextEnd = next.length - 1;
11318
- let prevNode = prev[0];
11319
- let nextNode = next[0];
11320
- outer: {
11321
- while (prevNode === nextNode) {
11322
- ++i;
11323
- if (i > prevEnd || i > nextEnd) {
11324
- break outer;
11325
- }
11326
- prevNode = prev[i];
11327
- nextNode = next[i];
11328
- }
11329
- prevNode = prev[prevEnd];
11330
- nextNode = next[nextEnd];
11331
- while (prevNode === nextNode) {
11332
- prevEnd--;
11333
- nextEnd--;
11334
- if (i > prevEnd || i > nextEnd) {
11335
- break outer;
11336
- }
11337
- prevNode = prev[prevEnd];
11338
- nextNode = next[nextEnd];
11339
- }
11340
- }
11341
- if (i > prevEnd) {
11342
- if (i <= nextEnd) {
11343
- while (i <= nextEnd) {
11344
- liveList.insert(deepLiveify(next[i]), i);
11345
- i++;
11346
- }
11347
- }
11348
- } else if (i > nextEnd) {
11349
- let localI = i;
11350
- while (localI <= prevEnd) {
11351
- liveList.delete(i);
11352
- localI++;
11353
- }
11354
- } else {
11355
- while (i <= prevEnd && i <= nextEnd) {
11356
- prevNode = prev[i];
11357
- nextNode = next[i];
11358
- const liveListNode = liveList.get(i);
11359
- if (isLiveObject(liveListNode) && isPlainObject(prevNode) && isPlainObject(nextNode)) {
11360
- patchLiveObject(liveListNode, prevNode, nextNode);
11361
- } else {
11362
- liveList.set(i, deepLiveify(nextNode));
11363
- }
11364
- i++;
11365
- }
11366
- while (i <= nextEnd) {
11367
- liveList.insert(deepLiveify(next[i]), i);
11368
- i++;
11369
- }
11370
- let localI = i;
11371
- while (localI <= prevEnd) {
11372
- liveList.delete(i);
11373
- localI++;
11374
- }
11375
- }
11376
- }
11377
- function patchLiveObjectKey(liveObject, key, prev, next) {
11378
- if (process.env.NODE_ENV !== "production") {
11379
- const nonSerializableValue = findNonSerializableValue(next);
11380
- if (nonSerializableValue) {
11381
- error2(
11382
- `New state path: '${nonSerializableValue.path}' value: '${String(
11383
- nonSerializableValue.value
11384
- )}' is not serializable.
11385
- Only serializable value can be synced with Liveblocks.`
11386
- );
11387
- return;
11388
- }
11389
- }
11390
- const value = liveObject.get(key);
11391
- if (next === void 0) {
11392
- liveObject.delete(key);
11393
- } else if (value === void 0) {
11394
- liveObject.set(key, deepLiveify(next));
11395
- } else if (prev === next) {
11396
- return;
11397
- } else if (isLiveList(value) && Array.isArray(prev) && Array.isArray(next)) {
11398
- patchLiveList(value, prev, next);
11399
- } else if (isLiveObject(value) && isPlainObject(prev) && isPlainObject(next)) {
11400
- patchLiveObject(value, prev, next);
11401
- } else {
11402
- liveObject.set(key, deepLiveify(next));
11403
- }
11404
- }
11405
- function patchLiveObject(root, prev, next) {
11406
- const updates = {};
11407
- for (const key in next) {
11408
- patchLiveObjectKey(root, key, prev[key], next[key]);
11409
- }
11410
- for (const key in prev) {
11411
- if (next[key] === void 0) {
11412
- root.delete(key);
11413
- }
11414
- }
11415
- if (Object.keys(updates).length > 0) {
11416
- root.update(updates);
11417
- }
11418
- }
11419
- function getParentsPath(node) {
11420
- const path = [];
11421
- while (node.parent.type === "HasParent") {
11422
- if (isLiveList(node.parent.node)) {
11423
- path.push(node.parent.node._indexOfPosition(node.parent.key));
11424
- } else {
11425
- path.push(node.parent.key);
11426
- }
11427
- node = node.parent.node;
11428
- }
11429
- return path;
11430
- }
11431
- function legacy_patchImmutableObject(state, updates) {
11432
- return updates.reduce(
11433
- (state2, update) => legacy_patchImmutableObjectWithUpdate(state2, update),
11434
- state
11435
- );
11436
- }
11437
- function legacy_patchImmutableObjectWithUpdate(state, update) {
11438
- const path = getParentsPath(update.node);
11439
- return legacy_patchImmutableNode(state, path, update);
11440
- }
11441
- function legacy_patchImmutableNode(state, path, update) {
11442
- const pathItem = path.pop();
11443
- if (pathItem === void 0) {
11444
- switch (update.type) {
11445
- case "LiveObject": {
11446
- if (!isJsonObject(state)) {
11447
- throw new Error(
11448
- "Internal: received update on LiveObject but state was not an object"
11449
- );
11450
- }
11451
- const newState = Object.assign({}, state);
11452
- for (const key in update.updates) {
11453
- if (update.updates[key]?.type === "update") {
11454
- const val = update.node.get(key);
11455
- if (val !== void 0) {
11456
- newState[key] = lsonToJson(val);
11457
- }
11458
- } else if (update.updates[key]?.type === "delete") {
11459
- delete newState[key];
11460
- }
11461
- }
11462
- return newState;
11463
- }
11464
- case "LiveList": {
11465
- if (!Array.isArray(state)) {
11466
- throw new Error(
11467
- "Internal: received update on LiveList but state was not an array"
11468
- );
11469
- }
11470
- let newState = state.map((x) => x);
11471
- for (const listUpdate of update.updates) {
11472
- if (listUpdate.type === "set") {
11473
- newState = newState.map(
11474
- (item, index) => index === listUpdate.index ? lsonToJson(listUpdate.item) : item
11475
- );
11476
- } else if (listUpdate.type === "insert") {
11477
- if (listUpdate.index === newState.length) {
11478
- newState.push(lsonToJson(listUpdate.item));
11479
- } else {
11480
- newState = [
11481
- ...newState.slice(0, listUpdate.index),
11482
- lsonToJson(listUpdate.item),
11483
- ...newState.slice(listUpdate.index)
11484
- ];
11485
- }
11486
- } else if (listUpdate.type === "delete") {
11487
- newState.splice(listUpdate.index, 1);
11488
- } else if (listUpdate.type === "move") {
11489
- if (listUpdate.previousIndex > listUpdate.index) {
11490
- newState = [
11491
- ...newState.slice(0, listUpdate.index),
11492
- lsonToJson(listUpdate.item),
11493
- ...newState.slice(listUpdate.index, listUpdate.previousIndex),
11494
- ...newState.slice(listUpdate.previousIndex + 1)
11495
- ];
11496
- } else {
11497
- newState = [
11498
- ...newState.slice(0, listUpdate.previousIndex),
11499
- ...newState.slice(
11500
- listUpdate.previousIndex + 1,
11501
- listUpdate.index + 1
11502
- ),
11503
- lsonToJson(listUpdate.item),
11504
- ...newState.slice(listUpdate.index + 1)
11505
- ];
11506
- }
11507
- }
11508
- }
11509
- return newState;
11510
- }
11511
- case "LiveMap": {
11512
- if (!isJsonObject(state)) {
11513
- throw new Error(
11514
- "Internal: received update on LiveMap but state was not an object"
11515
- );
11516
- }
11517
- const newState = Object.assign({}, state);
11518
- for (const key in update.updates) {
11519
- if (update.updates[key]?.type === "update") {
11520
- const value = update.node.get(key);
11521
- if (value !== void 0) {
11522
- newState[key] = lsonToJson(value);
11523
- }
11524
- } else if (update.updates[key]?.type === "delete") {
11525
- delete newState[key];
11526
- }
11527
- }
11528
- return newState;
11529
- }
11530
- }
11531
- }
11532
- if (Array.isArray(state)) {
11533
- const newArray = [...state];
11534
- newArray[pathItem] = legacy_patchImmutableNode(
11535
- state[pathItem],
11536
- path,
11537
- update
11538
- );
11539
- return newArray;
11540
- } else if (isJsonObject(state)) {
11541
- const node = state[pathItem];
11542
- if (node === void 0) {
11543
- return state;
11544
- } else {
11545
- const stateAsObj = state;
11546
- return {
11547
- ...stateAsObj,
11548
- [pathItem]: legacy_patchImmutableNode(node, path, update)
11549
- };
11550
- }
11551
- } else {
11552
- return state;
11553
- }
11554
- }
11555
-
11556
11779
  // src/lib/abortController.ts
11557
11780
  function makeAbortController(externalSignal) {
11558
11781
  const ctl = new AbortController();
@@ -11798,6 +12021,7 @@ export {
11798
12021
  kInternal,
11799
12022
  keys,
11800
12023
  legacy_patchImmutableObject,
12024
+ legacy_patchLiveObjectKey,
11801
12025
  lsonToJson,
11802
12026
  makeAbortController,
11803
12027
  makeEventSource,
@@ -11809,7 +12033,6 @@ export {
11809
12033
  nn,
11810
12034
  nodeStreamToCompactNodes,
11811
12035
  objectToQuery,
11812
- patchLiveObjectKey,
11813
12036
  patchNotificationSettings,
11814
12037
  raise,
11815
12038
  resolveMentionsInCommentBody,