@mengine/medeo-client 2.0.1-alpha.0 → 2.0.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/README.md CHANGED
@@ -8,18 +8,6 @@ This package owns the shared `VideoDocument` contract, semantic editing layer, a
8
8
 
9
9
  `getState().loaded` means the first local load completed. Once loaded, `loadedFromLocal` reports whether that load imported a snapshot, including a valid empty document. It stays unchanged by later edits and remote updates. Document readiness does not depend on metadata or nonempty tracks.
10
10
 
11
- ## Local persistence
12
-
13
- Browser hosts can inject `IndexedDBDocStorage` through `localStorage`. An optional `initialSnapshot` merges a freshly fetched server snapshot into that storage before DocManager loads it; it never replaces locally saved, unconfirmed edits. The host can require a successful online fetch before constructing the session.
14
-
15
- `getLocalSaveState()` / `subscribeLocalSaveState()` report `{ status: 'saving' | 'saved' | 'failed', error }` for this session's local commits. This is separate from `getSyncState()` and server confirmation. The storage adapter determines durability: IndexedDB resolves only after its transaction commits; MemoryDocStorage remains in memory. Failed saves retain their causal order and retry without blocking other documents.
16
-
17
- `waitForLocalSave({ timeoutMs? })` waits for the commits present at invocation (default deadline: 15 seconds). Later edits do not extend that target. Timing out only stops the wait; persistence continues. This does not wait for server acknowledgement.
18
-
19
- For normal teardown, use `await session.close()`. It immediately rejects new edits and stops synchronization, then keeps local persistence alive until pending commits are saved before releasing connections. Storage failures remain retryable while closing. `destroy()` is immediate cancellation for exceptional teardown, including failed startup; it can discard unpersisted in-memory work. Neither API can guarantee a save after the browser process is forcibly terminated. Only completed local saves survive reopening.
20
-
21
- Reopening uses the stored Loro history and the server's version vector to derive missing updates, without replaying semantic operations or their external side effects. Undo history remains limited to the current session.
22
-
23
11
  ## Session undo/redo
24
12
 
25
13
  After `await session.start()`, `session.undo(options?)` and `session.redo(options?)` synchronously apply local history and return whether they produced a document update. `options` accepts the same actor/intent metadata as semantic edits. Read the resulting document through `session.snapshot()`; use `waitForServerAck()` only when durable confirmation is required.
@@ -39,3 +27,9 @@ The session assembles SemanticEditor with MirrorVideoDocumentAdapter directly. D
39
27
  Undo/redo append ordinary synchronized updates and retain audit metadata (`Undo`/`Redo`, actor, and `payload.source` identifying an original local change). Existing revision bindings remain immutable. Redo reuses already materialized assets without repeating generation, billing, or attachment requests. Network failures do not automatically reverse history.
40
28
 
41
29
  Loro history uses new writes: undo on a shared LWW field restores its value before the local edit, potentially overwriting a later remote write to the same field; redo restores the value visible before undo. Unrelated remote fields remain intact. Undoing a local insertion removes the object and makes dependent remote edits invisible until redo restores the object. Caption creation includes its style container, even when empty, so a later style edit only changes fields and undo preserves another peer's independently edited fields. Initial document seeding preserves supplied styles and creates empty maps for captions without styles. No write-time repair or migration of older CRDT documents is provided.
30
+
31
+ ## Large-update resynchronization
32
+
33
+ `MengineDocSession` handles SSE `event: resync` by scheduling the existing version-vector sync on the current connection. The event carries no Loro bytes and does not create an edit or undo step. A hint arriving while an HTTP sync is in flight schedules another diff so changes newer than that response are still fetched. Low-level users of `readMengineEventStream` can handle the same hint with `onResync`; `MedeoHttpDocStorage` exposes `subscribeResync`.
34
+
35
+ Deploy this client before enabling server-side resync notifications. Older clients ignore the event. Large HTTP pushes remain valid: the server chooses inline update or resync notification after persistence, rather than requiring client-side fragmentation. Base64 encoding uses `Uint8Array.toBase64()` where available, with a chunked fallback preserving the same padded wire representation.
@@ -3,9 +3,11 @@ import { z } from "zod";
3
3
  import { LoroDoc } from "loro-crdt";
