@korajs/sync 1.0.0-beta.0 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SyncScopeMap, D as DeltaCursor, a as SyncMessage, b as SerializedOperation, W as WireFormat, c as SyncTransport, T as TransportOptions, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, g as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, h as SyncState, i as SyncStatusInfo, j as SyncConfig, k as SyncStatePersistence, l as SyncQuerySubset } from './transport-BMesMB6s.cjs';
2
- export { A as AcknowledgmentMessage, m as AwarenessStateWire, n as AwarenessUpdateMessage, E as EncryptedPayload, o as ErrorMessage, H as HandshakeMessage, p as HandshakeResponseMessage, I as InvalidScopeError, O as OperationBatchMessage, q as SYNC_STATES, r as SYNC_STATUSES, s as ScopeViolationError, t as SyncEncryptionAlgorithm, u as SyncScopeContext, v as SyncStatus, w as dedupeQuerySubsets, x as isAcknowledgmentMessage, y as isAwarenessUpdateMessage, z as isErrorMessage, B as isHandshakeMessage, C as isHandshakeResponseMessage, F as isOperationBatchMessage, G as isSyncMessage, J as isYjsDocUpdateMessage, K as operationMatchesQuerySubsets } from './transport-BMesMB6s.cjs';
1
+ import { S as SyncScopeMap, D as DeltaCursor, a as SyncMessage, b as SerializedOperation, W as WireFormat, c as SyncTransport, T as TransportOptions, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, B as BlobChunkRequestMessage, g as BlobChunkResponseMessage, h as BlobChunkPushMessage, i as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, j as SyncState, k as SyncStatusInfo, l as SyncConfig, m as SyncStatePersistence, n as SyncQuerySubset } from './transport-CZLMdZ0k.cjs';
2
+ export { A as AcknowledgmentMessage, o as AwarenessStateWire, p as AwarenessUpdateMessage, E as EncryptedPayload, q as ErrorMessage, H as HandshakeMessage, r as HandshakeResponseMessage, I as InvalidScopeError, O as OperationBatchMessage, s as SYNC_STATES, t as SYNC_STATUSES, u as ScopeViolationError, v as SyncEncryptionAlgorithm, w as SyncScopeContext, x as SyncStatus, y as dedupeQuerySubsets, z as isAcknowledgmentMessage, C as isAwarenessUpdateMessage, F as isBlobChunkPushMessage, G as isBlobChunkRequestMessage, J as isBlobChunkResponseMessage, K as isErrorMessage, L as isHandshakeMessage, M as isHandshakeResponseMessage, N as isOperationBatchMessage, P as isSyncMessage, R as isYjsDocUpdateMessage, U as operationMatchesQuerySubsets } from './transport-CZLMdZ0k.cjs';
3
3
  import * as _korajs_core from '@korajs/core';
4
4
  import { Operation, VersionVector, ApplyResult, KoraEventEmitter, TimeSource, SyncError, ConnectionQuality } from '@korajs/core';
5
5
  export { ApplyFailureReason, ApplyResult } from '@korajs/core';
@@ -470,6 +470,71 @@ declare class AwarenessManager {
470
470
  private emitAwarenessEvent;
471
471
  }
472
472
 
473
+ /**
474
+ * A blob-chunk message in decoded form (chunk bytes as a `Uint8Array` rather
475
+ * than the base64 wire string). This is the shape callers of the channel work
476
+ * with; the channel handles base64 encode/decode at the wire boundary.
477
+ *
478
+ * The shape mirrors `@korajs/store`'s `ChunkMessage` so an app-layer adapter can
479
+ * bridge the two with no field translation.
480
+ */
481
+ type BlobChunkChannelMessage = {
482
+ type: 'blob-chunk-request';
483
+ requestId: string;
484
+ hash: string;
485
+ } | {
486
+ type: 'blob-chunk-response';
487
+ requestId: string;
488
+ bytes: Uint8Array | null;
489
+ } | {
490
+ type: 'blob-chunk-push';
491
+ hash: string;
492
+ bytes: Uint8Array;
493
+ };
494
+ /** Encode chunk bytes to a base64 string for the JSON wire. */
495
+ declare function encodeBlobChunkBytes(bytes: Uint8Array): string;
496
+ /** Decode base64 chunk bytes from the wire back to a `Uint8Array`. */
497
+ declare function decodeBlobChunkBytes(encoded: string): Uint8Array;
498
+ interface BlobChunkChannelOptions {
499
+ /**
500
+ * Called when a local blob-chunk message should go out on the wire. The
501
+ * caller (the sync engine) decides whether the connection is ready to send.
502
+ */
503
+ onSend?: (message: BlobChunkRequestMessage | BlobChunkResponseMessage | BlobChunkPushMessage) => void;
504
+ }
505
+ /**
506
+ * Ephemeral side channel for out-of-band blob chunk transfer over the sync
507
+ * connection. Symmetric: a peer can both request chunks it needs and answer
508
+ * requests for chunks it holds. Never persisted in the operation log — durable
509
+ * state is the `BlobRef` inside a record's fields; this channel only moves the
510
+ * referenced bytes.
511
+ *
512
+ * The channel owns the base64 wire encoding so callers work purely in
513
+ * `Uint8Array`, and its {@link BlobChunkChannelMessage} shape mirrors
514
+ * `@korajs/store`'s `ChunkMessage` so the app layer can expose it as a
515
+ * `ChunkMessagePort` without translating fields.
516
+ */
517
+ declare class BlobChunkChannel {
518
+ private readonly onSend;
519
+ private readonly handlers;
520
+ constructor(options?: BlobChunkChannelOptions);
521
+ /**
522
+ * Send a blob-chunk message to the peer. Response bytes are base64-encoded
523
+ * onto the wire here; callers pass a `Uint8Array`.
524
+ */
525
+ send(message: BlobChunkChannelMessage): void;
526
+ /**
527
+ * Register a handler for inbound blob-chunk messages. Multiple handlers are
528
+ * allowed; each receives every delivered message.
529
+ */
530
+ onMessage(handler: (message: BlobChunkChannelMessage) => void): () => void;
531
+ /**
532
+ * Deliver an inbound wire message to registered handlers, decoding response
533
+ * bytes from base64 back to a `Uint8Array` first.
534
+ */
535
+ deliver(message: BlobChunkRequestMessage | BlobChunkResponseMessage | BlobChunkPushMessage): void;
536
+ }
537
+
473
538
  /**
474
539
  * Configuration for the SyncMetricsCollector.
475
540
  */
