@liveblocks/server 1.6.0 → 1.6.2

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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Json, asPos, IUserInfo, SerializedCrdt, JsonObject, DistributiveOmit, SetParentKeyOp, DeleteCrdtOp, ClientMsg as ClientMsg$1, Brand, SerializedRootObject, SerializedChild, StorageNode, CompactNode, PlainLsonObject, NodeMap as NodeMap$1, NodeStream as NodeStream$1, ClientWireOp, ServerMsg as ServerMsg$1, BaseUserMeta, IgnoredOp } from '@liveblocks/core';
1
+ import { Json, asPos, IUserInfo, SerializedCrdt, JsonObject, DistributiveOmit, SetParentKeyOp, DeleteCrdtOp, ClientMsg as ClientMsg$1, Brand, SerializedRootObject, SerializedChild, StorageNode, CompactNode, PlainLsonObject, NodeMap as NodeMap$1, NodeStream as NodeStream$1, ClientWireOp, CreateOp, HasOpId, ServerMsg as ServerMsg$1, BaseUserMeta, IgnoredOp } from '@liveblocks/core';
2
2
  export { BroadcastEventClientMsg, ClientMsg, ClientWireOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, DeleteCrdtOp, DeleteObjectKeyOp, FetchStorageClientMsg, FetchYDocClientMsg, HasOpId, IgnoredOp, Op, RoomStateServerMsg, ServerMsg, ServerWireOp, SetParentKeyOp, UpdateObjectOp, UpdatePresenceClientMsg, UpdateStorageClientMsg, UpdateYDocClientMsg } from '@liveblocks/core';
3
3
  import * as decoders from 'decoders';
4
4
  import { Decoder } from 'decoders';
