@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 +6 -12
- package/dist/{document-CmjJwOba.js → document-B_JQwrC5.js} +5 -3
- package/dist/index.d.ts +6 -20
- package/dist/index.js +19 -47
- package/dist/testing.js +1 -1
- package/package.json +4 -4
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
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,
|
|
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-
|
|
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,
|
|
184
|
+
handleSseFrame(frame, options);
|
|
188
185
|
splitAt = findSseFrameBoundary(buffer);
|
|
189
186
|
}
|
|
190
187
|
}
|
|
191
188
|
}
|
|
192
|
-
function handleSseFrame(frame,
|
|
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
|
-
*
|
|
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-
|
|
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
|
|
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
|
|
29
|
-
"@mengine/sync": "2.0.1
|
|
30
|
-
"@mengine/utils": "2.0.1
|
|
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",
|