4
4
  //#region src/client/base64.ts
5
5
  function bytesToBase64(bytes) {
6
- let binary = "";
7
- for (const byte of bytes) binary += String.fromCharCode(byte);
8
- return btoa(binary);
6
+ const native = bytes.toBase64;
7
+ if (typeof native === "function") return native.call(bytes);
8
+ const chunks = [];
9
+ for (let offset = 0; offset < bytes.length; offset += 8192) chunks.push(String.fromCharCode(...bytes.subarray(offset, offset + 8192)));
10
+ return btoa(chunks.join(""));
9
11
  }
10
12
  function base64ToBytes(base64) {
11
13
  const binary = atob(base64);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { $ as VideoDraft, A as assertValidVideoDocument, B as CaptionPart, C as index_d_exports, D as VideoDocumentMirrorSchema, E as VideoDocumentDraft, F as buildSpeechHostMap, G as Timeline, H as PartKind, I as derivePositionFromAbs, J as TrackItemTimePosition, K as Track, L as fromVideoDocument, M as buildInitialVideoDocument, N as DerivedItemPosition, O as videoDocumentMirrorSchema, P as SpeechHostMap, Q as VideoDocumentValidationIssueCode, R as toVideoDocument, S as ValidationError, T as TrackItemDraft, U as PartUnion, V as DEFAULT_UNIT_TIME_MS, W as SpeechPart, X as VideoDocument, Y as VideoClipPart, Z as VideoDocumentValidationIssue, _ as PlannedSemanticOpKind, a as MirrorVideoDocumentOptions, at as PartAggregation, b as SchemaValidator, c as CommitOptions, ct as Track$1, d as SemanticEditor, et as VideoDraftContent, f as SemanticOpInput, g as ImplementedSemanticOpKind, h as IMPLEMENTED_SEMANTIC_OP_KINDS, i as MirrorVideoDocumentAdapter, it as CaptionStyle, j as validateVideoDocument, k as VideoDocumentValidationError, l as OpActor, lt as TrackItem$1, m as TransactAudit, n as videoDocumentSchema, nt as Attachment, o as createMirrorVideoDocument, ot as SpeedShift, p as SemanticOpName, q as TrackItem, r as readVideoDocumentFromDraft, rt as CaptionPart$1, s as createMirrorVideoDocumentAdapter, st as Timeline$1, t as partUnionSchema, tt as VideoDraftPartUnion, u as SemanticDocumentAdapter, v as SemanticOpKind, w as TrackDraft, x as SnapshotReadable, y as isImplementedSemanticOpKind, z as BgmPart } from "./index-CnZ9l3rb.js";
2
2
  import { LoroDoc, PeerID } from "loro-crdt";
3
- import { DocState, LocalSaveState } from "@mengine/sync";
3
+ import { DocState } from "@mengine/sync";
4
4
  import { BaseDocStorage, Connection, DocDiff, DocPushReceipt, DocSnapshotRecord, DocStorage, DocStorageOptions, DocUpdate, DocUpdateRecord } from "@mengine/storage";
5
5
 
6
6
  //#region src/client/base64.d.ts
@@ -173,6 +173,8 @@ interface MengineEventStreamOptions {
173
173
  /** Fired once the stream response is established (HTTP ok, body readable). */
174
174
  onOpen?(): void;
175
175
  onUpdate(event: MengineSseUpdateEvent): void;
176
+ /** A committed update omitted from live transport; fetch the missing operations by VV. */
177
+ onResync?(): void;
176
178
  }
177
179
  declare function readMengineEventStream(options: MengineEventStreamOptions): Promise<void>;
178
180
  //#endregion
@@ -574,6 +576,8 @@ declare class MedeoHttpDocStorage implements DocStorage {
574
576
  * tool-level ack wait) subscribe here.
575
577
  */
576
578
  subscribePushOutcome(callback: (outcome: PushOutcome) => void): () => void;
579
+ /** Observe live hints that require VV catch-up without replacing the SSE connection. */
580
+ subscribeResync(callback: () => void): () => void;
577
581
  /** Authoritative HTTP reads and accepted pushes, never inferred from SSE bytes. */
578
582
  subscribeServerVersion(callback: (version: Uint8Array) => void): () => void;
579
583
  private reportServerVersion;
@@ -628,11 +632,6 @@ declare class MengineAckFailedError extends Error {
628
632
  readonly cause: Error | undefined;
629
633
  constructor(reason: 'timeout' | 'rejected' | 'failed', code: string | undefined, cause: Error | undefined, message: string);
630
634
  }
631
- type MengineLocalSaveState = LocalSaveState;
632
- interface WaitForLocalSaveOptions {
633
- /** Only limits waiting, never cancels persistence. */
634
- timeoutMs?: number;
635
- }
636
635
  interface WaitForServerAckOptions {
637
636
  /** Deadline in ms. Rejects with reason `timeout` when it elapses. */
638
637
  timeoutMs?: number;
@@ -654,9 +653,6 @@ interface MengineDocSessionOptions {
654
653
  * Browsers should pass an `IndexedDBDocStorage` for refresh/cross-tab support.
655
654
  */
656
655
  localStorage?: DocStorageLike;
657
- /** A freshly fetched remote snapshot to merge into storage before loading.
658
- * Existing local changes are retained; the host owns any online startup gate. */
659
- initialSnapshot?: Uint8Array;
660
656
  sseReconnectDelayMs?: number;
661
657
  }
662
658
  /**
@@ -693,7 +689,6 @@ declare class MengineDocSession {
693
689
  private started;
694
690
  private startTask;
695
691
  private destroyed;
696
- private closeTask;
697
692
  private lifetime;
698
693
  private hasConnected;
699
694
  private initialVersionValue;
@@ -787,15 +782,6 @@ declare class MengineDocSession {
787
782
  */
788
783
  start(): Promise<VideoDocument>;
789
784
  private open;
790
- getLocalSaveState(): MengineLocalSaveState;
791
- /** Immediately reports local transaction state; independent of server confirmation. */
792
- subscribeLocalSaveState(cb: (state: MengineLocalSaveState) => void): () => void;
793
- /** Waits for commits present now to reach local storage. Does not wait for the network. */
794
- waitForLocalSave(options?: WaitForLocalSaveOptions): Promise<void>;
795
- /** Stop accepting edits and drain local persistence before releasing resources.
796
- * A failed local write keeps retrying, even after the host has unmounted.
797
- * Hosts should use this for normal teardown; destroy() is immediate cancellation. */
798
- close(): Promise<void>;
799
785
  getSyncState(): MengineDocSyncState;
800
786
  private confirmationTarget;
801
787
  /** Immediately supplies current state; unsubscribe when the host changes docs. */
@@ -1133,4 +1119,4 @@ declare function hostForAbsMs(ranges: MainClipRange[], absMs: number): MainClipR
1133
1119
  */
1134
1120
  declare function relativePositionForAbs(ranges: MainClipRange[], absMs: number): TrackItemTimePosition;
1135
1121
  //#endregion
1136
- export { type Aggregation, type Attachment, type BgmPart, type CaptionPart, type CaptionStyle, type CommitOptions, DEFAULT_UNIT_TIME_MS, type DerivedItemPosition, type DocStorageLike, type DocVersionMark, IMPLEMENTED_SEMANTIC_OP_KINDS, type ImplementedSemanticOpKind, LANE_KINDS_IN_STACK_ORDER, type LaneKind, type MainClipRange, type MakeEmptyPart, ManualSyncDoc, type ManualSyncDocOptions, MedeoHttpDocStorage, type MedeoHttpDocStorageOptions, MemoryDocStorage, MengineAckFailedError, type MengineAuditEntry, type MengineAuditResponse, MengineDocSession, type MengineDocSessionOptions, type MengineDocSessionUpdateEvent, type MengineDocSyncState, type MengineDocumentVersion, type MengineEventStreamOptions, MengineHttpClient, type MengineHttpClientOptions, MengineHttpRequestError, type MengineLocalSaveState, MenginePushRejectedError, type MenginePushResponse, type MenginePushUpdateResponse, type MengineRejectedResponse, type MengineSnapshotResponse, type MengineSseUpdateEvent, type MengineSyncResponse, type MengineUndoState, type MengineUpdateMeta, MirrorVideoDocumentAdapter, type MirrorVideoDocumentOptions, type OpActor, type PartAggregation, type PartKind, type PartUnion, type PlannedSemanticOpKind, type PullFailureReason, type PullResult, type PushOutcome, type PushOutcomeKind, type PushResult, type PushResultKind, SchemaValidator, type SemanticDocumentAdapter, SemanticEditor, type SemanticOpInput, type SemanticOpKind, type SemanticOpName, type SnapshotReadable, type SolvedVideoDocument, type SpeechHostMap, type SpeechPart, type SpeedShift, TIMELINE_SKELETON_DURATION_MS, type Timeline, type TimelineDoc, type TimelineItem, type Track, type TrackDraft, type TrackItem, type TrackItemDraft, type TrackItemTimePosition, type TransactAudit, ValidationError, type VideoClipPart, type VideoDocument, type VideoDocumentDraft, type VideoDocumentMirrorSchema, VideoDocumentValidationError, type VideoDocumentValidationIssue, type VideoDocumentValidationIssueCode, type VideoDraft, type CaptionPart$1 as VideoDraftCaptionPart, type VideoDraftContent, type VideoDraftPartUnion, type Timeline$1 as VideoDraftTimeline, type Track$1 as VideoDraftTrack, type TrackItem$1 as VideoDraftTrackItem, type WaitForLocalSaveOptions, type WaitForServerAckOptions, arrangeMainTrackSeamlessly, assertValidVideoDocument, base64ToBytes, buildInitialVideoDocument, buildSpeechHostMap, bytesToBase64, cascadeAfterVideoClipChanges, createMirrorVideoDocument, createMirrorVideoDocumentAdapter, decodeDocVersionMark, derivePositionFromAbs, encodeDocVersionMark, ensureLaneTrack, fillMainTrackTimeGaps, findLaneTrack, fromVideoDocument, generatePartId, getAt, hostForAbsMs, isEmptyVideoClip, isImplementedSemanticOpKind, isMap, laneTrackId, mainTrackRanges, partDurationMs, partUnionSchema, readMainTrackItems, readMengineEventStream, readPart, readPartDurationMs, readVideoDocumentFromDraft, reassignSpeechesToVideoClipsByTime, recalculateTimelineDuration, relativePositionForAbs, resolveAllSpeechOverlaps, resolveSpeechOverlapByShiftingVideos, safeDurationMs, index_d_exports as schemas, snapshotToPlain, solveVideoDocument, syncAggregatedClipsTimePosition, toVideoDocument, validateVideoDocument, videoDocumentMirrorSchema, videoDocumentSchema };
1122
+ export { type Aggregation, type Attachment, type BgmPart, type CaptionPart, type CaptionStyle, type CommitOptions, DEFAULT_UNIT_TIME_MS, type DerivedItemPosition, type DocStorageLike, type DocVersionMark, IMPLEMENTED_SEMANTIC_OP_KINDS, type ImplementedSemanticOpKind, LANE_KINDS_IN_STACK_ORDER, type LaneKind, type MainClipRange, type MakeEmptyPart, ManualSyncDoc, type ManualSyncDocOptions, MedeoHttpDocStorage, type MedeoHttpDocStorageOptions, MemoryDocStorage, MengineAckFailedError, type MengineAuditEntry, type MengineAuditResponse, MengineDocSession, type MengineDocSessionOptions, type MengineDocSessionUpdateEvent, type MengineDocSyncState, type MengineDocumentVersion, type MengineEventStreamOptions, MengineHttpClient, type MengineHttpClientOptions, MengineHttpRequestError, MenginePushRejectedError, type MenginePushResponse, type MenginePushUpdateResponse, type MengineRejectedResponse, type MengineSnapshotResponse, type MengineSseUpdateEvent, type MengineSyncResponse, type MengineUndoState, type MengineUpdateMeta, MirrorVideoDocumentAdapter, type MirrorVideoDocumentOptions, type OpActor, type PartAggregation, type PartKind, type PartUnion, type PlannedSemanticOpKind, type PullFailureReason, type PullResult, type PushOutcome, type PushOutcomeKind, type PushResult, type PushResultKind, SchemaValidator, type SemanticDocumentAdapter, SemanticEditor, type SemanticOpInput, type SemanticOpKind, type SemanticOpName, type SnapshotReadable, type SolvedVideoDocument, type SpeechHostMap, type SpeechPart, type SpeedShift, TIMELINE_SKELETON_DURATION_MS, type Timeline, type TimelineDoc, type TimelineItem, type Track, type TrackDraft, type TrackItem, type TrackItemDraft, type TrackItemTimePosition, type TransactAudit, ValidationError, type VideoClipPart, type VideoDocument, type VideoDocumentDraft, type VideoDocumentMirrorSchema, VideoDocumentValidationError, type VideoDocumentValidationIssue, type VideoDocumentValidationIssueCode, type VideoDraft, type CaptionPart$1 as VideoDraftCaptionPart, type VideoDraftContent, type VideoDraftPartUnion, type Timeline$1 as VideoDraftTimeline, type Track$1 as VideoDraftTrack, type TrackItem$1 as VideoDraftTrackItem, type WaitForServerAckOptions, arrangeMainTrackSeamlessly, assertValidVideoDocument, base64ToBytes, buildInitialVideoDocument, buildSpeechHostMap, bytesToBase64, cascadeAfterVideoClipChanges, createMirrorVideoDocument, createMirrorVideoDocumentAdapter, decodeDocVersionMark, derivePositionFromAbs, encodeDocVersionMark, ensureLaneTrack, fillMainTrackTimeGaps, findLaneTrack, fromVideoDocument, generatePartId, getAt, hostForAbsMs, isEmptyVideoClip, isImplementedSemanticOpKind, isMap, laneTrackId, mainTrackRanges, partDurationMs, partUnionSchema, readMainTrackItems, readMengineEventStream, readPart, readPartDurationMs, readVideoDocumentFromDraft, reassignSpeechesToVideoClipsByTime, recalculateTimelineDuration, relativePositionForAbs, resolveAllSpeechOverlaps, resolveSpeechOverlapByShiftingVideos, safeDurationMs, index_d_exports as schemas, snapshotToPlain, solveVideoDocument, syncAggregatedClipsTimePosition, toVideoDocument, validateVideoDocument, videoDocumentMirrorSchema, videoDocumentSchema };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-D7D4PA-g.js";
2
- import { A as isEmptyVideoClip, C as fillMainTrackTimeGaps, D as resolveSpeechOverlapByShiftingVideos, E as resolveAllSpeechOverlaps, F as partUnionToDraft, I as recordEntries, L as videoDocumentMirrorSchema, M as safeDurationMs, N as DEFAULT_UNIT_TIME_MS, O as syncAggregatedClipsTimePosition, P as effectiveVideoClipDurationMs, R as base64ToBytes, S as arrangeMainTrackSeamlessly, T as recalculateTimelineDuration, _ as ensureLaneTrack, a as DocumentMutationGuard, b as solveVideoDocument, c as derivePositionFromAbs, d as VideoDocumentValidationError, f as assertValidVideoDocument, g as LANE_KINDS_IN_STACK_ORDER, h as videoDocumentSchema, i as readVideoDocumentFromDraft, j as partDurationMs, k as TIMELINE_SKELETON_DURATION_MS, l as fromVideoDocument, m as partUnionSchema, n as createMirrorVideoDocument, o as buildInitialVideoDocument, p as validateVideoDocument, r as createMirrorVideoDocumentAdapter, s as buildSpeechHostMap, t as MirrorVideoDocumentAdapter, u as toVideoDocument, v as findLaneTrack, w as reassignSpeechesToVideoClipsByTime, x as cascadeAfterVideoClipChanges, y as laneTrackId, z as bytesToBase64 } from "./document-CmjJwOba.js";
2
+ import { A as isEmptyVideoClip, C as fillMainTrackTimeGaps, D as resolveSpeechOverlapByShiftingVideos, E as resolveAllSpeechOverlaps, F as partUnionToDraft, I as recordEntries, L as videoDocumentMirrorSchema, M as safeDurationMs, N as DEFAULT_UNIT_TIME_MS, O as syncAggregatedClipsTimePosition, P as effectiveVideoClipDurationMs, R as base64ToBytes, S as arrangeMainTrackSeamlessly, T as recalculateTimelineDuration, _ as ensureLaneTrack, a as DocumentMutationGuard, b as solveVideoDocument, c as derivePositionFromAbs, d as VideoDocumentValidationError, f as assertValidVideoDocument, g as LANE_KINDS_IN_STACK_ORDER, h as videoDocumentSchema, i as readVideoDocumentFromDraft, j as partDurationMs, k as TIMELINE_SKELETON_DURATION_MS, l as fromVideoDocument, m as partUnionSchema, n as createMirrorVideoDocument, o as buildInitialVideoDocument, p as validateVideoDocument, r as createMirrorVideoDocumentAdapter, s as buildSpeechHostMap, t as MirrorVideoDocumentAdapter, u as toVideoDocument, v as findLaneTrack, w as reassignSpeechesToVideoClipsByTime, x as cascadeAfterVideoClipChanges, y as laneTrackId, z as bytesToBase64 } from "./document-B_JQwrC5.js";
3
3
  import { z } from "zod";
4
4
  import { LoroDoc, UndoManager, VersionVector } from "loro-crdt";
5
5
  import { ClientServerSynchronizer, DocManager } from "@mengine/sync";
@@ -163,9 +163,6 @@ async function safeReadJson(response) {
163
163
  //#endregion
164
164
  //#region src/client/sse.ts
165
165
  async function readMengineEventStream(options) {
166
- const onUpdate = (event) => {
167
- options.onUpdate(event);
168
- };
169
166
  const response = await options.client.fetch(options.client.eventsUrl(), {
170
167
  headers: options.client.headers(),
171
168
  signal: options.signal
@@ -184,15 +181,19 @@ async function readMengineEventStream(options) {
184
181
  while (splitAt >= 0) {
185
182
  const frame = buffer.slice(0, splitAt);
186
183
  buffer = buffer.slice(buffer[splitAt] === "\r" ? splitAt + 4 : splitAt + 2);
187
- handleSseFrame(frame, onUpdate);
184
+ handleSseFrame(frame, options);
188
185
  splitAt = findSseFrameBoundary(buffer);
189
186
  }
190
187
  }
191
188
  }
192
- function handleSseFrame(frame, onUpdate) {
189
+ function handleSseFrame(frame, options) {
193
190
  const event = parseSseFrame(frame);
191
+ if (event.event === "resync") {
192
+ options.onResync?.();
193
+ return;
194
+ }
194
195
  if (event.event !== "update" || event.data.length === 0) return;
195
- onUpdate(JSON.parse(event.data.join("\n")));
196
+ options.onUpdate(JSON.parse(event.data.join("\n")));
196
197
  }
197
198
  function parseSseFrame(frame) {
198
199
  let event = null;
@@ -2199,22 +2200,24 @@ var ManualSyncDoc = class ManualSyncDoc {
2199
2200
  * recovers whatever the stream missed while it was down. Recovery therefore
2200
2201
  * lives in the synchronizer (keyed on the real doc version), not here.
2201
2202
  *
2202
- * Each SSE frame's updates are forwarded verbatim via `onUpdate`; this
2203
+ * Update frames are forwarded via `onUpdate`, resync hints via `onResync`; this
2203
2204
  * connection keeps no version cursor and does no catch-up of its own. Dedup is
2204
2205
  * unnecessary because `LoroDoc.import` is idempotent by OpId/VV.
2205
2206
  */
2206
2207
  var SseConnection = class {
2207
2208
  client;
2208
2209
  onUpdate;
2210
+ onResync;
2209
2211
  reconnectDelayMs;
2210
2212
  inner = void 0;
2211
2213
  event = new EventBus();
2212
2214
  streamTask = null;
2213
2215
  _status = "idle";
2214
2216
  _error;
2215
- constructor(client, onUpdate, reconnectDelayMs) {
2217
+ constructor(client, onUpdate, onResync, reconnectDelayMs) {
2216
2218
  this.client = client;
2217
2219
  this.onUpdate = onUpdate;
2220
+ this.onResync = onResync;
2218
2221
  this.reconnectDelayMs = reconnectDelayMs;
2219
2222
  }
2220
2223
  get status() {
@@ -2275,6 +2278,7 @@ var SseConnection = class {
2275
2278
  client: this.client,
2276
2279
  signal,
2277
2280
  onOpen: () => this.setStatus("connected"),
2281
+ onResync: this.onResync,
2278
2282
  onUpdate: (event) => {
2279
2283
  for (const update of event.updates) this.onUpdate(update);
2280
2284
  }
@@ -2324,7 +2328,7 @@ var MedeoHttpDocStorage = class {
2324
2328
  this.options = options;
2325
2329
  this.client = options.client;
2326
2330
  this.docId = options.docId;
2327
- this.connection = new SseConnection(this.client, (update) => this.emitUpdate(update), options.sseReconnectDelayMs ?? 500);
2331
+ this.connection = new SseConnection(this.client, (update) => this.emitUpdate(update), () => this.events.emit("resync"), options.sseReconnectDelayMs ?? 500);
2328
2332
  }
2329
2333
  get isReadonly() {
2330
2334
  return this.options.readonlyMode ?? false;
@@ -2403,6 +2407,10 @@ var MedeoHttpDocStorage = class {
2403
2407
  subscribePushOutcome(callback) {
2404
2408
  return this.events.on("pushOutcome", callback);
2405
2409
  }
2410
+ /** Observe live hints that require VV catch-up without replacing the SSE connection. */
2411
+ subscribeResync(callback) {
2412
+ return this.events.on("resync", callback);
2413
+ }
2406
2414
  /** Authoritative HTTP reads and accepted pushes, never inferred from SSE bytes. */
2407
2415
  subscribeServerVersion(callback) {
2408
2416
  return this.events.on("serverVersion", callback);
@@ -2705,7 +2713,6 @@ var MengineDocSession = class {
2705
2713
  started = false;
2706
2714
  startTask = null;
2707
2715
  destroyed = false;
2708
- closeTask = null;
2709
2716
  lifetime = null;
2710
2717
  hasConnected = false;
2711
2718
  initialVersionValue = null;
@@ -2887,6 +2894,7 @@ var MengineDocSession = class {
2887
2894
  return this.startTask;
2888
2895
  }
2889
2896
  async open() {
2897
+ this.disposables.add(this.server.subscribeResync(() => this.synchronizer.requestSync(this.docId)));
2890
2898
  this.disposables.add(this.server.subscribeServerVersion((version) => {
2891
2899
  this.serverVVValue = version;
2892
2900
  if (this.getSyncState().confirmation === "confirmed") this.pushErrorValue = null;
@@ -2905,13 +2913,6 @@ var MengineDocSession = class {
2905
2913
  this.local.connection.connect();
2906
2914
  await this.local.connection.waitForConnected().abortOn(this.lifetime.signal);
2907
2915
  if (this.destroyed) throw new Error("mengine doc session destroyed");
2908
- if (this.options.initialSnapshot) {
2909
- await this.local.pushDocUpdate({
2910
- docId: this.docId,
2911
- data: this.options.initialSnapshot
2912
- }, "initial-snapshot");
2913
- if (this.destroyed) throw new Error("mengine doc session destroyed");
2914
- }
2915
2916
  this.server.connection.connect();
2916
2917
  this.synchronizer.start();
2917
2918
  this.manager.start();
@@ -2944,35 +2945,6 @@ var MengineDocSession = class {
2944
2945
  this.emitSyncState();
2945
2946
  return this.snapshot();
2946
2947
  }
2947
- getLocalSaveState() {
2948
- return this.getState().localSave;
2949
- }
2950
- /** Immediately reports local transaction state; independent of server confirmation. */
2951
- subscribeLocalSaveState(cb) {
2952
- const off = this.events.on("state", () => cb(this.getLocalSaveState()));
2953
- cb(this.getLocalSaveState());
2954
- return off;
2955
- }
2956
- /** Waits for commits present now to reach local storage. Does not wait for the network. */
2957
- async waitForLocalSave(options = {}) {
2958
- if (!this.editorValue || this.destroyed) throw new Error("mengine doc session is not started");
2959
- await this.manager.waitForLocalSave(this.docId).timeout(options.timeoutMs ?? 15e3);
2960
- }
2961
- /** Stop accepting edits and drain local persistence before releasing resources.
2962
- * A failed local write keeps retrying, even after the host has unmounted.
2963
- * Hosts should use this for normal teardown; destroy() is immediate cancellation. */
2964
- close() {
2965
- if (this.closeTask) return this.closeTask;
2966
- if (this.destroyed || !this.editorValue) {
2967
- this.destroy();
2968
- return Promise.resolve();
2969
- }
2970
- this.mutationGuard.close();
2971
- this.synchronizer.stop();
2972
- this.server.connection.disconnect();
2973
- this.closeTask = this.manager.waitForLocalSave(this.docId).then(() => this.destroy());
2974
- return this.closeTask;
2975
- }
2976
2948
  getSyncState() {
2977
2949
  const state = this.getState();
2978
2950
  const target = this.confirmationTarget();
package/dist/testing.js CHANGED
@@ -1,4 +1,4 @@
1
- import { R as base64ToBytes, r as createMirrorVideoDocumentAdapter, z as bytesToBase64 } from "./document-CmjJwOba.js";
1
+ import { R as base64ToBytes, r as createMirrorVideoDocumentAdapter, z as bytesToBase64 } from "./document-B_JQwrC5.js";
2
2
  import { t as classifyUpdate } from "./loro-relay-doc-cJSY-uau.js";
3
3
  import { LoroDoc, VersionVector, encodeFrontiers } from "loro-crdt";
4
4
  //#region src/testing/in-memory-mengine-server.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mengine/medeo-client",
3
- "version": "2.0.1-alpha.0",
3
+ "version": "2.0.1",
4
4
  "license": "UNLICENSED",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,9 +25,9 @@
25
25
  "dependencies": {
26
26
  "loro-mirror": "^2.3.3",
27
27
  "zod": "^4.4.3",
28
- "@mengine/storage": "2.0.1-alpha.0",
29
- "@mengine/sync": "2.0.1-alpha.0",
30
- "@mengine/utils": "2.0.1-alpha.0"
28
+ "@mengine/storage": "2.0.1",
29
+ "@mengine/sync": "2.0.1",
30
+ "@mengine/utils": "2.0.1"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^25.9.1",