@liveblocks/server 1.4.1 → 1.4.2-pre1

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, Awaitable, 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, Awaitable, CompactNode, PlainLsonObject, NodeMap as NodeMap$1, NodeStream as NodeStream$1, ClientWireOp, 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';
@@ -23,6 +23,37 @@ import * as Y from 'yjs';
23
23
  */
24
24
 
25
25
  type Pos = ReturnType<typeof asPos>;
26
+ declare const fromType: unique symbol;
27
+ /**
28
+ * A string known to be a valid JSON-encoded value. Use this whenever a
29
+ * `string` already carries JSON, to keep that fact visible in the type system
30
+ * (instead of letting it look like a freeform `string`).
31
+ *
32
+ * Optionally parameterised by the *parsed* shape: `jstring<CompactNode>`
33
+ * means "a string whose `JSON.parse` result is a `CompactNode`". Without
34
+ * a type argument, defaults to `jstring<Json>` (any JSON value).
35
+ *
36
+ * At storage boundaries (e.g. reading `jdata` from SQLite), `as jstring`
37
+ * is acceptable — we trust the bytes are valid JSON because we wrote them as
38
+ * JSON ourselves.
39
+ *
40
+ * For example, these are valid JSON strings:
41
+ * - '0', '1', '2', '3', '3.14', etc.
42
+ * - 'null', 'true', 'false'
43
+ * - '{"foo":1}', '{ "foo": 1 }' (spaces are fine)
44
+ * - '[]', '[1,2, 3]', '[[]]', etc.
45
+ * - '["hi",{}]'
46
+ * - '"foo"'
47
+ *
48
+ * But these are not:
49
+ * - 'foo'
50
+ * - '1,2,3'
51
+ * - '{'
52
+ * - '[1,2,3,]' or '{"foo":1},' (trailing commas are not valid)
53
+ */
54
+ type jstring<J = Json> = string & {
55
+ readonly [fromType]: J;
56
+ };
26
57
  type NodeTuple<T extends SerializedCrdt = SerializedCrdt> = [
27
58
  id: string,
28
59
  value: T
@@ -683,6 +714,27 @@ interface IStorageDriverNodeAPI {
683
714
  * Yield all nodes as [id, node] pairs. Must always include the root node.
684
715
  */
685
716
  iter_nodes(): Iterable<StorageNode>;
717
+ /**
718
+ * Yield each node as a pre-built `CompactNode` JSON tuple string, ready to
719
+ * be emitted directly into a STORAGE_CHUNK wire frame without JSON.parse()
720
+ * or JSON.stringify()'ing overhead. Implementations MUST produce text whose
721
+ * parsed shape exactly matches the `CompactNode` union type from
722
+ * @liveblocks/core. The emitted shapes are:
723
+ *
724
+ * - Root node: '["root",<data>]'
725
+ * - OBJECT / REGISTER: '["0:1",0,"root","a",<data>]'
726
+ * - LIST / MAP: '["0:2",1,"0:1","b"]'
727
+ *
728
+ * Invariant (implementations MUST uphold; asserted by `_generateFullTestSuite`):
729
+ *
730
+ * iter_nodes_optimized().map(JSON.parse)
731
+ * ≡ nodeStreamToCompactNodes(iter_nodes())
732
+ *
733
+ * i.e. parsing each yielded string yields the same sequence of CompactNodes
734
+ * that the canonical `iter_nodes()` + `nodeStreamToCompactNodes()` path
735
+ * would produce.
736
+ */
737
+ iter_nodes_optimized(): Iterable<jstring<CompactNode>>;
686
738
  /**
687
739
  * Return true iff a node with the given id exists. Must return true for "root".
688
740
  */
@@ -1228,10 +1280,9 @@ type Millis = Brand<number, "Millis">;
1228
1280
  * receives it as part of its ROOM_STATE message.
1229
1281
  */
1230
1282
  type SessionKey = Brand<string, "SessionKey">;
1231
- type PreSerializedServerMsg = Brand<string, "PreSerializedServerMsg">;
1232
1283
  type ClientMsg = ClientMsg$1<JsonObject, Json> | FetchFeedsClientMsg | FetchFeedMessagesClientMsg | AddFeedClientMsg | UpdateFeedClientMsg | DeleteFeedClientMsg | AddFeedMessageClientMsg | UpdateFeedMessageClientMsg | DeleteFeedMessageClientMsg;
1233
1284
  type ServerMsg = ServerMsg$1<JsonObject, BaseUserMeta, Json> | FeedMessagesServerMsg | FeedsServerMsg;
1234
- declare function serialize(msgs: ServerMsg | readonly ServerMsg[]): PreSerializedServerMsg;
1285
+ declare function serialize(msgs: ServerMsg | readonly ServerMsg[]): jstring<ServerMsg>;
1235
1286
  declare function ackIgnoredOp(opId: string): IgnoredOp;
1236
1287
  /**
1237
1288
  * A known or anonymous user.
@@ -1278,7 +1329,7 @@ declare class BrowserSession<SM, CM extends JsonObject> {
1278
1329
  get hasNotifiedClientStorageUpdateError(): boolean;
1279
1330
  setHasNotifiedClientStorageUpdateError(): void;
1280
1331
  sendPong(): number;
1281
- send(serverMsg: ServerMsg | ServerMsg[] | PreSerializedServerMsg): number;
1332
+ send(serverMsg: ServerMsg | ServerMsg[] | jstring<ServerMsg>): number;
1282
1333
  }
1283
1334
  declare class BackendSession extends BrowserSession<never, never> {
1284
1335
  }
@@ -1317,13 +1368,6 @@ type RoomOptions<SM, CM extends JsonObject, C> = {
1317
1368
  */
1318
1369
  storage?: IStorageDriver;
1319
1370
  logger?: Logger;
1320
- /**
1321
- * Whether to allow streaming storage responses. Only safe with drivers
1322
- * that can guarantee that no Ops from other clients can get interleaved
1323
- * between the chunk generation until the last chunk has been sent.
1324
- * Defaults to true, but is notably NOT safe to use from DOS-KV backends.
1325
- */
1326
- allowStreaming?: boolean;
1327
1371
  hooks?: {
1328
1372
  /** Customize which incoming messages from a client are allowed or disallowed. */
1329
1373
  isClientMsgAllowed?: (msg: ClientMsg, session: BrowserSession<SM, CM>) => {
@@ -1436,7 +1480,7 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
1436
1480
  createTicket(options?: CreateTicketOptions<SM, CM>): Promise<Ticket<SM, CM>>;
1437
1481
  createBackendSession_experimental(): Promise<[
1438
1482
  session: BackendSession,
1439
- outgoingMessages: PreSerializedServerMsg[]
1483
+ outgoingMessages: jstring<ServerMsg>[]
1440
1484
  ]>;
1441
1485
  /**
1442
1486
  * Restores the given sessions as the Room server's session list. Can only be
@@ -1895,4 +1939,4 @@ declare class InMemoryDriver implements IStorageDriver {
1895
1939
  load_nodes_api(): IStorageDriverNodeAPI;
1896
1940
  }
1897
1941
 
1898
- 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 IStorageDriverNodeAPI, type IUserData, InMemoryDriver, type LeasedSession, type ListFeedMessagesOptions, type ListFeedMessagesResult, type ListFeedsOptions, type ListFeedsResult, type LoadingState, LogLevel, LogTarget, Logger, type MetadataDB, type Millis, NestedMap, type NodeMap, type NodeStream, type NodeTuple, type Pos, type PreSerializedServerMsg, 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, 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 };
1942
+ 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 IStorageDriverNodeAPI, type IUserData, InMemoryDriver, type LeasedSession, type ListFeedMessagesOptions, type ListFeedMessagesResult, type ListFeedsOptions, type ListFeedsResult, type LoadingState, 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 };
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, Awaitable, 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, Awaitable, CompactNode, PlainLsonObject, NodeMap as NodeMap$1, NodeStream as NodeStream$1, ClientWireOp, 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';
@@ -23,6 +23,37 @@ import * as Y from 'yjs';
23
23
  */
24
24
 
25
25
  type Pos = ReturnType<typeof asPos>;
26
+ declare const fromType: unique symbol;
27
+ /**
28
+ * A string known to be a valid JSON-encoded value. Use this whenever a
29
+ * `string` already carries JSON, to keep that fact visible in the type system
30
+ * (instead of letting it look like a freeform `string`).
31
+ *
32
+ * Optionally parameterised by the *parsed* shape: `jstring<CompactNode>`
33
+ * means "a string whose `JSON.parse` result is a `CompactNode`". Without
34
+ * a type argument, defaults to `jstring<Json>` (any JSON value).
35
+ *
36
+ * At storage boundaries (e.g. reading `jdata` from SQLite), `as jstring`
37
+ * is acceptable — we trust the bytes are valid JSON because we wrote them as
38
+ * JSON ourselves.
39
+ *
40
+ * For example, these are valid JSON strings:
41
+ * - '0', '1', '2', '3', '3.14', etc.
42
+ * - 'null', 'true', 'false'
43
+ * - '{"foo":1}', '{ "foo": 1 }' (spaces are fine)
44
+ * - '[]', '[1,2, 3]', '[[]]', etc.
45
+ * - '["hi",{}]'
46
+ * - '"foo"'
47
+ *
48
+ * But these are not:
49
+ * - 'foo'
50
+ * - '1,2,3'
51
+ * - '{'
52
+ * - '[1,2,3,]' or '{"foo":1},' (trailing commas are not valid)
53
+ */
54
+ type jstring<J = Json> = string & {
55
+ readonly [fromType]: J;
56
+ };
26
57
  type NodeTuple<T extends SerializedCrdt = SerializedCrdt> = [
27
58
  id: string,
28
59
  value: T
@@ -683,6 +714,27 @@ interface IStorageDriverNodeAPI {
683
714
  * Yield all nodes as [id, node] pairs. Must always include the root node.
684
715
  */
685
716
  iter_nodes(): Iterable<StorageNode>;
717
+ /**
718
+ * Yield each node as a pre-built `CompactNode` JSON tuple string, ready to
719
+ * be emitted directly into a STORAGE_CHUNK wire frame without JSON.parse()
720
+ * or JSON.stringify()'ing overhead. Implementations MUST produce text whose
721
+ * parsed shape exactly matches the `CompactNode` union type from
722
+ * @liveblocks/core. The emitted shapes are:
723
+ *
724
+ * - Root node: '["root",<data>]'
725
+ * - OBJECT / REGISTER: '["0:1",0,"root","a",<data>]'
726
+ * - LIST / MAP: '["0:2",1,"0:1","b"]'
727
+ *
728
+ * Invariant (implementations MUST uphold; asserted by `_generateFullTestSuite`):
729
+ *
730
+ * iter_nodes_optimized().map(JSON.parse)
731
+ * ≡ nodeStreamToCompactNodes(iter_nodes())
732
+ *
733
+ * i.e. parsing each yielded string yields the same sequence of CompactNodes
734
+ * that the canonical `iter_nodes()` + `nodeStreamToCompactNodes()` path
735
+ * would produce.
736
+ */
737
+ iter_nodes_optimized(): Iterable<jstring<CompactNode>>;
686
738
  /**
687
739
  * Return true iff a node with the given id exists. Must return true for "root".
688
740
  */
@@ -1228,10 +1280,9 @@ type Millis = Brand<number, "Millis">;
1228
1280
  * receives it as part of its ROOM_STATE message.
1229
1281
  */
1230
1282
  type SessionKey = Brand<string, "SessionKey">;
1231
- type PreSerializedServerMsg = Brand<string, "PreSerializedServerMsg">;
1232
1283
  type ClientMsg = ClientMsg$1<JsonObject, Json> | FetchFeedsClientMsg | FetchFeedMessagesClientMsg | AddFeedClientMsg | UpdateFeedClientMsg | DeleteFeedClientMsg | AddFeedMessageClientMsg | UpdateFeedMessageClientMsg | DeleteFeedMessageClientMsg;
1233
1284
  type ServerMsg = ServerMsg$1<JsonObject, BaseUserMeta, Json> | FeedMessagesServerMsg | FeedsServerMsg;
1234
- declare function serialize(msgs: ServerMsg | readonly ServerMsg[]): PreSerializedServerMsg;
1285
+ declare function serialize(msgs: ServerMsg | readonly ServerMsg[]): jstring<ServerMsg>;
1235
1286
  declare function ackIgnoredOp(opId: string): IgnoredOp;
1236
1287
  /**
1237
1288
  * A known or anonymous user.
@@ -1278,7 +1329,7 @@ declare class BrowserSession<SM, CM extends JsonObject> {
1278
1329
  get hasNotifiedClientStorageUpdateError(): boolean;
1279
1330
  setHasNotifiedClientStorageUpdateError(): void;
1280
1331
  sendPong(): number;
1281
- send(serverMsg: ServerMsg | ServerMsg[] | PreSerializedServerMsg): number;
1332
+ send(serverMsg: ServerMsg | ServerMsg[] | jstring<ServerMsg>): number;
1282
1333
  }
1283
1334
  declare class BackendSession extends BrowserSession<never, never> {
1284
1335
  }
@@ -1317,13 +1368,6 @@ type RoomOptions<SM, CM extends JsonObject, C> = {
1317
1368
  */
1318
1369
  storage?: IStorageDriver;
1319
1370
  logger?: Logger;
1320
- /**
1321
- * Whether to allow streaming storage responses. Only safe with drivers
1322
- * that can guarantee that no Ops from other clients can get interleaved
1323
- * between the chunk generation until the last chunk has been sent.
1324
- * Defaults to true, but is notably NOT safe to use from DOS-KV backends.
1325
- */
1326
- allowStreaming?: boolean;
1327
1371
  hooks?: {
1328
1372
  /** Customize which incoming messages from a client are allowed or disallowed. */
1329
1373
  isClientMsgAllowed?: (msg: ClientMsg, session: BrowserSession<SM, CM>) => {
@@ -1436,7 +1480,7 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
1436
1480
  createTicket(options?: CreateTicketOptions<SM, CM>): Promise<Ticket<SM, CM>>;
1437
1481
  createBackendSession_experimental(): Promise<[
1438
1482
  session: BackendSession,
1439
- outgoingMessages: PreSerializedServerMsg[]
1483
+ outgoingMessages: jstring<ServerMsg>[]
1440
1484
  ]>;
1441
1485
  /**
1442
1486
  * Restores the given sessions as the Room server's session list. Can only be
@@ -1895,4 +1939,4 @@ declare class InMemoryDriver implements IStorageDriver {
1895
1939
  load_nodes_api(): IStorageDriverNodeAPI;
1896
1940
  }
1897
1941
 
1898
- 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 IStorageDriverNodeAPI, type IUserData, InMemoryDriver, type LeasedSession, type ListFeedMessagesOptions, type ListFeedMessagesResult, type ListFeedsOptions, type ListFeedsResult, type LoadingState, LogLevel, LogTarget, Logger, type MetadataDB, type Millis, NestedMap, type NodeMap, type NodeStream, type NodeTuple, type Pos, type PreSerializedServerMsg, 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, 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 };
1942
+ 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 IStorageDriverNodeAPI, type IUserData, InMemoryDriver, type LeasedSession, type ListFeedMessagesOptions, type ListFeedMessagesResult, type ListFeedsOptions, type ListFeedsResult, type LoadingState, 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 };
package/dist/index.js CHANGED
@@ -129,7 +129,9 @@ var feedMetadataRecordForCreate = record(
129
129
  (value) => Object.keys(value).length <= MAX_METADATA_COUNT,
130
130
  `Must be at most ${MAX_METADATA_COUNT} items`
131
131
  );
132
- var optionalFeedMetadataDecoder = optional(feedMetadataRecordForCreate);
132
+ var optionalFeedMetadataDecoder = optional(
133
+ feedMetadataRecordForCreate
134
+ );
133
135
  var feedMetadataUpdateDecoder = record(
134
136
  feedMetadataIdDecoder,
135
137
  feedMetadataNullableValueDecoder
@@ -820,7 +822,6 @@ function makeMetadataDB(driver) {
820
822
  import {
821
823
  assertNever as assertNever3,
822
824
  ClientMsgCode as ClientMsgCode2,
823
- nodeStreamToCompactNodes,
824
825
  OpCode as OpCode3,
825
826
  raise as raise3,
826
827
  ServerMsgCode as CoreServerMsgCode,
@@ -829,7 +830,7 @@ import {
829
830
  } from "@liveblocks/core";
830
831
  import { Mutex, tryAcquire } from "async-mutex";
831
832
  import { array as array3, formatInline } from "decoders";
832
- import { chunked } from "itertools";
833
+ import { chunkedByCost } from "itertools";
833
834
  import { nanoid as nanoid2 } from "nanoid";
834
835
 
835
836
  // src/lib/Logger.ts
@@ -964,7 +965,13 @@ var Logger = class _Logger {
964
965
  };
965
966
 
966
967
  // src/plugins/InMemoryDriver.ts
967
- import { asPos, CrdtType as CrdtType4, isRootStorageNode as isRootStorageNode2, nn as nn2 } from "@liveblocks/core";
968
+ import {
969
+ asPos,
970
+ CrdtType as CrdtType4,
971
+ isRootStorageNode as isRootStorageNode2,
972
+ nn as nn2,
973
+ nodeStreamToCompactNodes
974
+ } from "@liveblocks/core";
968
975
  import { ifilter, imap } from "itertools";
969
976
 
970
977
  // src/lib/text.ts
@@ -1374,6 +1381,17 @@ var InMemoryDriver = class {
1374
1381
  * Yield all nodes as [id, node] pairs. Must always include the root node.
1375
1382
  */
1376
1383
  iter_nodes: () => nodes,
1384
+ /**
1385
+ * Yield each node as a pre-built CompactNode JSON tuple string.
1386
+ *
1387
+ * This implementation IS the canonical reference for the invariant:
1388
+ * iter_nodes_optimized` ≡ `nodeStreamToCompactNodes(iter_nodes())
1389
+ */
1390
+ *iter_nodes_optimized() {
1391
+ for (const compact of nodeStreamToCompactNodes(nodes)) {
1392
+ yield JSON.stringify(compact);
1393
+ }
1394
+ },
1377
1395
  /**
1378
1396
  * Return true iff a node with the given id exists. Must return true for "root".
1379
1397
  */
@@ -2035,6 +2053,7 @@ function isLeasedSessionExpired(leasedSession) {
2035
2053
  }
2036
2054
 
2037
2055
  // src/Room.ts
2056
+ var MB = 1024 * 1024;
2038
2057
  var messagesDecoder = array3(clientMsgDecoder);
2039
2058
  var ServerMsgCode2 = { ...CoreServerMsgCode, ...FeedMsgCode };
2040
2059
  var HIGHEST_PROTOCOL_VERSION = Math.max(
@@ -2048,6 +2067,11 @@ var SERVER_MSG_CODE_NAMES = Object.fromEntries(
2048
2067
  var BLACK_HOLE = new Logger([
2049
2068
  /* No targets, i.e. black hole logger */
2050
2069
  ]);
2070
+ function groupNodesForWebSocketMessages(input) {
2071
+ const MAX_SIZE = 16 * MB;
2072
+ const idealSize = (chunkIndex) => chunkIndex < 3 ? 1 * MB : Math.min(8, chunkIndex - 1) * MB;
2073
+ return chunkedByCost(input, (tup) => tup.length, MAX_SIZE, idealSize);
2074
+ }
2051
2075
  function collectSideEffects() {
2052
2076
  const deferred = [];
2053
2077
  return {
@@ -2181,7 +2205,7 @@ var BackendSession = class extends BrowserSession {
2181
2205
  super(ticket, socket, debug);
2182
2206
  }
2183
2207
  };
2184
- var __debug2, __allowStreaming;
2208
+ var __debug2;
2185
2209
  var Room = class {
2186
2210
  constructor(meta, options) {
2187
2211
  // ^^^^^^^^^^ User-defined Room Metadata, Session Metadata, and Client Metadata
@@ -2203,12 +2227,10 @@ var Room = class {
2203
2227
  __publicField(this, "sessions", new UniqueMap((s) => s.actor));
2204
2228
  __publicField(this, "hooks");
2205
2229
  __privateAdd(this, __debug2);
2206
- __privateAdd(this, __allowStreaming);
2207
2230
  const driver = options?.storage ?? makeNewInMemoryDriver();
2208
2231
  this.meta = meta;
2209
2232
  this.driver = driver;
2210
2233
  this.logger = options?.logger ?? BLACK_HOLE;
2211
- __privateSet(this, __allowStreaming, options?.allowStreaming ?? true);
2212
2234
  this.hooks = {
2213
2235
  isClientMsgAllowed: options?.hooks?.isClientMsgAllowed ?? (() => {
2214
2236
  return {
@@ -2973,7 +2995,13 @@ var Room = class {
2973
2995
  const toReplyImmediately = [];
2974
2996
  const toReplyAfter = [];
2975
2997
  const replyImmediately = (msg) => {
2976
- if (Array.isArray(msg)) {
2998
+ if (typeof msg === "string") {
2999
+ if (toReplyImmediately.length > 0) {
3000
+ session.send(toReplyImmediately);
3001
+ toReplyImmediately.length = 0;
3002
+ }
3003
+ session.send(msg);
3004
+ } else if (Array.isArray(msg)) {
2977
3005
  for (const m of msg) {
2978
3006
  toReplyImmediately.push(m);
2979
3007
  }
@@ -3031,24 +3059,10 @@ var Room = class {
3031
3059
  }
3032
3060
  case ClientMsgCode2.FETCH_STORAGE: {
3033
3061
  if (session.version >= 8 /* V8 */) {
3034
- if (__privateGet(this, __allowStreaming)) {
3035
- const NODES_PER_CHUNK = 250;
3036
- for (const chunk of chunked(
3037
- nodeStreamToCompactNodes(this.storage.loadedDriver.iter_nodes()),
3038
- NODES_PER_CHUNK
3039
- )) {
3040
- replyImmediately({
3041
- type: ServerMsgCode2.STORAGE_CHUNK,
3042
- nodes: chunk
3043
- });
3044
- }
3045
- } else {
3046
- replyImmediately({
3047
- type: ServerMsgCode2.STORAGE_CHUNK,
3048
- nodes: Array.from(
3049
- nodeStreamToCompactNodes(this.storage.loadedDriver.iter_nodes())
3050
- )
3051
- });
3062
+ const rawStream = this.storage.loadedDriver.iter_nodes_optimized();
3063
+ for (const chunk of groupNodesForWebSocketMessages(rawStream)) {
3064
+ const frame = `{"type":${ServerMsgCode2.STORAGE_CHUNK},"nodes":[${chunk.join(",")}]}`;
3065
+ replyImmediately(frame);
3052
3066
  }
3053
3067
  replyImmediately({ type: ServerMsgCode2.STORAGE_STREAM_END });
3054
3068
  } else {
@@ -3327,7 +3341,6 @@ var Room = class {
3327
3341
  }
3328
3342
  };
3329
3343
  __debug2 = new WeakMap();
3330
- __allowStreaming = new WeakMap();
3331
3344
  export {
3332
3345
  BackendSession,
3333
3346
  BrowserSession,