@@ -850,6 +915,7 @@ declare class SyncEngine {
850
915
  private readonly encryptor;
851
916
  private readonly awarenessManager;
852
917
  private readonly richtextDocChannel;
918
+ private readonly blobChunkChannel;
853
919
  private readonly metricsCollector;
854
920
  private readonly syncState;
855
921
  private remoteVector;
@@ -864,6 +930,7 @@ declare class SyncEngine {
864
930
  private schemaBlocked;
865
931
  private clockBlocked;
866
932
  private clockSkewMs;
933
+ private blobStorageEnabled;
867
934
  private deltaBatchesReceived;
868
935
  private deltaReceiveComplete;
869
936
  private deltaSendComplete;
@@ -998,6 +1065,22 @@ declare class SyncEngine {
998
1065
  * Optional side channel for incremental Yjs updates on large richtext fields.
999
1066
  */
1000
1067
  getRichtextDocChannel(): RichtextDocChannel;
1068
+ /**
1069
+ * Side channel for out-of-band blob chunk transfer over the sync connection.
1070
+ * The app layer bridges this to a `ChunkMessagePort` to pull/serve blob bytes.
1071
+ */
1072
+ getBlobChunkChannel(): BlobChunkChannel;
1073
+ /**
1074
+ * Whether the connected server persists blob bytes centrally (learned from the
1075
+ * handshake response). When true, the app uploads the bytes behind `blob`
1076
+ * fields so they stay available after the authoring device goes offline.
1077
+ */
1078
+ isBlobStorageEnabled(): boolean;
1079
+ /**
1080
+ * Upload a blob chunk (or manifest) to the server for central persistence.
1081
+ * A no-op unless connected and the server advertised blob storage.
1082
+ */
1083
+ uploadBlobChunk(hash: string, bytes: Uint8Array): void;
1001
1084
  private messageChain;
1002
1085
  private enqueueMessage;
1003
1086
  private handleMessageAsync;
@@ -1292,4 +1375,4 @@ declare function getRemoteAwarenessStates(awareness: AwarenessManager): Awarenes
1292
1375
  */
1293
1376
  declare function subscribeRemoteAwarenessStates(awareness: AwarenessManager, listener: (states: AwarenessState[]) => void): () => void;
1294
1377
 
1295
- export { type AwarenessChange, type AwarenessCursor, AwarenessManager, type AwarenessMessage, type AwarenessState, type AwarenessUser, type ChaosConfig, ChaosTransport, ConnectionMonitor, type ConnectionMonitorConfig, type CursorInfo, DEFAULT_RICHTEXT_DOC_CHANNEL_THRESHOLD, DecryptionError, DeltaCursor, EncryptionError, HttpLongPollingTransport, type HttpLongPollingTransportOptions, JsonMessageSerializer, KeyDerivationError, type MessageSerializer, NegotiatedMessageSerializer, OFFLINE_SYNC_STATUS, type OutboundBatch, OutboundQueue, ProtobufMessageSerializer, QueueStorage, type ReconnectionConfig, ReconnectionManager, RichtextDocChannel, type RichtextDocChannelOptions, type RichtextDocListener, SCHEMA_MISMATCH_PREFIX, SerializedOperation, SyncConfig, type SyncDiagnostics, SyncEncryptionConfig, SyncEncryptor, SyncEngine, type SyncEngineOptions, SyncMessage, SyncQuerySubset, SyncScopeMap, SyncState, SyncStatePersistence, type SyncStatusController, type SyncStatusControllerOptions, SyncStatusInfo, type SyncStore, SyncTransport, TransportCloseHandler, TransportErrorHandler, TransportMessageHandler, TransportOptions, VersionedKey, type WebSocketConstructor, type WebSocketLike, WebSocketTransport, type WebSocketTransportOptions, WireFormat, YjsDocUpdateMessage, createDeltaCursorFromBatch, createSyncStatusController, decodeDeltaCursor, decodeYjsUpdate, deriveKey, deriveVersionedKey, encodeDeltaCursor, encodeYjsUpdate, filterOperationsByScope, generateSalt, getRemoteAwarenessStates, isClientSchemaVersionSupported, isEncryptedPayload, isSchemaMismatchReject, operationMatchesScope, richtextDocKey, sliceOperationsAfterCursor, subscribeRemoteAwarenessStates, versionVectorToWire, wireToVersionVector };
1378
+ export { type AwarenessChange, type AwarenessCursor, AwarenessManager, type AwarenessMessage, type AwarenessState, type AwarenessUser, BlobChunkChannel, type BlobChunkChannelMessage, type BlobChunkChannelOptions, BlobChunkPushMessage, BlobChunkRequestMessage, BlobChunkResponseMessage, type ChaosConfig, ChaosTransport, ConnectionMonitor, type ConnectionMonitorConfig, type CursorInfo, DEFAULT_RICHTEXT_DOC_CHANNEL_THRESHOLD, DecryptionError, DeltaCursor, EncryptionError, HttpLongPollingTransport, type HttpLongPollingTransportOptions, JsonMessageSerializer, KeyDerivationError, type MessageSerializer, NegotiatedMessageSerializer, OFFLINE_SYNC_STATUS, type OutboundBatch, OutboundQueue, ProtobufMessageSerializer, QueueStorage, type ReconnectionConfig, ReconnectionManager, RichtextDocChannel, type RichtextDocChannelOptions, type RichtextDocListener, SCHEMA_MISMATCH_PREFIX, SerializedOperation, SyncConfig, type SyncDiagnostics, SyncEncryptionConfig, SyncEncryptor, SyncEngine, type SyncEngineOptions, SyncMessage, SyncQuerySubset, SyncScopeMap, SyncState, SyncStatePersistence, type SyncStatusController, type SyncStatusControllerOptions, SyncStatusInfo, type SyncStore, SyncTransport, TransportCloseHandler, TransportErrorHandler, TransportMessageHandler, TransportOptions, VersionedKey, type WebSocketConstructor, type WebSocketLike, WebSocketTransport, type WebSocketTransportOptions, WireFormat, YjsDocUpdateMessage, createDeltaCursorFromBatch, createSyncStatusController, decodeBlobChunkBytes, decodeDeltaCursor, decodeYjsUpdate, deriveKey, deriveVersionedKey, encodeBlobChunkBytes, encodeDeltaCursor, encodeYjsUpdate, filterOperationsByScope, generateSalt, getRemoteAwarenessStates, isClientSchemaVersionSupported, isEncryptedPayload, isSchemaMismatchReject, operationMatchesScope, richtextDocKey, sliceOperationsAfterCursor, subscribeRemoteAwarenessStates, versionVectorToWire, wireToVersionVector };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SyncScopeMap, D as DeltaCursor, a as SyncMessage, b as SerializedOperation, W as WireFormat, c as SyncTransport, T as TransportOptions, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, g as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, h as SyncState, i as SyncStatusInfo, j as SyncConfig, k as SyncStatePersistence, l as SyncQuerySubset } from './transport-BMesMB6s.js';
2
- export { A as AcknowledgmentMessage, m as AwarenessStateWire, n as AwarenessUpdateMessage, E as EncryptedPayload, o as ErrorMessage, H as HandshakeMessage, p as HandshakeResponseMessage, I as InvalidScopeError, O as OperationBatchMessage, q as SYNC_STATES, r as SYNC_STATUSES, s as ScopeViolationError, t as SyncEncryptionAlgorithm, u as SyncScopeContext, v as SyncStatus, w as dedupeQuerySubsets, x as isAcknowledgmentMessage, y as isAwarenessUpdateMessage, z as isErrorMessage, B as isHandshakeMessage, C as isHandshakeResponseMessage, F as isOperationBatchMessage, G as isSyncMessage, J as isYjsDocUpdateMessage, K as operationMatchesQuerySubsets } from './transport-BMesMB6s.js';
1
+ import { S as SyncScopeMap, D as DeltaCursor, a as SyncMessage, b as SerializedOperation, W as WireFormat, c as SyncTransport, T as TransportOptions, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, B as BlobChunkRequestMessage, g as BlobChunkResponseMessage, h as BlobChunkPushMessage, i as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, j as SyncState, k as SyncStatusInfo, l as SyncConfig, m as SyncStatePersistence, n as SyncQuerySubset } from './transport-CZLMdZ0k.js';
2
+ export { A as AcknowledgmentMessage, o as AwarenessStateWire, p as AwarenessUpdateMessage, E as EncryptedPayload, q as ErrorMessage, H as HandshakeMessage, r as HandshakeResponseMessage, I as InvalidScopeError, O as OperationBatchMessage, s as SYNC_STATES, t as SYNC_STATUSES, u as ScopeViolationError, v as SyncEncryptionAlgorithm, w as SyncScopeContext, x as SyncStatus, y as dedupeQuerySubsets, z as isAcknowledgmentMessage, C as isAwarenessUpdateMessage, F as isBlobChunkPushMessage, G as isBlobChunkRequestMessage, J as isBlobChunkResponseMessage, K as isErrorMessage, L as isHandshakeMessage, M as isHandshakeResponseMessage, N as isOperationBatchMessage, P as isSyncMessage, R as isYjsDocUpdateMessage, U as operationMatchesQuerySubsets } from './transport-CZLMdZ0k.js';
3
3
  import * as _korajs_core from '@korajs/core';
4
4
  import { Operation, VersionVector, ApplyResult, KoraEventEmitter, TimeSource, SyncError, ConnectionQuality } from '@korajs/core';
5
5
  export { ApplyFailureReason, ApplyResult } from '@korajs/core';
@@ -470,6 +470,71 @@ declare class AwarenessManager {
470
470
  private emitAwarenessEvent;
471
471
  }
472
472
 
473
+ /**
474
+ * A blob-chunk message in decoded form (chunk bytes as a `Uint8Array` rather
475
+ * than the base64 wire string). This is the shape callers of the channel work
476
+ * with; the channel handles base64 encode/decode at the wire boundary.
477
+ *
478
+ * The shape mirrors `@korajs/store`'s `ChunkMessage` so an app-layer adapter can
479
+ * bridge the two with no field translation.
480
+ */
481
+ type BlobChunkChannelMessage = {
482
+ type: 'blob-chunk-request';
483
+ requestId: string;
484
+ hash: string;
485
+ } | {
486
+ type: 'blob-chunk-response';
487
+ requestId: string;
488
+ bytes: Uint8Array | null;
489
+ } | {
490
+ type: 'blob-chunk-push';
491
+ hash: string;
492
+ bytes: Uint8Array;
493
+ };
494
+ /** Encode chunk bytes to a base64 string for the JSON wire. */
495
+ declare function encodeBlobChunkBytes(bytes: Uint8Array): string;
496
+ /** Decode base64 chunk bytes from the wire back to a `Uint8Array`. */
497
+ declare function decodeBlobChunkBytes(encoded: string): Uint8Array;
498
+ interface BlobChunkChannelOptions {
499
+ /**
500
+ * Called when a local blob-chunk message should go out on the wire. The
501
+ * caller (the sync engine) decides whether the connection is ready to send.
502
+ */
503
+ onSend?: (message: BlobChunkRequestMessage | BlobChunkResponseMessage | BlobChunkPushMessage) => void;
504
+ }
505
+ /**
506
+ * Ephemeral side channel for out-of-band blob chunk transfer over the sync
507
+ * connection. Symmetric: a peer can both request chunks it needs and answer
508
+ * requests for chunks it holds. Never persisted in the operation log — durable
509
+ * state is the `BlobRef` inside a record's fields; this channel only moves the
510
+ * referenced bytes.
511
+ *
512
+ * The channel owns the base64 wire encoding so callers work purely in
513
+ * `Uint8Array`, and its {@link BlobChunkChannelMessage} shape mirrors
514
+ * `@korajs/store`'s `ChunkMessage` so the app layer can expose it as a
515
+ * `ChunkMessagePort` without translating fields.
516
+ */
517
+ declare class BlobChunkChannel {
518
+ private readonly onSend;
519
+ private readonly handlers;
520
+ constructor(options?: BlobChunkChannelOptions);
521
+ /**
522
+ * Send a blob-chunk message to the peer. Response bytes are base64-encoded
523
+ * onto the wire here; callers pass a `Uint8Array`.
524
+ */
525
+ send(message: BlobChunkChannelMessage): void;
526
+ /**
527
+ * Register a handler for inbound blob-chunk messages. Multiple handlers are
528
+ * allowed; each receives every delivered message.
529
+ */
530
+ onMessage(handler: (message: BlobChunkChannelMessage) => void): () => void;
531
+ /**
532
+ * Deliver an inbound wire message to registered handlers, decoding response
533
+ * bytes from base64 back to a `Uint8Array` first.
534
+ */
535
+ deliver(message: BlobChunkRequestMessage | BlobChunkResponseMessage | BlobChunkPushMessage): void;
536
+ }
537
+
473
538
  /**
474
539
  * Configuration for the SyncMetricsCollector.
475
540
  */
@@ -850,6 +915,7 @@ declare class SyncEngine {
850
915
  private readonly encryptor;
851
916
  private readonly awarenessManager;
852
917
  private readonly richtextDocChannel;
918
+ private readonly blobChunkChannel;
853
919
  private readonly metricsCollector;
854
920
  private readonly syncState;
855
921
  private remoteVector;
@@ -864,6 +930,7 @@ declare class SyncEngine {
864
930
  private schemaBlocked;
865
931
  private clockBlocked;
866
932
  private clockSkewMs;
933
+ private blobStorageEnabled;
867
934
  private deltaBatchesReceived;
868
935
  private deltaReceiveComplete;
869
936
  private deltaSendComplete;
@@ -998,6 +1065,22 @@ declare class SyncEngine {
998
1065
  * Optional side channel for incremental Yjs updates on large richtext fields.
999
1066
  */
1000
1067
  getRichtextDocChannel(): RichtextDocChannel;
1068
+ /**
1069
+ * Side channel for out-of-band blob chunk transfer over the sync connection.
1070
+ * The app layer bridges this to a `ChunkMessagePort` to pull/serve blob bytes.
1071
+ */
1072
+ getBlobChunkChannel(): BlobChunkChannel;
1073
+ /**
1074
+ * Whether the connected server persists blob bytes centrally (learned from the
1075
+ * handshake response). When true, the app uploads the bytes behind `blob`
1076
+ * fields so they stay available after the authoring device goes offline.
1077
+ */
1078
+ isBlobStorageEnabled(): boolean;
1079
+ /**
1080
+ * Upload a blob chunk (or manifest) to the server for central persistence.
1081
+ * A no-op unless connected and the server advertised blob storage.
1082
+ */
1083
+ uploadBlobChunk(hash: string, bytes: Uint8Array): void;
1001
1084
  private messageChain;
1002
1085
  private enqueueMessage;
1003
1086
  private handleMessageAsync;
@@ -1292,4 +1375,4 @@ declare function getRemoteAwarenessStates(awareness: AwarenessManager): Awarenes
1292
1375
  */
1293
1376
  declare function subscribeRemoteAwarenessStates(awareness: AwarenessManager, listener: (states: AwarenessState[]) => void): () => void;
1294
1377
 
1295
- export { type AwarenessChange, type AwarenessCursor, AwarenessManager, type AwarenessMessage, type AwarenessState, type AwarenessUser, type ChaosConfig, ChaosTransport, ConnectionMonitor, type ConnectionMonitorConfig, type CursorInfo, DEFAULT_RICHTEXT_DOC_CHANNEL_THRESHOLD, DecryptionError, DeltaCursor, EncryptionError, HttpLongPollingTransport, type HttpLongPollingTransportOptions, JsonMessageSerializer, KeyDerivationError, type MessageSerializer, NegotiatedMessageSerializer, OFFLINE_SYNC_STATUS, type OutboundBatch, OutboundQueue, ProtobufMessageSerializer, QueueStorage, type ReconnectionConfig, ReconnectionManager, RichtextDocChannel, type RichtextDocChannelOptions, type RichtextDocListener, SCHEMA_MISMATCH_PREFIX, SerializedOperation, SyncConfig, type SyncDiagnostics, SyncEncryptionConfig, SyncEncryptor, SyncEngine, type SyncEngineOptions, SyncMessage, SyncQuerySubset, SyncScopeMap, SyncState, SyncStatePersistence, type SyncStatusController, type SyncStatusControllerOptions, SyncStatusInfo, type SyncStore, SyncTransport, TransportCloseHandler, TransportErrorHandler, TransportMessageHandler, TransportOptions, VersionedKey, type WebSocketConstructor, type WebSocketLike, WebSocketTransport, type WebSocketTransportOptions, WireFormat, YjsDocUpdateMessage, createDeltaCursorFromBatch, createSyncStatusController, decodeDeltaCursor, decodeYjsUpdate, deriveKey, deriveVersionedKey, encodeDeltaCursor, encodeYjsUpdate, filterOperationsByScope, generateSalt, getRemoteAwarenessStates, isClientSchemaVersionSupported, isEncryptedPayload, isSchemaMismatchReject, operationMatchesScope, richtextDocKey, sliceOperationsAfterCursor, subscribeRemoteAwarenessStates, versionVectorToWire, wireToVersionVector };
1378
+ export { type AwarenessChange, type AwarenessCursor, AwarenessManager, type AwarenessMessage, type AwarenessState, type AwarenessUser, BlobChunkChannel, type BlobChunkChannelMessage, type BlobChunkChannelOptions, BlobChunkPushMessage, BlobChunkRequestMessage, BlobChunkResponseMessage, type ChaosConfig, ChaosTransport, ConnectionMonitor, type ConnectionMonitorConfig, type CursorInfo, DEFAULT_RICHTEXT_DOC_CHANNEL_THRESHOLD, DecryptionError, DeltaCursor, EncryptionError, HttpLongPollingTransport, type HttpLongPollingTransportOptions, JsonMessageSerializer, KeyDerivationError, type MessageSerializer, NegotiatedMessageSerializer, OFFLINE_SYNC_STATUS, type OutboundBatch, OutboundQueue, ProtobufMessageSerializer, QueueStorage, type ReconnectionConfig, ReconnectionManager, RichtextDocChannel, type RichtextDocChannelOptions, type RichtextDocListener, SCHEMA_MISMATCH_PREFIX, SerializedOperation, SyncConfig, type SyncDiagnostics, SyncEncryptionConfig, SyncEncryptor, SyncEngine, type SyncEngineOptions, SyncMessage, SyncQuerySubset, SyncScopeMap, SyncState, SyncStatePersistence, type SyncStatusController, type SyncStatusControllerOptions, SyncStatusInfo, type SyncStore, SyncTransport, TransportCloseHandler, TransportErrorHandler, TransportMessageHandler, TransportOptions, VersionedKey, type WebSocketConstructor, type WebSocketLike, WebSocketTransport, type WebSocketTransportOptions, WireFormat, YjsDocUpdateMessage, createDeltaCursorFromBatch, createSyncStatusController, decodeBlobChunkBytes, decodeDeltaCursor, decodeYjsUpdate, deriveKey, deriveVersionedKey, encodeBlobChunkBytes, encodeDeltaCursor, encodeYjsUpdate, filterOperationsByScope, generateSalt, getRemoteAwarenessStates, isClientSchemaVersionSupported, isEncryptedPayload, isSchemaMismatchReject, operationMatchesScope, richtextDocKey, sliceOperationsAfterCursor, subscribeRemoteAwarenessStates, versionVectorToWire, wireToVersionVector };
package/dist/index.js CHANGED
@@ -222,6 +222,12 @@ function isSyncMessage(value) {
222
222
  return isAwarenessUpdateMessage(value);
223
223
  case "yjs-doc-update":
224
224
  return isYjsDocUpdateMessage(value);
225
+ case "blob-chunk-request":
226
+ return isBlobChunkRequestMessage(value);
227
+ case "blob-chunk-response":
228
+ return isBlobChunkResponseMessage(value);
229
+ case "blob-chunk-push":
230
+ return isBlobChunkPushMessage(value);
225
231
  default:
226
232
  return false;
227
233
  }
@@ -261,6 +267,21 @@ function isYjsDocUpdateMessage(value) {
261
267
  const msg = value;
262
268
  return msg.type === "yjs-doc-update" && typeof msg.messageId === "string" && typeof msg.collection === "string" && typeof msg.recordId === "string" && typeof msg.field === "string" && typeof msg.update === "string";
263
269
  }
270
+ function isBlobChunkRequestMessage(value) {
271
+ if (typeof value !== "object" || value === null) return false;
272
+ const msg = value;
273
+ return msg.type === "blob-chunk-request" && typeof msg.messageId === "string" && typeof msg.requestId === "string" && typeof msg.hash === "string";
274
+ }
275
+ function isBlobChunkResponseMessage(value) {
276
+ if (typeof value !== "object" || value === null) return false;
277
+ const msg = value;
278
+ return msg.type === "blob-chunk-response" && typeof msg.messageId === "string" && typeof msg.requestId === "string" && (typeof msg.bytes === "string" || msg.bytes === null);
279
+ }
280
+ function isBlobChunkPushMessage(value) {
281
+ if (typeof value !== "object" || value === null) return false;
282
+ const msg = value;
283
+ return msg.type === "blob-chunk-push" && typeof msg.messageId === "string" && typeof msg.hash === "string" && typeof msg.bytes === "string";
284
+ }
264
285
 
265
286
  // src/protocol/serializer.ts
266
287
  import { SyncError } from "@korajs/core";
@@ -501,7 +522,8 @@ function toProtoEnvelope(message) {
501
522
  accepted: message.accepted,
502
523
  rejectReason: message.rejectReason,
503
524
  selectedWireFormat: message.selectedWireFormat,
504
- serverTime: message.serverTime
525
+ serverTime: message.serverTime,
526
+ blobStorageEnabled: message.blobStorageEnabled
505
527
  };
506
528
  case "operation-batch":
507
529
  return {
@@ -532,6 +554,28 @@ function toProtoEnvelope(message) {
532
554
  type: message.type,
533
555
  messageId: message.messageId
534
556
  };
557
+ case "blob-chunk-request":
558
+ return {
559
+ type: message.type,
560
+ messageId: message.messageId,
561
+ requestId: message.requestId,
562
+ hash: message.hash
563
+ };
564
+ case "blob-chunk-response":
565
+ return {
566
+ type: message.type,
567
+ messageId: message.messageId,
568
+ requestId: message.requestId,
569
+ hasBytes: message.bytes !== null,
570
+ ...message.bytes !== null ? { chunkBytes: message.bytes } : {}
571
+ };
572
+ case "blob-chunk-push":
573
+ return {
574
+ type: message.type,
575
+ messageId: message.messageId,
576
+ hash: message.hash,
577
+ chunkBytes: message.bytes
578
+ };
535
579
  }
536
580
  }
537
581
  function fromProtoEnvelope(envelope) {
@@ -564,7 +608,8 @@ function fromProtoEnvelope(envelope) {
564
608
  selectedWireFormat: envelope.selectedWireFormat === "json" || envelope.selectedWireFormat === "protobuf" ? envelope.selectedWireFormat : void 0,
565
609
  // Preserve the server's wall-clock time so clock-skew detection and
566
610
  // automatic timestamp rebase work over the protobuf wire, not just JSON.
567
- ...envelope.serverTime !== void 0 ? { serverTime: envelope.serverTime } : {}
611
+ ...envelope.serverTime !== void 0 ? { serverTime: envelope.serverTime } : {},
612
+ ...envelope.blobStorageEnabled !== void 0 ? { blobStorageEnabled: envelope.blobStorageEnabled } : {}
568
613
  };
569
614
  case "operation-batch":
570
615
  return {
@@ -589,6 +634,27 @@ function fromProtoEnvelope(envelope) {
589
634
  message: envelope.errorMessage ?? "Unknown error",
590
635
  retriable: envelope.retriable ?? false
591
636
  };
637
+ case "blob-chunk-request":
638
+ return {
639
+ type: "blob-chunk-request",
640
+ messageId: envelope.messageId,
641
+ requestId: envelope.requestId ?? "",
642
+ hash: envelope.hash ?? ""
643
+ };
644
+ case "blob-chunk-response":
645
+ return {
646
+ type: "blob-chunk-response",
647
+ messageId: envelope.messageId,
648
+ requestId: envelope.requestId ?? "",
649
+ bytes: envelope.hasBytes ? envelope.chunkBytes ?? "" : null
650
+ };
651
+ case "blob-chunk-push":
652
+ return {
653
+ type: "blob-chunk-push",
654
+ messageId: envelope.messageId,
655
+ hash: envelope.hash ?? "",
656
+ bytes: envelope.chunkBytes ?? ""
657
+ };
592
658
  default:
593
659
  throw new SyncError("Failed to decode sync message: unknown protobuf type", {
594
660
  type: envelope.type
@@ -734,6 +800,14 @@ function encodeEnvelope(envelope) {
734
800
  }
735
801
  if (envelope.retriable !== void 0) writer.uint32(144).bool(envelope.retriable);
736
802
  if (envelope.serverTime !== void 0) writer.uint32(152).int64(envelope.serverTime);
803
+ if (envelope.requestId && envelope.requestId.length > 0)
804
+ writer.uint32(162).string(envelope.requestId);
805
+ if (envelope.hash && envelope.hash.length > 0) writer.uint32(170).string(envelope.hash);
806
+ if (envelope.chunkBytes && envelope.chunkBytes.length > 0)
807
+ writer.uint32(178).string(envelope.chunkBytes);
808
+ if (envelope.hasBytes !== void 0) writer.uint32(184).bool(envelope.hasBytes);
809
+ if (envelope.blobStorageEnabled !== void 0)
810
+ writer.uint32(200).bool(envelope.blobStorageEnabled);
737
811
  return writer.finish();
738
812
  }
739
813
  function decodeEnvelope(bytes) {
@@ -805,6 +879,21 @@ function decodeEnvelope(bytes) {
805
879
  case 19:
806
880
  envelope.serverTime = longToNumber(reader.int64());
807
881
  break;
882
+ case 20:
883
+ envelope.requestId = reader.string();
884
+ break;
885
+ case 21:
886
+ envelope.hash = reader.string();
887
+ break;
888
+ case 22:
889
+ envelope.chunkBytes = reader.string();
890
+ break;
891
+ case 23:
892
+ envelope.hasBytes = reader.bool();
893
+ break;
894
+ case 25:
895
+ envelope.blobStorageEnabled = reader.bool();
896
+ break;
808
897
  default:
809
898
  reader.skipType(tag & 7);
810
899
  }
@@ -1612,6 +1701,95 @@ var AwarenessManager = class {
1612
1701
  }
1613
1702
  };
1614
1703
 
1704
+ // src/blob/blob-chunk-channel.ts
1705
+ import { generateUUIDv7 } from "@korajs/core";
1706
+ function encodeBlobChunkBytes(bytes) {
1707
+ let binary = "";
1708
+ for (let i = 0; i < bytes.length; i++) {
1709
+ binary += String.fromCharCode(bytes[i]);
1710
+ }
1711
+ return btoa(binary);
1712
+ }
1713
+ function decodeBlobChunkBytes(encoded) {
1714
+ const binary = atob(encoded);
1715
+ const out = new Uint8Array(binary.length);
1716
+ for (let i = 0; i < binary.length; i++) {
1717
+ out[i] = binary.charCodeAt(i);
1718
+ }
1719
+ return out;
1720
+ }
1721
+ var BlobChunkChannel = class {
1722
+ onSend;
1723
+ handlers = /* @__PURE__ */ new Set();
1724
+ constructor(options) {
1725
+ this.onSend = options?.onSend ?? null;
1726
+ }
1727
+ /**
1728
+ * Send a blob-chunk message to the peer. Response bytes are base64-encoded
1729
+ * onto the wire here; callers pass a `Uint8Array`.
1730
+ */
1731
+ send(message) {
1732
+ if (!this.onSend) {
1733
+ return;
1734
+ }
1735
+ if (message.type === "blob-chunk-request") {
1736
+ this.onSend({
1737
+ type: "blob-chunk-request",
1738
+ messageId: generateUUIDv7(),
1739
+ requestId: message.requestId,
1740
+ hash: message.hash
1741
+ });
1742
+ return;
1743
+ }
1744
+ if (message.type === "blob-chunk-push") {
1745
+ this.onSend({
1746
+ type: "blob-chunk-push",
1747
+ messageId: generateUUIDv7(),
1748
+ hash: message.hash,
1749
+ bytes: encodeBlobChunkBytes(message.bytes)
1750
+ });
1751
+ return;
1752
+ }
1753
+ this.onSend({
1754
+ type: "blob-chunk-response",
1755
+ messageId: generateUUIDv7(),
1756
+ requestId: message.requestId,
1757
+ bytes: message.bytes === null ? null : encodeBlobChunkBytes(message.bytes)
1758
+ });
1759
+ }
1760
+ /**
1761
+ * Register a handler for inbound blob-chunk messages. Multiple handlers are
1762
+ * allowed; each receives every delivered message.
1763
+ */
1764
+ onMessage(handler) {
1765
+ this.handlers.add(handler);
1766
+ return () => {
1767
+ this.handlers.delete(handler);
1768
+ };
1769
+ }
1770
+ /**
1771
+ * Deliver an inbound wire message to registered handlers, decoding response
1772
+ * bytes from base64 back to a `Uint8Array` first.
1773
+ */
1774
+ deliver(message) {
1775
+ if (this.handlers.size === 0) {
1776
+ return;
1777
+ }
1778
+ const decoded = message.type === "blob-chunk-request" ? { type: "blob-chunk-request", requestId: message.requestId, hash: message.hash } : message.type === "blob-chunk-push" ? {
1779
+ type: "blob-chunk-push",
1780
+ hash: message.hash,
1781
+ bytes: decodeBlobChunkBytes(message.bytes)
1782
+ } : {
1783
+ type: "blob-chunk-response",
1784
+ requestId: message.requestId,
1785
+ bytes: message.bytes === null ? null : decodeBlobChunkBytes(message.bytes)
1786
+ };
1787
+ for (const handler of this.handlers) {
1788
+ handler(decoded);
1789
+ }
1790
+ }
1791
+ };
1792
+
1615
1793
  // src/diagnostics/bandwidth-estimator.ts
1616
1794
  var BandwidthEstimator = class {
1617
1795
  maxSamples;
@@ -2055,7 +2233,7 @@ var SyncMetricsCollector = class {
2055
2233
  };
2056
2234
 
2057
2235
  // src/richtext/richtext-doc-channel.ts
2058
- import { generateUUIDv7 } from "@korajs/core";
2236
+ import { generateUUIDv7 as generateUUIDv72 } from "@korajs/core";
2059
2237
 
2060
2238
  // src/richtext/doc-channel-wire.ts
2061
2239
  function encodeYjsUpdate(update) {
@@ -2134,7 +2312,7 @@ var RichtextDocChannel = class {
2134
2312
  }
2135
2313
  this.onSend({
2136
2314
  type: "yjs-doc-update",
2137
- messageId: generateUUIDv7(),
2315
+ messageId: generateUUIDv72(),
2138
2316
  collection,
2139
2317
  recordId,
2140
2318
  field,
@@ -2347,6 +2525,7 @@ var SyncEngine = class {
2347
2525
  encryptor;
2348
2526
  awarenessManager;
2349
2527
  richtextDocChannel;
2528
+ blobChunkChannel;
2350
2529
  metricsCollector;
2351
2530
  syncState;
2352
2531
  remoteVector = /* @__PURE__ */ new Map();
@@ -2361,6 +2540,7 @@ var SyncEngine = class {
2361
2540
  schemaBlocked = false;
2362
2541
  clockBlocked = false;
2363
2542
  clockSkewMs = null;
2543
+ blobStorageEnabled = false;
2364
2544
  // Track delta exchange state
2365
2545
  deltaBatchesReceived = 0;
2366
2546
  deltaReceiveComplete = false;
@@ -2409,6 +2589,14 @@ var SyncEngine = class {
2409
2589
  this.transport.send(message);
2410
2590
  }
2411
2591
  });
2592
+ this.blobChunkChannel = new BlobChunkChannel({
2593
+ onSend: (message) => {
2594
+ if (this.state !== "streaming") {
2595
+ return;
2596
+ }
2597
+ this.transport.send(message);
2598
+ }
2599
+ });
2412
2600
  this.awarenessManager.onSend((message) => {
2413
2601
  if (this.state !== "streaming") return;
2414
2602
  const wireMessage = {
@@ -2722,6 +2910,31 @@ var SyncEngine = class {
2722
2910
  getRichtextDocChannel() {
2723
2911
  return this.richtextDocChannel;
2724
2912
  }
2913
+ /**
2914
+ * Side channel for out-of-band blob chunk transfer over the sync connection.
2915
+ * The app layer bridges this to a `ChunkMessagePort` to pull/serve blob bytes.
2916
+ */
2917
+ getBlobChunkChannel() {
2918
+ return this.blobChunkChannel;
2919
+ }
2920
+ /**
2921
+ * Whether the connected server persists blob bytes centrally (learned from the
2922
+ * handshake response). When true, the app uploads the bytes behind `blob`
2923
+ * fields so they stay available after the authoring device goes offline.
2924
+ */
2925
+ isBlobStorageEnabled() {
2926
+ return this.blobStorageEnabled;
2927
+ }
2928
+ /**
2929
+ * Upload a blob chunk (or manifest) to the server for central persistence.
2930
+ * A no-op unless connected and the server advertised blob storage.
2931
+ */
2932
+ uploadBlobChunk(hash, bytes) {
2933
+ if (this.state !== "streaming" || !this.blobStorageEnabled) {
2934
+ return;
2935
+ }
2936
+ this.blobChunkChannel.send({ type: "blob-chunk-push", hash, bytes });
2937
+ }
2725
2938
  // --- Private methods ---
2726
2939
  messageChain = Promise.resolve();
2727
2940
  enqueueMessage(message) {
@@ -2747,6 +2960,11 @@ var SyncEngine = class {
2747
2960
  case "yjs-doc-update":
2748
2961
  this.richtextDocChannel.deliver(message);
2749
2962
  break;
2963
+ case "blob-chunk-request":
2964
+ case "blob-chunk-response":
2965
+ case "blob-chunk-push":
2966
+ this.blobChunkChannel.deliver(message);
2967
+ break;
2750
2968
  }
2751
2969
  }
2752
2970
  handleMessageFailure(error) {
@@ -2779,6 +2997,7 @@ var SyncEngine = class {
2779
2997
  }
2780
2998
  async handleHandshakeResponse(msg) {
2781
2999
  if (this.state !== "handshaking") return;
3000
+ this.blobStorageEnabled = msg.blobStorageEnabled === true;
2782
3001
  if (typeof msg.serverTime === "number") {
2783
3002
  this.evaluateClockSkew(msg.serverTime);
2784
3003
  if (this.clockBlocked) {
@@ -4126,6 +4345,7 @@ function subscribeRemoteAwarenessStates(awareness, listener) {
4126
4345
  }
4127
4346
  export {
4128
4347
  AwarenessManager,
4348
+ BlobChunkChannel,
4129
4349
  ChaosTransport,
4130
4350
  ConnectionMonitor,
4131
4351
  DEFAULT_RICHTEXT_DOC_CHANNEL_THRESHOLD,
@@ -4150,11 +4370,13 @@ export {
4150
4370
  WebSocketTransport,
4151
4371
  createDeltaCursorFromBatch,
4152
4372
  createSyncStatusController,
4373
+ decodeBlobChunkBytes,
4153
4374
  decodeDeltaCursor,
4154
4375
  decodeYjsUpdate,
4155
4376
  dedupeQuerySubsets,
4156
4377
  deriveKey,
4157
4378
  deriveVersionedKey,
4379
+ encodeBlobChunkBytes,
4158
4380
  encodeDeltaCursor,
4159
4381
  encodeYjsUpdate,
4160
4382
  filterOperationsByScope,
@@ -4162,6 +4384,9 @@ export {
4162
4384
  getRemoteAwarenessStates,
4163
4385
  isAcknowledgmentMessage,
4164
4386
  isAwarenessUpdateMessage,
4387
+ isBlobChunkPushMessage,
4388
+ isBlobChunkRequestMessage,
4389
+ isBlobChunkResponseMessage,
4165
4390
  isClientSchemaVersionSupported,
4166
4391
  isEncryptedPayload,
4167
4392
  isErrorMessage,