@@ -1121,7 +1121,18 @@ declare class Logger {
1121
1121
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
1122
1122
  */
1123
1123
 
1124
- type ApplyOpResult = OpAccepted | OpIgnored;
1124
+ /**
1125
+ * The three possible outcomes of applying a client op. They differ along
1126
+ * when the op (first) changed storage state, who hears about it, and what
1127
+ * gets sent back to the originating client:
1128
+ *
1129
+ * | | state change | fan out to others | reply to sender |
1130
+ * |-------------|--------------|-------------------|------------------|
1131
+ * | OpAccepted | now | yes | ack echo (+ fix) |
1132
+ * | OpRectified | in the past | no | ack echo + fix |
1133
+ * | OpIgnored | never | no | bare (H)Ack |
1134
+ */
1135
+ type ApplyOpResult = OpAccepted | OpIgnored | OpRectified;
1125
1136
  type OpAccepted = {
1126
1137
  action: "accepted";
1127
1138
  op: ClientWireOp;
@@ -1131,6 +1142,24 @@ type OpIgnored = {
1131
1142
  action: "ignored";
1132
1143
  ignoredOpId?: string;
1133
1144
  };
1145
+ type OpRectified = {
1146
+ action: "rectified";
1147
+ /**
1148
+ * Echo of the client's op, with the stored, authoritative parentKey. Sent
1149
+ * back to the originating client as the acknowledgement, instead of the
1150
+ * bare (H)Ack. Used for re-sent CREATE ops whose node the server already
1151
+ * stored: the echo carries the authoritative position, so the client can
1152
+ * correct any optimistic local position it may have predicted while the op
1153
+ * was pending. Never fanned out to others: they already received the op
1154
+ * when it was originally accepted.
1155
+ */
1156
+ ackOp: CreateOp & HasOpId;
1157
+ /**
1158
+ * A corrective op to send back to the originating client, stating that
1159
+ * same authoritative position (see ackOp).
1160
+ */
1161
+ fix: FixOp;
1162
+ };
1134
1163
  declare class Storage {
1135
1164
  readonly driver: IStorageDriver;
1136
1165
  constructor(driver: IStorageDriver);
@@ -1410,7 +1439,7 @@ type RoomOptions<SM, CM extends JsonObject, C> = {
1410
1439
  * Therefore, only ever use this hook to implement a side effect (like
1411
1440
  * trigger a notification), don't read storage in this hook directly.
1412
1441
  */
1413
- postClientMsgStorageDidUpdate?: (ctx?: C) => void | Promise<void>;
1442
+ postClientMsgStorageDidUpdate?: (ctx?: C, sess?: BrowserSession<SM, CM>) => void | Promise<void>;
1414
1443
  /**
1415
1444
  * Called when Yjs Storage for the room was updated.
1416
1445
  *
@@ -1782,6 +1811,33 @@ declare class NestedMap<K1, K2, V> {
1782
1811
  deleteAll(key1: K1): void;
1783
1812
  }
1784
1813
 
1814
+ /**
1815
+ * Copyright (c) Liveblocks Inc.
1816
+ *
1817
+ * This program is free software: you can redistribute it and/or modify
1818
+ * it under the terms of the GNU Affero General Public License as published
1819
+ * by the Free Software Foundation, either version 3 of the License, or
1820
+ * (at your option) any later version.
1821
+ *
1822
+ * This program is distributed in the hope that it will be useful,
1823
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1824
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1825
+ * GNU Affero General Public License for more details.
1826
+ *
1827
+ * You should have received a copy of the GNU Affero General Public License
1828
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
1829
+ */
1830
+ /**
1831
+ * Compares two semver-ish version strings (`major.minor.patch` with an optional
1832
+ * `-prerelease` suffix). Returns a negative number if `a < b`, a positive number
1833
+ * if `a > b`, and `0` if they're equal.
1834
+ *
1835
+ * Missing numeric components are treated as `0`, so `"3.21"` and `"3.21.0"`
1836
+ * compare equal. A final release outranks any of its prereleases
1837
+ * (`"3.14.0" > "3.14.0-rc1"`); prereleases are otherwise compared lexically.
1838
+ */
1839
+ declare function cmpSemver(a: string, b: string): number;
1840
+
1785
1841
  /**
1786
1842
  * Copyright (c) Liveblocks Inc.
1787
1843
  *
@@ -1938,4 +1994,4 @@ declare class InMemoryDriver implements IStorageDriver {
1938
1994
  private _loadNodesApi;
1939
1995
  }
1940
1996
 
1941
- export { type ActorID, type AddFeedClientMsg, type AddFeedMessageClientMsg, BackendSession, BrowserSession, ConsoleTarget, type CreateTicketOptions, DefaultMap, type DeleteFeedClientMsg, type DeleteFeedMessageClientMsg, type Feed, type FeedDeletedServerMsg, type FeedMessage, type FeedMessagesAddedServerMsg, type FeedMessagesDeletedServerMsg, type FeedMessagesListServerMsg, type FeedMessagesServerMsg, type FeedMessagesUpdatedServerMsg, FeedMsgCode, FeedRequestErrorCode, type FeedRequestFailedServerMsg, type FeedsAddedServerMsg, type FeedsListServerMsg, type FeedsServerMsg, type FeedsUpdatedServerMsg, type FetchFeedMessagesClientMsg, type FetchFeedsClientMsg, type FixOp, type Guid, type IReadableSnapshot, type IServerWebSocket, type IStorageDriver, type IUserData, InMemoryDriver, type LeasedSession, type ListFeedMessagesOptions, type ListFeedMessagesResult, type ListFeedsOptions, type ListFeedsResult, LogLevel, LogTarget, Logger, type MetadataDB, type Millis, NestedMap, type NodeMap, type NodeStream, type NodeTuple, type Pos, ProtocolVersion, ROOT_YDOC_ID, Room, type SessionKey, type Ticket, UniqueMap, type UpdateFeedClientMsg, type UpdateFeedMessageClientMsg, type YDocId, type YUpdate, type YVector, ackIgnoredOp, clientMsgDecoder, concatUint8Arrays, feedFailureServerMsg, feedMetadataIdDecoder, feedMetadataUpdateDecoder, feedRequestFailed, fetchFeedsMetadataFilterDecoder, guidDecoder, isLeasedSessionExpired, jsonObjectYolo, jsonYolo, type jstring, makeInMemorySnapshot, makeMetadataDB, mapFeedError, optionalFeedMetadataDecoder, plainLsonToNodeStream, protocolVersionDecoder, quote, serialize as serializeServerMsg, snapshotToLossyJson_eager, snapshotToLossyJson_lazy, snapshotToNodeStream, snapshotToPlainLson_eager, snapshotToPlainLson_lazy, Storage as test_only__Storage, YjsStorage as test_only__YjsStorage, transientClientMsgDecoder, tryCatch };
1997
+ export { type ActorID, type AddFeedClientMsg, type AddFeedMessageClientMsg, BackendSession, BrowserSession, ConsoleTarget, type CreateTicketOptions, DefaultMap, type DeleteFeedClientMsg, type DeleteFeedMessageClientMsg, type Feed, type FeedDeletedServerMsg, type FeedMessage, type FeedMessagesAddedServerMsg, type FeedMessagesDeletedServerMsg, type FeedMessagesListServerMsg, type FeedMessagesServerMsg, type FeedMessagesUpdatedServerMsg, FeedMsgCode, FeedRequestErrorCode, type FeedRequestFailedServerMsg, type FeedsAddedServerMsg, type FeedsListServerMsg, type FeedsServerMsg, type FeedsUpdatedServerMsg, type FetchFeedMessagesClientMsg, type FetchFeedsClientMsg, type FixOp, type Guid, type IReadableSnapshot, type IServerWebSocket, type IStorageDriver, type IUserData, InMemoryDriver, type LeasedSession, type ListFeedMessagesOptions, type ListFeedMessagesResult, type ListFeedsOptions, type ListFeedsResult, LogLevel, LogTarget, Logger, type MetadataDB, type Millis, NestedMap, type NodeMap, type NodeStream, type NodeTuple, type Pos, ProtocolVersion, ROOT_YDOC_ID, Room, type SessionKey, type Ticket, UniqueMap, type UpdateFeedClientMsg, type UpdateFeedMessageClientMsg, type YDocId, type YUpdate, type YVector, ackIgnoredOp, clientMsgDecoder, cmpSemver, concatUint8Arrays, feedFailureServerMsg, feedMetadataIdDecoder, feedMetadataUpdateDecoder, feedRequestFailed, fetchFeedsMetadataFilterDecoder, guidDecoder, isLeasedSessionExpired, jsonObjectYolo, jsonYolo, type jstring, makeInMemorySnapshot, makeMetadataDB, mapFeedError, optionalFeedMetadataDecoder, plainLsonToNodeStream, protocolVersionDecoder, quote, serialize as serializeServerMsg, snapshotToLossyJson_eager, snapshotToLossyJson_lazy, snapshotToNodeStream, snapshotToPlainLson_eager, snapshotToPlainLson_lazy, Storage as test_only__Storage, YjsStorage as test_only__YjsStorage, transientClientMsgDecoder, tryCatch };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Json, asPos, IUserInfo, SerializedCrdt, JsonObject, DistributiveOmit, SetParentKeyOp, DeleteCrdtOp, ClientMsg as ClientMsg$1, Brand, SerializedRootObject, SerializedChild, StorageNode, CompactNode, PlainLsonObject, NodeMap as NodeMap$1, NodeStream as NodeStream$1, ClientWireOp, ServerMsg as ServerMsg$1, BaseUserMeta, IgnoredOp } from '@liveblocks/core';
1
+ import { Json, asPos, IUserInfo, SerializedCrdt, JsonObject, DistributiveOmit, SetParentKeyOp, DeleteCrdtOp, ClientMsg as ClientMsg$1, Brand, SerializedRootObject, SerializedChild, StorageNode, CompactNode, PlainLsonObject, NodeMap as NodeMap$1, NodeStream as NodeStream$1, ClientWireOp, CreateOp, HasOpId, ServerMsg as ServerMsg$1, BaseUserMeta, IgnoredOp } from '@liveblocks/core';
2
2
  export { BroadcastEventClientMsg, ClientMsg, ClientWireOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, DeleteCrdtOp, DeleteObjectKeyOp, FetchStorageClientMsg, FetchYDocClientMsg, HasOpId, IgnoredOp, Op, RoomStateServerMsg, ServerMsg, ServerWireOp, SetParentKeyOp, UpdateObjectOp, UpdatePresenceClientMsg, UpdateStorageClientMsg, UpdateYDocClientMsg } from '@liveblocks/core';
3
3
  import * as decoders from 'decoders';
4
4
  import { Decoder } from 'decoders';
@@ -1121,7 +1121,18 @@ declare class Logger {
1121
1121
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
1122
1122
  */
1123
1123
 
1124
- type ApplyOpResult = OpAccepted | OpIgnored;
1124
+ /**
1125
+ * The three possible outcomes of applying a client op. They differ along
1126
+ * when the op (first) changed storage state, who hears about it, and what
1127
+ * gets sent back to the originating client:
1128
+ *
1129
+ * | | state change | fan out to others | reply to sender |
1130
+ * |-------------|--------------|-------------------|------------------|
1131
+ * | OpAccepted | now | yes | ack echo (+ fix) |
1132
+ * | OpRectified | in the past | no | ack echo + fix |
1133
+ * | OpIgnored | never | no | bare (H)Ack |
1134
+ */
1135
+ type ApplyOpResult = OpAccepted | OpIgnored | OpRectified;
1125
1136
  type OpAccepted = {
1126
1137
  action: "accepted";
1127
1138
  op: ClientWireOp;
@@ -1131,6 +1142,24 @@ type OpIgnored = {
1131
1142
  action: "ignored";
1132
1143
  ignoredOpId?: string;
1133
1144
  };
1145
+ type OpRectified = {
1146
+ action: "rectified";
1147
+ /**
1148
+ * Echo of the client's op, with the stored, authoritative parentKey. Sent
1149
+ * back to the originating client as the acknowledgement, instead of the
1150
+ * bare (H)Ack. Used for re-sent CREATE ops whose node the server already
1151
+ * stored: the echo carries the authoritative position, so the client can
1152
+ * correct any optimistic local position it may have predicted while the op
1153
+ * was pending. Never fanned out to others: they already received the op
1154
+ * when it was originally accepted.
1155
+ */
1156
+ ackOp: CreateOp & HasOpId;
1157
+ /**
1158
+ * A corrective op to send back to the originating client, stating that
1159
+ * same authoritative position (see ackOp).
1160
+ */
1161
+ fix: FixOp;
1162
+ };
1134
1163
  declare class Storage {
1135
1164
  readonly driver: IStorageDriver;
1136
1165
  constructor(driver: IStorageDriver);
@@ -1410,7 +1439,7 @@ type RoomOptions<SM, CM extends JsonObject, C> = {
1410
1439
  * Therefore, only ever use this hook to implement a side effect (like
1411
1440
  * trigger a notification), don't read storage in this hook directly.
1412
1441
  */
1413
- postClientMsgStorageDidUpdate?: (ctx?: C) => void | Promise<void>;
1442
+ postClientMsgStorageDidUpdate?: (ctx?: C, sess?: BrowserSession<SM, CM>) => void | Promise<void>;
1414
1443
  /**
1415
1444
  * Called when Yjs Storage for the room was updated.
1416
1445
  *
@@ -1782,6 +1811,33 @@ declare class NestedMap<K1, K2, V> {
1782
1811
  deleteAll(key1: K1): void;
1783
1812
  }
1784
1813
 
1814
+ /**
1815
+ * Copyright (c) Liveblocks Inc.
1816
+ *
1817
+ * This program is free software: you can redistribute it and/or modify
1818
+ * it under the terms of the GNU Affero General Public License as published
1819
+ * by the Free Software Foundation, either version 3 of the License, or
1820
+ * (at your option) any later version.
1821
+ *
1822
+ * This program is distributed in the hope that it will be useful,
1823
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1824
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1825
+ * GNU Affero General Public License for more details.
1826
+ *
1827
+ * You should have received a copy of the GNU Affero General Public License
1828
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
1829
+ */
1830
+ /**
1831
+ * Compares two semver-ish version strings (`major.minor.patch` with an optional
1832
+ * `-prerelease` suffix). Returns a negative number if `a < b`, a positive number
1833
+ * if `a > b`, and `0` if they're equal.
1834
+ *
1835
+ * Missing numeric components are treated as `0`, so `"3.21"` and `"3.21.0"`
1836
+ * compare equal. A final release outranks any of its prereleases
1837
+ * (`"3.14.0" > "3.14.0-rc1"`); prereleases are otherwise compared lexically.
1838
+ */
1839
+ declare function cmpSemver(a: string, b: string): number;
1840
+
1785
1841
  /**
1786
1842
  * Copyright (c) Liveblocks Inc.
1787
1843
  *
@@ -1938,4 +1994,4 @@ declare class InMemoryDriver implements IStorageDriver {
1938
1994
  private _loadNodesApi;
1939
1995
  }
1940
1996
 
1941
- export { type ActorID, type AddFeedClientMsg, type AddFeedMessageClientMsg, BackendSession, BrowserSession, ConsoleTarget, type CreateTicketOptions, DefaultMap, type DeleteFeedClientMsg, type DeleteFeedMessageClientMsg, type Feed, type FeedDeletedServerMsg, type FeedMessage, type FeedMessagesAddedServerMsg, type FeedMessagesDeletedServerMsg, type FeedMessagesListServerMsg, type FeedMessagesServerMsg, type FeedMessagesUpdatedServerMsg, FeedMsgCode, FeedRequestErrorCode, type FeedRequestFailedServerMsg, type FeedsAddedServerMsg, type FeedsListServerMsg, type FeedsServerMsg, type FeedsUpdatedServerMsg, type FetchFeedMessagesClientMsg, type FetchFeedsClientMsg, type FixOp, type Guid, type IReadableSnapshot, type IServerWebSocket, type IStorageDriver, type IUserData, InMemoryDriver, type LeasedSession, type ListFeedMessagesOptions, type ListFeedMessagesResult, type ListFeedsOptions, type ListFeedsResult, LogLevel, LogTarget, Logger, type MetadataDB, type Millis, NestedMap, type NodeMap, type NodeStream, type NodeTuple, type Pos, ProtocolVersion, ROOT_YDOC_ID, Room, type SessionKey, type Ticket, UniqueMap, type UpdateFeedClientMsg, type UpdateFeedMessageClientMsg, type YDocId, type YUpdate, type YVector, ackIgnoredOp, clientMsgDecoder, concatUint8Arrays, feedFailureServerMsg, feedMetadataIdDecoder, feedMetadataUpdateDecoder, feedRequestFailed, fetchFeedsMetadataFilterDecoder, guidDecoder, isLeasedSessionExpired, jsonObjectYolo, jsonYolo, type jstring, makeInMemorySnapshot, makeMetadataDB, mapFeedError, optionalFeedMetadataDecoder, plainLsonToNodeStream, protocolVersionDecoder, quote, serialize as serializeServerMsg, snapshotToLossyJson_eager, snapshotToLossyJson_lazy, snapshotToNodeStream, snapshotToPlainLson_eager, snapshotToPlainLson_lazy, Storage as test_only__Storage, YjsStorage as test_only__YjsStorage, transientClientMsgDecoder, tryCatch };
1997
+ export { type ActorID, type AddFeedClientMsg, type AddFeedMessageClientMsg, BackendSession, BrowserSession, ConsoleTarget, type CreateTicketOptions, DefaultMap, type DeleteFeedClientMsg, type DeleteFeedMessageClientMsg, type Feed, type FeedDeletedServerMsg, type FeedMessage, type FeedMessagesAddedServerMsg, type FeedMessagesDeletedServerMsg, type FeedMessagesListServerMsg, type FeedMessagesServerMsg, type FeedMessagesUpdatedServerMsg, FeedMsgCode, FeedRequestErrorCode, type FeedRequestFailedServerMsg, type FeedsAddedServerMsg, type FeedsListServerMsg, type FeedsServerMsg, type FeedsUpdatedServerMsg, type FetchFeedMessagesClientMsg, type FetchFeedsClientMsg, type FixOp, type Guid, type IReadableSnapshot, type IServerWebSocket, type IStorageDriver, type IUserData, InMemoryDriver, type LeasedSession, type ListFeedMessagesOptions, type ListFeedMessagesResult, type ListFeedsOptions, type ListFeedsResult, LogLevel, LogTarget, Logger, type MetadataDB, type Millis, NestedMap, type NodeMap, type NodeStream, type NodeTuple, type Pos, ProtocolVersion, ROOT_YDOC_ID, Room, type SessionKey, type Ticket, UniqueMap, type UpdateFeedClientMsg, type UpdateFeedMessageClientMsg, type YDocId, type YUpdate, type YVector, ackIgnoredOp, clientMsgDecoder, cmpSemver, concatUint8Arrays, feedFailureServerMsg, feedMetadataIdDecoder, feedMetadataUpdateDecoder, feedRequestFailed, fetchFeedsMetadataFilterDecoder, guidDecoder, isLeasedSessionExpired, jsonObjectYolo, jsonYolo, type jstring, makeInMemorySnapshot, makeMetadataDB, mapFeedError, optionalFeedMetadataDecoder, plainLsonToNodeStream, protocolVersionDecoder, quote, serialize as serializeServerMsg, snapshotToLossyJson_eager, snapshotToLossyJson_lazy, snapshotToNodeStream, snapshotToPlainLson_eager, snapshotToPlainLson_lazy, Storage as test_only__Storage, YjsStorage as test_only__YjsStorage, transientClientMsgDecoder, tryCatch };
package/dist/index.js CHANGED
@@ -1563,6 +1563,13 @@ function accept(op2, fix) {
1563
1563
  function ignore(ignoredOp) {
1564
1564
  return { action: "ignored", ignoredOpId: ignoredOp.opId };
1565
1565
  }
1566
+ function rectify(op2, parentKey) {
1567
+ return {
1568
+ action: "rectified",
1569
+ ackOp: { ...op2, parentKey },
1570
+ fix: { type: OpCode2.SET_PARENT_KEY, id: op2.id, parentKey }
1571
+ };
1572
+ }
1566
1573
  function nodeFromCreateChildOp(op2) {
1567
1574
  switch (op2.type) {
1568
1575
  case OpCode2.CREATE_LIST:
@@ -1652,6 +1659,12 @@ var Storage = class {
1652
1659
  }
1653
1660
  applyCreateOp(op2) {
1654
1661
  if (this.driver.has_node(op2.id)) {
1662
+ if (op2.intent === "push") {
1663
+ const stored = this.driver.get_node(op2.id);
1664
+ if (stored?.parentId !== void 0 && this.driver.get_node(stored.parentId)?.type === CrdtType5.LIST) {
1665
+ return rectify(op2, stored.parentKey);
1666
+ }
1667
+ }
1655
1668
  return ignore(op2);
1656
1669
  }
1657
1670
  const node = nodeFromCreateChildOp(op2);
@@ -3109,17 +3122,21 @@ var Room = class {
3109
3122
  const opsToForward = result.flatMap(
3110
3123
  (r) => r.action === "accepted" ? [r.op] : []
3111
3124
  );
3112
- const opsToSendBack = result.flatMap((r) => {
3113
- switch (r.action) {
3114
- case "ignored":
3115
- return r.ignoredOpId !== void 0 ? [ackIgnoredOp(r.ignoredOpId)] : [];
3116
- case "accepted":
3117
- return r.fix !== void 0 ? [r.fix] : [];
3118
- // istanbul ignore next
3119
- default:
3120
- return assertNever3(r, "Unhandled case");
3125
+ const opsToSendBack = result.flatMap(
3126
+ (r) => {
3127
+ switch (r.action) {
3128
+ case "ignored":
3129
+ return r.ignoredOpId !== void 0 ? [ackIgnoredOp(r.ignoredOpId)] : [];
3130
+ case "rectified":
3131
+ return [r.ackOp, r.fix];
3132
+ case "accepted":
3133
+ return r.fix !== void 0 ? [r.fix] : [];
3134
+ // istanbul ignore next
3135
+ default:
3136
+ return assertNever3(r, "Unhandled case");
3137
+ }
3121
3138
  }
3122
- });
3139
+ );
3123
3140
  if (opsToForward.length > 0) {
3124
3141
  scheduleFanOut({
3125
3142
  type: ServerMsgCode2.UPDATE_STORAGE,
@@ -3137,7 +3154,7 @@ var Room = class {
3137
3154
  });
3138
3155
  }
3139
3156
  if (opsToForward.length > 0) {
3140
- const p$ = this.hooks.postClientMsgStorageDidUpdate?.(ctx);
3157
+ const p$ = this.hooks.postClientMsgStorageDidUpdate?.(ctx, session);
3141
3158
  if (p$) defer(p$);
3142
3159
  }
3143
3160
  break;
@@ -3378,6 +3395,23 @@ var Room = class {
3378
3395
  };
3379
3396
  _logger = new WeakMap();
3380
3397
  __debug2 = new WeakMap();
3398
+
3399
+ // src/lib/semver.ts
3400
+ function cmpSemver(a, b) {
3401
+ const [coreA = "", preA] = a.split("-", 2);
3402
+ const [coreB = "", preB] = b.split("-", 2);
3403
+ const partsA = coreA.split(".").map(Number);
3404
+ const partsB = coreB.split(".").map(Number);
3405
+ for (let i = 0; i < 3; i++) {
3406
+ const pa = partsA[i] ?? 0;
3407
+ const pb = partsB[i] ?? 0;
3408
+ if (pa !== pb) return pa - pb;
3409
+ }
3410
+ if (!preA && preB) return 1;
3411
+ if (preA && !preB) return -1;
3412
+ if (preA && preB) return preA < preB ? -1 : preA > preB ? 1 : 0;
3413
+ return 0;
3414
+ }
3381
3415
  export {
3382
3416
  BackendSession,
3383
3417
  BrowserSession,
@@ -3396,6 +3430,7 @@ export {
3396
3430
  UniqueMap,
3397
3431
  ackIgnoredOp,
3398
3432
  clientMsgDecoder,
3433
+ cmpSemver,
3399
3434
  concatUint8Arrays,
3400
3435
  feedFailureServerMsg,
3401
3436
  feedMetadataIdDecoder,