@mengine/medeo-client 2.0.1-alpha.5 → 2.0.1-alpha.7
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 +31 -0
- package/dist/{document-Bzzcn3g7.js → document-Bnxm1p5E.js} +156 -183
- package/dist/{index-B31KeDzA.d.ts → index-CDrPUhBH.d.ts} +276 -181
- package/dist/index.d.ts +96 -134
- package/dist/index.js +368 -104
- package/dist/{loro-relay-doc-ssdYpuef.js → loro-relay-doc-cJSY-uau.js} +7 -2
- package/dist/relay.js +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +4 -19
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as findLaneTrack, A as assemblePhoneticScriptContent, B as isMediaAssetVariantKind, C as EntityProjectionGraph, D as ScriptCompositionError, E as decodeEntityRelationRows, F as variantBaseEntityIds, G as toVideoDocument, H as buildSpeechHostMap, I as isJsonObject, J as validateVideoDocument, K as VideoDocumentValidationError, L as createEntityId, M as findComposedAudioScript, N as selectAudioScriptSegment, O as assembleCaptionContent, P as updateEntityFields, Q as ensureLaneTrack, R as createRelationId, S as projectEntityTimeline, St as bytesToBase64, T as EntityTimelineProjectionError, U as derivePositionFromAbs, V as buildInitialVideoDocument, W as fromVideoDocument, X as videoDocumentSchema, Y as partUnionSchema, Z as LANE_KINDS_IN_STACK_ORDER, _ as hasAudioScriptAsset, _t as speedOf, a as createMirrorVideoDocument, at as fillMainTrackTimeGaps, b as assertFieldChanges, bt as videoDocumentMirrorSchema, c as DocumentMutationGuard, ct as resolveAllSpeechOverlaps, d as LoroEntityDraft, dt as TIMELINE_SKELETON_DURATION_MS, et as hasResolvedPlacement, f as readEntityDocument, ft as isEmptyVideoClip, g as audioScriptAssetHash, gt as effectiveVideoClipDurationMs, h as audioScriptAssetFields, ht as DEFAULT_UNIT_TIME_MS, i as MirrorVideoDocumentAdapter, it as arrangeMainTrackSeamlessly, j as assembleScriptText, k as assembleEntityContent, l as LORO_ENTITY_SCHEMA, lt as resolveSpeechOverlapByShiftingVideos, m as audioScriptAssetContent, mt as safeDurationMs, n as createPlainMemoryAdapter, nt as solveVideoDocument, o as createMirrorVideoDocumentAdapter, ot as reassignSpeechesToVideoClipsByTime, p as entityOrderGroups, pt as partDurationMs, q as assertValidVideoDocument, r as generatePartId, rt as cascadeAfterVideoClipChanges, s as readVideoDocumentFromDraft, st as recalculateTimelineDuration, t as PlainMemoryAdapter, tt as laneTrackId, u as LoroEntityDocument, ut as syncAggregatedClipsTimePosition, v as equalJson, vt as partUnionToDraft, w as resolveEntityTimelineLayout, x as resolveFieldPath, xt as base64ToBytes, y as applyFieldChanges, yt as recordEntries, z as hasSequence } from "./document-Bnxm1p5E.js";
|
|
2
2
|
import { addSpeechesInputSchema, addVideoClipsInputSchema, adjustBgmVolumeInputSchema, adjustSpeechVolumeInputSchema, adjustVideoClipDurationInputSchema, adjustVideoClipVolumeInputSchema, changeSpeechScriptInputSchema, changeSpeechVoiceInputSchema, deleteBgmInputSchema, deleteSpeechesInputSchema, deleteVideoClipsInputSchema, moveSpeechesInputSchema, moveVideoClipsByAnchorInputSchema, moveVideoClipsInputSchema, replaceVideoClipContentInputSchema, replaceVideoClipSequenceInputSchema, setBgmInputSchema, setCaptionStyleInputSchema, setCaptionVisibilityInputSchema, setVideoClipSpeedShiftInputSchema, t as schemas_exports } from "./schemas.js";
|
|
3
|
-
import { LoroDoc, VersionVector } from "loro-crdt";
|
|
3
|
+
import { LoroDoc, UndoManager, VersionVector } from "loro-crdt";
|
|
4
4
|
import { ClientServerSynchronizer, DocManager } from "@mengine/sync";
|
|
5
5
|
import { DisposableSet, EventBus, Task } from "@mengine/utils";
|
|
6
6
|
import { BaseDocStorage, DummyConnection } from "@mengine/storage";
|
|
@@ -60,12 +60,6 @@ var MengineHttpClient = class {
|
|
|
60
60
|
async fetchSnapshot() {
|
|
61
61
|
return await this.requestJson("snapshot");
|
|
62
62
|
}
|
|
63
|
-
async bootstrapSnapshot(snapshot) {
|
|
64
|
-
return await this.requestJson("bootstrap", {
|
|
65
|
-
method: "POST",
|
|
66
|
-
body: JSON.stringify({ snapshot: bytesToBase64(snapshot) })
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
63
|
/**
|
|
70
64
|
* Loro VV-diff pull: send the caller's oplog `VersionVector.encode` as `from`
|
|
71
65
|
* (omit for a full pull) and receive exactly the updates it is missing plus
|
|
@@ -168,9 +162,6 @@ async function safeReadJson$1(response) {
|
|
|
168
162
|
//#endregion
|
|
169
163
|
//#region src/client/sse.ts
|
|
170
164
|
async function readMengineEventStream(options) {
|
|
171
|
-
const onUpdate = (event) => {
|
|
172
|
-
options.onUpdate(event);
|
|
173
|
-
};
|
|
174
165
|
const response = await options.client.fetch(options.client.eventsUrl(), {
|
|
175
166
|
headers: options.client.headers(),
|
|
176
167
|
signal: options.signal
|
|
@@ -189,15 +180,19 @@ async function readMengineEventStream(options) {
|
|
|
189
180
|
while (splitAt >= 0) {
|
|
190
181
|
const frame = buffer.slice(0, splitAt);
|
|
191
182
|
buffer = buffer.slice(buffer[splitAt] === "\r" ? splitAt + 4 : splitAt + 2);
|
|
192
|
-
handleSseFrame(frame,
|
|
183
|
+
handleSseFrame(frame, options);
|
|
193
184
|
splitAt = findSseFrameBoundary(buffer);
|
|
194
185
|
}
|
|
195
186
|
}
|
|
196
187
|
}
|
|
197
|
-
function handleSseFrame(frame,
|
|
188
|
+
function handleSseFrame(frame, options) {
|
|
198
189
|
const event = parseSseFrame(frame);
|
|
190
|
+
if (event.event === "resync") {
|
|
191
|
+
options.onResync?.();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
199
194
|
if (event.event !== "update" || event.data.length === 0) return;
|
|
200
|
-
onUpdate(JSON.parse(event.data.join("\n")));
|
|
195
|
+
options.onUpdate(JSON.parse(event.data.join("\n")));
|
|
201
196
|
}
|
|
202
197
|
function parseSseFrame(frame) {
|
|
203
198
|
let event = null;
|
|
@@ -1999,7 +1994,7 @@ function migrateLegacyTimelineToEntities(legacy, assetFacts, initial = {
|
|
|
1999
1994
|
}, idFactory = (prefix) => `${prefix}_${globalThis.crypto.randomUUID()}`) {
|
|
2000
1995
|
assertSupportedLegacy(legacy);
|
|
2001
1996
|
if (initial.entities.some((row) => row.entityKind === "timeline")) fail("Timeline already exists; migration is not an editing operation");
|
|
2002
|
-
if (legacy
|
|
1997
|
+
if (hasResolvedPlacement(legacy)) fail("An entity projection cannot be migrated as legacy state");
|
|
2003
1998
|
const normalizedMainRanges = overlapNormalizedMainRanges(legacy);
|
|
2004
1999
|
let entities = structuredClone([...initial.entities]);
|
|
2005
2000
|
let relations = structuredClone([...initial.relations]);
|
|
@@ -2225,27 +2220,23 @@ function migrateLegacyTimelineToEntities(legacy, assetFacts, initial = {
|
|
|
2225
2220
|
entities,
|
|
2226
2221
|
relations
|
|
2227
2222
|
};
|
|
2228
|
-
assertEntityProjectionPreservesLegacy(legacy, projectEntityTimeline(rows
|
|
2223
|
+
assertEntityProjectionPreservesLegacy(legacy, projectEntityTimeline(rows), rows);
|
|
2229
2224
|
return rows;
|
|
2230
2225
|
}
|
|
2231
2226
|
/** Server gate: a cutover may change representation, never the existing render facts. */
|
|
2232
2227
|
function assertEntityProjectionPreservesLegacy(legacy, projection, rows) {
|
|
2233
2228
|
assertSupportedLegacy(legacy);
|
|
2234
|
-
if (projection
|
|
2229
|
+
if (!hasResolvedPlacement(projection)) fail("Expected an entity projection for migration");
|
|
2235
2230
|
const normalizedMainRanges = overlapNormalizedMainRanges(legacy);
|
|
2236
|
-
const compatibilityFacts = (document) => {
|
|
2237
|
-
const
|
|
2231
|
+
const compatibilityFacts = (document, placementResolved) => {
|
|
2232
|
+
const view = fromVideoDocument(document, { placementResolved });
|
|
2238
2233
|
for (const part of Object.values(view.part_library ?? {})) {
|
|
2239
2234
|
const speech = part.speech;
|
|
2240
2235
|
if (speech?.phoneme_script === null) delete speech.phoneme_script;
|
|
2241
2236
|
}
|
|
2242
|
-
|
|
2243
|
-
return {
|
|
2244
|
-
meta,
|
|
2245
|
-
view
|
|
2246
|
-
};
|
|
2237
|
+
return { view };
|
|
2247
2238
|
};
|
|
2248
|
-
if (canonical(compatibilityFacts(legacy)) !== canonical(compatibilityFacts(projection))) fail("Entity migration must preserve all existing timeline facts; edit after cutover");
|
|
2239
|
+
if (canonical(compatibilityFacts(legacy, false)) !== canonical(compatibilityFacts(projection, true))) fail("Entity migration must preserve all existing timeline facts; edit after cutover");
|
|
2249
2240
|
const trackOrders = [];
|
|
2250
2241
|
for (const oldTrack of legacy.tracks ?? []) {
|
|
2251
2242
|
const candidates = rows.entities.filter((row) => row.entityKind === "track" && (oldTrack.id === void 0 ? row.payload.role === oldTrack.parts_kind : row.entityId === oldTrack.id));
|
|
@@ -3648,7 +3639,7 @@ function writeSpeechAssets(draft, assets) {
|
|
|
3648
3639
|
if (caption == null) continue;
|
|
3649
3640
|
const priorCaption = draft.part_library?.[captionId]?.caption;
|
|
3650
3641
|
setPart(draft, captionId, { caption: {
|
|
3651
|
-
...priorCaption,
|
|
3642
|
+
...priorCaption ?? { style: {} },
|
|
3652
3643
|
id: captionId,
|
|
3653
3644
|
kind: "caption",
|
|
3654
3645
|
initial_duration_ms: caption.duration_ms,
|
|
@@ -4057,6 +4048,10 @@ var ManualSyncDoc = class ManualSyncDoc {
|
|
|
4057
4048
|
client;
|
|
4058
4049
|
doc;
|
|
4059
4050
|
adapter;
|
|
4051
|
+
/** Whether this document's timeline is owned by the Entity graph. */
|
|
4052
|
+
isEntityDocument() {
|
|
4053
|
+
return this.adapter.isEntityDocument();
|
|
4054
|
+
}
|
|
4060
4055
|
editor;
|
|
4061
4056
|
/**
|
|
4062
4057
|
* The version the server is known to hold. Starts empty (nothing confirmed)
|
|
@@ -4075,17 +4070,24 @@ var ManualSyncDoc = class ManualSyncDoc {
|
|
|
4075
4070
|
* Fetches the snapshot up front rather than starting empty and converging: the
|
|
4076
4071
|
* agent's first act is to read the document, so there is no useful state before
|
|
4077
4072
|
* the snapshot lands. This also fails fast and loudly on a document that does
|
|
4078
|
-
* not exist
|
|
4079
|
-
* waiting forever for content that will never arrive.
|
|
4073
|
+
* not exist or whose first snapshot is incomplete or invalid.
|
|
4080
4074
|
*/
|
|
4081
4075
|
static async open(options) {
|
|
4082
|
-
const doc = new LoroDoc();
|
|
4083
|
-
if (options.peerId != null) doc.setPeerId(options.peerId);
|
|
4084
4076
|
const response = await options.client.fetchSnapshot();
|
|
4085
|
-
doc
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4077
|
+
const doc = new LoroDoc();
|
|
4078
|
+
let manualSyncDoc;
|
|
4079
|
+
try {
|
|
4080
|
+
if (options.peerId != null) doc.setPeerId(options.peerId);
|
|
4081
|
+
if (doc.import(base64ToBytes(response.snapshot)).pending != null) throw new Error("Initial mengine snapshot has missing dependencies");
|
|
4082
|
+
manualSyncDoc = new ManualSyncDoc(options.client, doc);
|
|
4083
|
+
assertValidVideoDocument(manualSyncDoc.snapshot());
|
|
4084
|
+
manualSyncDoc.watermark = manualSyncDoc.serverVVFrom(response.version.server_vv) ?? doc.oplogVersion();
|
|
4085
|
+
return manualSyncDoc;
|
|
4086
|
+
} catch (error) {
|
|
4087
|
+
manualSyncDoc?.adapter.dispose();
|
|
4088
|
+
doc.free();
|
|
4089
|
+
throw error;
|
|
4090
|
+
}
|
|
4089
4091
|
}
|
|
4090
4092
|
/** Current document read model (authoritative shape). */
|
|
4091
4093
|
snapshot() {
|
|
@@ -4104,9 +4106,9 @@ var ManualSyncDoc = class ManualSyncDoc {
|
|
|
4104
4106
|
editorPeerId() {
|
|
4105
4107
|
return this.doc.peerIdStr;
|
|
4106
4108
|
}
|
|
4107
|
-
/** Current
|
|
4108
|
-
|
|
4109
|
-
return fromVideoDocument(this.adapter.snapshot());
|
|
4109
|
+
/** Current content projected into the compatible track and part read shape. */
|
|
4110
|
+
content() {
|
|
4111
|
+
return fromVideoDocument(this.adapter.snapshot(), { placementResolved: this.isEntityDocument() });
|
|
4110
4112
|
}
|
|
4111
4113
|
/**
|
|
4112
4114
|
* Replay a deterministic sandbox journal into this document.
|
|
@@ -4126,9 +4128,8 @@ var ManualSyncDoc = class ManualSyncDoc {
|
|
|
4126
4128
|
*
|
|
4127
4129
|
* This pair replaces the legacy integer-version comparison that
|
|
4128
4130
|
* `DraftVersionDetector` used to tell the LLM "the draft was modified
|
|
4129
|
-
* externally, reload before editing".
|
|
4130
|
-
*
|
|
4131
|
-
* vectors restores the same capability.
|
|
4131
|
+
* externally, reload before editing". Comparing Loro version vectors covers
|
|
4132
|
+
* all collaborative edits without a business revision field in the document.
|
|
4132
4133
|
*
|
|
4133
4134
|
* Same capability, not a stronger one: like the legacy detector, this only
|
|
4134
4135
|
* reports what changed between two moments the caller chose to sample.
|
|
@@ -4212,6 +4213,7 @@ var ManualSyncDoc = class ManualSyncDoc {
|
|
|
4212
4213
|
if (serverVV != null) this.watermark = serverVV;
|
|
4213
4214
|
return {
|
|
4214
4215
|
kind: response.kind,
|
|
4216
|
+
mengineUpdateSeq: response.version?.update_seq ?? response.update_seq ?? void 0,
|
|
4215
4217
|
updateSeq: response.update_seq ?? void 0,
|
|
4216
4218
|
collaborated: serverVV != null && !covers(localVersion, serverVV)
|
|
4217
4219
|
};
|
|
@@ -4250,22 +4252,24 @@ var ManualSyncDoc = class ManualSyncDoc {
|
|
|
4250
4252
|
* recovers whatever the stream missed while it was down. Recovery therefore
|
|
4251
4253
|
* lives in the synchronizer (keyed on the real doc version), not here.
|
|
4252
4254
|
*
|
|
4253
|
-
*
|
|
4255
|
+
* Update frames are forwarded via `onUpdate`, resync hints via `onResync`; this
|
|
4254
4256
|
* connection keeps no version cursor and does no catch-up of its own. Dedup is
|
|
4255
4257
|
* unnecessary because `LoroDoc.import` is idempotent by OpId/VV.
|
|
4256
4258
|
*/
|
|
4257
4259
|
var SseConnection = class {
|
|
4258
4260
|
client;
|
|
4259
4261
|
onUpdate;
|
|
4262
|
+
onResync;
|
|
4260
4263
|
reconnectDelayMs;
|
|
4261
4264
|
inner = void 0;
|
|
4262
4265
|
event = new EventBus();
|
|
4263
4266
|
streamTask = null;
|
|
4264
4267
|
_status = "idle";
|
|
4265
4268
|
_error;
|
|
4266
|
-
constructor(client, onUpdate, reconnectDelayMs) {
|
|
4269
|
+
constructor(client, onUpdate, onResync, reconnectDelayMs) {
|
|
4267
4270
|
this.client = client;
|
|
4268
4271
|
this.onUpdate = onUpdate;
|
|
4272
|
+
this.onResync = onResync;
|
|
4269
4273
|
this.reconnectDelayMs = reconnectDelayMs;
|
|
4270
4274
|
}
|
|
4271
4275
|
get status() {
|
|
@@ -4326,10 +4330,12 @@ var SseConnection = class {
|
|
|
4326
4330
|
client: this.client,
|
|
4327
4331
|
signal,
|
|
4328
4332
|
onOpen: () => this.setStatus("connected"),
|
|
4333
|
+
onResync: this.onResync,
|
|
4329
4334
|
onUpdate: (event) => {
|
|
4330
4335
|
for (const update of event.updates) this.onUpdate(update);
|
|
4331
4336
|
}
|
|
4332
4337
|
});
|
|
4338
|
+
if (!signal.aborted) this.setStatus("connecting", /* @__PURE__ */ new Error("SSE stream closed"));
|
|
4333
4339
|
} catch (error) {
|
|
4334
4340
|
if (signal.aborted) break;
|
|
4335
4341
|
this.setStatus("connecting", error instanceof Error ? error : new Error(String(error)));
|
|
@@ -4374,7 +4380,7 @@ var MedeoHttpDocStorage = class {
|
|
|
4374
4380
|
this.options = options;
|
|
4375
4381
|
this.client = options.client;
|
|
4376
4382
|
this.docId = options.docId;
|
|
4377
|
-
this.connection = new SseConnection(this.client, (update) => this.emitUpdate(update), options.sseReconnectDelayMs ?? 500);
|
|
4383
|
+
this.connection = new SseConnection(this.client, (update) => this.emitUpdate(update), () => this.events.emit("resync"), options.sseReconnectDelayMs ?? 500);
|
|
4378
4384
|
}
|
|
4379
4385
|
get isReadonly() {
|
|
4380
4386
|
return this.options.readonlyMode ?? false;
|
|
@@ -4382,6 +4388,7 @@ var MedeoHttpDocStorage = class {
|
|
|
4382
4388
|
async getDoc(docId) {
|
|
4383
4389
|
this.assertDocId(docId);
|
|
4384
4390
|
const snapshot = await this.client.fetchSnapshot();
|
|
4391
|
+
this.reportServerVersion(snapshot.version?.server_vv);
|
|
4385
4392
|
const now = /* @__PURE__ */ new Date();
|
|
4386
4393
|
return {
|
|
4387
4394
|
docId,
|
|
@@ -4393,6 +4400,7 @@ var MedeoHttpDocStorage = class {
|
|
|
4393
4400
|
async getDocDiff(docId, knownVersion) {
|
|
4394
4401
|
this.assertDocId(docId);
|
|
4395
4402
|
const response = await this.client.sync(knownVersion);
|
|
4403
|
+
this.reportServerVersion(response.server_vv);
|
|
4396
4404
|
return {
|
|
4397
4405
|
docId,
|
|
4398
4406
|
missing: base64ToBytes(response.update),
|
|
@@ -4423,7 +4431,7 @@ var MedeoHttpDocStorage = class {
|
|
|
4423
4431
|
if (update.data.byteLength === 0) return {};
|
|
4424
4432
|
try {
|
|
4425
4433
|
const response = await this.client.pushUpdate(update.data);
|
|
4426
|
-
const serverVV =
|
|
4434
|
+
const serverVV = this.reportServerVersion(response.version?.server_vv);
|
|
4427
4435
|
this.events.emit("pushOutcome", {
|
|
4428
4436
|
kind: response.kind,
|
|
4429
4437
|
updateSeq: response.update_seq ?? void 0,
|
|
@@ -4451,6 +4459,19 @@ var MedeoHttpDocStorage = class {
|
|
|
4451
4459
|
subscribePushOutcome(callback) {
|
|
4452
4460
|
return this.events.on("pushOutcome", callback);
|
|
4453
4461
|
}
|
|
4462
|
+
/** Observe live hints that require VV catch-up without replacing the SSE connection. */
|
|
4463
|
+
subscribeResync(callback) {
|
|
4464
|
+
return this.events.on("resync", callback);
|
|
4465
|
+
}
|
|
4466
|
+
/** Authoritative HTTP reads and accepted pushes, never inferred from SSE bytes. */
|
|
4467
|
+
subscribeServerVersion(callback) {
|
|
4468
|
+
return this.events.on("serverVersion", callback);
|
|
4469
|
+
}
|
|
4470
|
+
reportServerVersion(encoded) {
|
|
4471
|
+
const version = decodeServerVV(encoded);
|
|
4472
|
+
if (version != null) this.events.emit("serverVersion", version);
|
|
4473
|
+
return version;
|
|
4474
|
+
}
|
|
4454
4475
|
async deleteDoc(_docId) {
|
|
4455
4476
|
throw new Error("MedeoHttpDocStorage does not support deleteDoc");
|
|
4456
4477
|
}
|
|
@@ -4567,6 +4588,118 @@ var MemoryDocStorage = class extends BaseDocStorage {
|
|
|
4567
4588
|
}
|
|
4568
4589
|
};
|
|
4569
4590
|
//#endregion
|
|
4591
|
+
//#region src/session/document-undo-manager.ts
|
|
4592
|
+
/**
|
|
4593
|
+
* Tracks the shared LoroDoc independently of semantic editing. Undo/redo use
|
|
4594
|
+
* the same mutation guard as the adapter and publish settled availability.
|
|
4595
|
+
*/
|
|
4596
|
+
var DocumentUndoManager = class {
|
|
4597
|
+
doc;
|
|
4598
|
+
mutationGuard;
|
|
4599
|
+
onChange;
|
|
4600
|
+
manager;
|
|
4601
|
+
unsubscribe;
|
|
4602
|
+
disposed = false;
|
|
4603
|
+
notificationQueued = false;
|
|
4604
|
+
published = {
|
|
4605
|
+
canUndo: false,
|
|
4606
|
+
canRedo: false
|
|
4607
|
+
};
|
|
4608
|
+
activeSource = null;
|
|
4609
|
+
constructor(doc, mutationGuard, onChange) {
|
|
4610
|
+
this.doc = doc;
|
|
4611
|
+
this.mutationGuard = mutationGuard;
|
|
4612
|
+
this.onChange = onChange;
|
|
4613
|
+
const peer = doc.peerIdStr;
|
|
4614
|
+
this.manager = new UndoManager(doc, {
|
|
4615
|
+
mergeInterval: 0,
|
|
4616
|
+
maxUndoSteps: 100,
|
|
4617
|
+
excludeOriginPrefixes: ["mengine.bootstrap", "mengine.initialize"],
|
|
4618
|
+
onPush: (_isUndo, range) => {
|
|
4619
|
+
this.scheduleNotification();
|
|
4620
|
+
return {
|
|
4621
|
+
value: this.activeSource ?? {
|
|
4622
|
+
peer,
|
|
4623
|
+
counter: range.start
|
|
4624
|
+
},
|
|
4625
|
+
cursors: []
|
|
4626
|
+
};
|
|
4627
|
+
},
|
|
4628
|
+
onPop: () => this.scheduleNotification()
|
|
4629
|
+
});
|
|
4630
|
+
this.unsubscribe = doc.subscribe(() => this.scheduleNotification());
|
|
4631
|
+
}
|
|
4632
|
+
getState() {
|
|
4633
|
+
this.assertActive();
|
|
4634
|
+
return {
|
|
4635
|
+
canUndo: this.manager.canUndo(),
|
|
4636
|
+
canRedo: this.manager.canRedo()
|
|
4637
|
+
};
|
|
4638
|
+
}
|
|
4639
|
+
undo(options) {
|
|
4640
|
+
return this.applyUndoRedo("Undo", options);
|
|
4641
|
+
}
|
|
4642
|
+
redo(options) {
|
|
4643
|
+
return this.applyUndoRedo("Redo", options);
|
|
4644
|
+
}
|
|
4645
|
+
beginGroup() {
|
|
4646
|
+
this.assertActive();
|
|
4647
|
+
this.mutationGuard.run(() => this.manager.groupStart());
|
|
4648
|
+
}
|
|
4649
|
+
endGroup() {
|
|
4650
|
+
this.assertActive();
|
|
4651
|
+
this.mutationGuard.run(() => this.manager.groupEnd());
|
|
4652
|
+
this.scheduleNotification();
|
|
4653
|
+
}
|
|
4654
|
+
applyUndoRedo(kind, options) {
|
|
4655
|
+
this.assertActive();
|
|
4656
|
+
return this.mutationGuard.run(() => {
|
|
4657
|
+
if (kind === "Undo" ? !this.manager.canUndo() : !this.manager.canRedo()) return false;
|
|
4658
|
+
this.activeSource = kind === "Undo" ? this.manager.topUndoValue() : this.manager.topRedoValue();
|
|
4659
|
+
try {
|
|
4660
|
+
this.doc.setNextCommitOptions({ message: JSON.stringify({
|
|
4661
|
+
semantic_op: kind,
|
|
4662
|
+
payload: { source: this.activeSource },
|
|
4663
|
+
intent: options?.intent ?? null,
|
|
4664
|
+
actor: options?.actor ?? null
|
|
4665
|
+
}) });
|
|
4666
|
+
const before = this.doc.oplogFrontiers();
|
|
4667
|
+
return (kind === "Undo" ? this.manager.undo() : this.manager.redo()) && this.doc.cmpWithFrontiers(before) !== 0;
|
|
4668
|
+
} finally {
|
|
4669
|
+
this.doc.clearNextCommitOptions();
|
|
4670
|
+
this.activeSource = null;
|
|
4671
|
+
this.scheduleNotification();
|
|
4672
|
+
}
|
|
4673
|
+
});
|
|
4674
|
+
}
|
|
4675
|
+
dispose() {
|
|
4676
|
+
if (this.disposed) return;
|
|
4677
|
+
this.disposed = true;
|
|
4678
|
+
this.unsubscribe();
|
|
4679
|
+
this.manager.setOnPush(void 0);
|
|
4680
|
+
this.manager.setOnPop(void 0);
|
|
4681
|
+
this.manager.free();
|
|
4682
|
+
}
|
|
4683
|
+
scheduleNotification() {
|
|
4684
|
+
if (this.notificationQueued || this.disposed) return;
|
|
4685
|
+
this.notificationQueued = true;
|
|
4686
|
+
queueMicrotask(() => {
|
|
4687
|
+
this.notificationQueued = false;
|
|
4688
|
+
if (!this.disposed) this.publish();
|
|
4689
|
+
});
|
|
4690
|
+
}
|
|
4691
|
+
publish() {
|
|
4692
|
+
if (this.disposed) return;
|
|
4693
|
+
const state = this.getState();
|
|
4694
|
+
if (state.canUndo === this.published.canUndo && state.canRedo === this.published.canRedo) return;
|
|
4695
|
+
this.published = state;
|
|
4696
|
+
this.onChange();
|
|
4697
|
+
}
|
|
4698
|
+
assertActive() {
|
|
4699
|
+
if (this.disposed) throw new Error("mengine doc undo manager is disposed");
|
|
4700
|
+
}
|
|
4701
|
+
};
|
|
4702
|
+
//#endregion
|
|
4570
4703
|
//#region src/session/mengine-doc-session.ts
|
|
4571
4704
|
/**
|
|
4572
4705
|
* Raised by {@link MengineDocSession.waitForServerAck} when the local edits it
|
|
@@ -4602,13 +4735,9 @@ var MengineAckFailedError = class extends Error {
|
|
|
4602
4735
|
* A live editing session for one Medeo document — the single entry point clients
|
|
4603
4736
|
* (FE draft driver, Agent, embeds) use to open, edit, and observe a document.
|
|
4604
4737
|
*
|
|
4605
|
-
*
|
|
4606
|
-
*
|
|
4607
|
-
*
|
|
4608
|
-
* ├─ ClientServerSynchronizer ── DocManager ── LoroDoc
|
|
4609
|
-
* MedeoHttpDocStorage┘ │
|
|
4610
|
-
* (remote = Rust mengine-server) ▼
|
|
4611
|
-
* MirrorVideoDocumentAdapter + SemanticEditor
|
|
4738
|
+
* SemanticEditor writes through MirrorVideoDocumentAdapter directly, while
|
|
4739
|
+
* DocumentUndoManager observes the same LoroDoc independently. Both mutation
|
|
4740
|
+
* paths use the session's DocumentMutationGuard, closed before teardown.
|
|
4612
4741
|
*
|
|
4613
4742
|
* `DocManager` owns the `LoroDoc`: local edits committed on the adapter are
|
|
4614
4743
|
* picked up via `subscribeLocalUpdates`, saved to local storage, then pushed to
|
|
@@ -4629,11 +4758,20 @@ var MengineDocSession = class {
|
|
|
4629
4758
|
manager;
|
|
4630
4759
|
events = new EventBus();
|
|
4631
4760
|
disposables = new DisposableSet();
|
|
4761
|
+
mutationGuard = new DocumentMutationGuard();
|
|
4632
4762
|
adapterValue = null;
|
|
4633
4763
|
editorValue = null;
|
|
4764
|
+
undoManagerValue = null;
|
|
4634
4765
|
started = false;
|
|
4766
|
+
startTask = null;
|
|
4767
|
+
destroyed = false;
|
|
4768
|
+
lifetime = null;
|
|
4769
|
+
hasConnected = false;
|
|
4770
|
+
initialVersionValue = null;
|
|
4771
|
+
localVersionValue = null;
|
|
4772
|
+
pushErrorValue = null;
|
|
4635
4773
|
/**
|
|
4636
|
-
* Latest server oplog version seen on
|
|
4774
|
+
* Latest server oplog version seen on an authoritative HTTP response. Lets `waitForServerAck`
|
|
4637
4775
|
* return without waiting when the server is already known to cover the local
|
|
4638
4776
|
* doc (e.g. nothing was edited since the last confirmed push).
|
|
4639
4777
|
*/
|
|
@@ -4662,14 +4800,14 @@ var MengineDocSession = class {
|
|
|
4662
4800
|
return this.server.subscribePushOutcome(callback);
|
|
4663
4801
|
}
|
|
4664
4802
|
/**
|
|
4665
|
-
* Resolve once the server has durably accepted the local
|
|
4803
|
+
* Resolve once the server has durably accepted the local commits as of
|
|
4666
4804
|
* *now* — the capability an Agent tool needs to answer "did my edit land?"
|
|
4667
4805
|
* before reporting success.
|
|
4668
4806
|
*
|
|
4669
4807
|
* Call it right after the editor ops whose durability matters. It snapshots the
|
|
4670
|
-
* local
|
|
4671
|
-
*
|
|
4672
|
-
*
|
|
4808
|
+
* latest local commit's version (or the loaded baseline before any edit) and
|
|
4809
|
+
* resolves when an authoritative server version covers it. Remote-only
|
|
4810
|
+
* imports do not extend this target, matching the displayed confirmation state.
|
|
4673
4811
|
*
|
|
4674
4812
|
* Version coverage, not byte identity, is the acceptance test — for two reasons:
|
|
4675
4813
|
* the ops may reach the server inside a merged blob rather than as the exact
|
|
@@ -4684,31 +4822,39 @@ var MengineDocSession = class {
|
|
|
4684
4822
|
* @throws {MengineAckFailedError} on timeout, rejection, or transport failure.
|
|
4685
4823
|
*/
|
|
4686
4824
|
async waitForServerAck(options = {}) {
|
|
4687
|
-
if (this.adapterValue == null) throw new Error("mengine doc session is not started");
|
|
4825
|
+
if (this.adapterValue == null || this.lifetime == null) throw new Error("mengine doc session is not started");
|
|
4688
4826
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
4689
|
-
const target = this.manager.connectDoc(this.docId).version();
|
|
4827
|
+
const target = this.confirmationTarget() ?? this.manager.connectDoc(this.docId).version();
|
|
4828
|
+
const signal = this.lifetime.signal;
|
|
4690
4829
|
if (this.serverVVValue != null && serverCovers(this.serverVVValue, target)) return;
|
|
4691
4830
|
await new Promise((resolve, reject) => {
|
|
4692
|
-
|
|
4831
|
+
const cleanup = [];
|
|
4693
4832
|
let done = false;
|
|
4694
4833
|
const settle = (error) => {
|
|
4695
4834
|
if (done) return;
|
|
4696
4835
|
done = true;
|
|
4697
4836
|
clearTimeout(timer);
|
|
4698
|
-
off();
|
|
4837
|
+
cleanup.forEach((off) => off());
|
|
4699
4838
|
if (error == null) resolve();
|
|
4700
4839
|
else reject(error);
|
|
4701
4840
|
};
|
|
4702
4841
|
const timer = setTimeout(() => {
|
|
4703
4842
|
settle(new MengineAckFailedError("timeout", void 0, void 0, `mengine did not acknowledge the update within ${timeoutMs}ms`));
|
|
4704
4843
|
}, timeoutMs);
|
|
4705
|
-
|
|
4844
|
+
const onAbort = () => settle(new MengineAckFailedError("failed", void 0, void 0, "mengine doc session destroyed"));
|
|
4845
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4846
|
+
cleanup.push(() => signal.removeEventListener("abort", onAbort));
|
|
4847
|
+
cleanup.push(this.events.on("syncState", () => {
|
|
4848
|
+
if (this.serverVVValue != null && serverCovers(this.serverVVValue, target)) settle(void 0);
|
|
4849
|
+
}));
|
|
4850
|
+
cleanup.push(this.server.subscribePushOutcome((outcome) => {
|
|
4706
4851
|
if (outcome.kind === "rejected" || outcome.kind === "failed") {
|
|
4707
4852
|
settle(new MengineAckFailedError(outcome.kind, outcome.code, outcome.error, outcome.error?.message ?? `mengine push ${outcome.kind}`));
|
|
4708
4853
|
return;
|
|
4709
4854
|
}
|
|
4710
|
-
|
|
4711
|
-
|
|
4855
|
+
}));
|
|
4856
|
+
if (signal.aborted) onAbort();
|
|
4857
|
+
else this.synchronizer.requestSync(this.docId);
|
|
4712
4858
|
});
|
|
4713
4859
|
}
|
|
4714
4860
|
/**
|
|
@@ -4744,44 +4890,154 @@ var MengineDocSession = class {
|
|
|
4744
4890
|
if (this.adapterValue == null) throw new Error("mengine doc session is not started");
|
|
4745
4891
|
return this.adapterValue.snapshot();
|
|
4746
4892
|
}
|
|
4893
|
+
/** Current-session undo/redo availability. Loading and closed sessions report false. */
|
|
4894
|
+
getUndoState() {
|
|
4895
|
+
return this.undoManagerValue?.getState() ?? {
|
|
4896
|
+
canUndo: false,
|
|
4897
|
+
canRedo: false
|
|
4898
|
+
};
|
|
4899
|
+
}
|
|
4900
|
+
/**
|
|
4901
|
+
* Supplies current availability immediately, then coalesces stack changes in a
|
|
4902
|
+
* microtask. Session teardown publishes empty availability synchronously.
|
|
4903
|
+
*/
|
|
4904
|
+
subscribeUndoState(cb) {
|
|
4905
|
+
const notify = () => cb(this.getUndoState());
|
|
4906
|
+
const off = this.destroyed ? () => {} : this.events.on("undoState", notify);
|
|
4907
|
+
notify();
|
|
4908
|
+
return off;
|
|
4909
|
+
}
|
|
4910
|
+
/**
|
|
4911
|
+
* Undo a local edit immediately; server confirmation remains asynchronous.
|
|
4912
|
+
* On a shared LWW field this writes its prior value, potentially overwriting a
|
|
4913
|
+
* later remote edit to that same field. Returns false if no update is produced.
|
|
4914
|
+
*/
|
|
4915
|
+
undo(options) {
|
|
4916
|
+
if (this.undoManagerValue == null) throw new Error("mengine doc undo manager is not ready");
|
|
4917
|
+
return this.undoManagerValue.undo(options);
|
|
4918
|
+
}
|
|
4919
|
+
/**
|
|
4920
|
+
* Restore the state visible before undo without rerunning external side effects.
|
|
4921
|
+
* Returns false if no update is produced; durable confirmation is separate.
|
|
4922
|
+
*/
|
|
4923
|
+
redo(options) {
|
|
4924
|
+
if (this.undoManagerValue == null) throw new Error("mengine doc undo manager is not ready");
|
|
4925
|
+
return this.undoManagerValue.redo(options);
|
|
4926
|
+
}
|
|
4927
|
+
/**
|
|
4928
|
+
* Combine existing semantic operations (e.g. trim a clip and adjust its volume)
|
|
4929
|
+
* or repeated commits during one drag gesture into a single undo step. A gesture
|
|
4930
|
+
* that only previews locally and commits once on release needs no group.
|
|
4931
|
+
* Each commit is immediately visible locally and enters sync independently;
|
|
4932
|
+
* a later failure does not roll it back.
|
|
4933
|
+
* Always pair with endUndoGroup in a finally block. Conflicting remote imports
|
|
4934
|
+
* can end/split the group early, and unrelated local edits would join it.
|
|
4935
|
+
* Complete asynchronous asset preparation before opening the group.
|
|
4936
|
+
*/
|
|
4937
|
+
beginUndoGroup() {
|
|
4938
|
+
if (this.undoManagerValue == null) throw new Error("mengine doc undo manager is not ready");
|
|
4939
|
+
this.undoManagerValue.beginGroup();
|
|
4940
|
+
}
|
|
4941
|
+
/** End a gesture, including one already split by a conflicting remote import. */
|
|
4942
|
+
endUndoGroup() {
|
|
4943
|
+
if (this.undoManagerValue == null) throw new Error("mengine doc undo manager is not ready");
|
|
4944
|
+
this.undoManagerValue.endGroup();
|
|
4945
|
+
}
|
|
4747
4946
|
/**
|
|
4748
4947
|
* Start the sync stack and connect the document.
|
|
4749
4948
|
*
|
|
4750
|
-
* Returns
|
|
4751
|
-
*
|
|
4752
|
-
*
|
|
4949
|
+
* Returns after DocManager loads a local snapshot, or after the first remote
|
|
4950
|
+
* sync when no local snapshot exists. A valid empty document is ready too.
|
|
4951
|
+
* Cached documents can open before SSE connects; subsequent remote changes
|
|
4952
|
+
* surface through `subscribe`.
|
|
4753
4953
|
*/
|
|
4754
|
-
|
|
4755
|
-
if (this.
|
|
4954
|
+
start() {
|
|
4955
|
+
if (this.destroyed) return Promise.reject(/* @__PURE__ */ new Error("mengine doc session destroyed"));
|
|
4956
|
+
if (this.startTask != null) return this.startTask;
|
|
4756
4957
|
this.started = true;
|
|
4757
|
-
this.
|
|
4758
|
-
this.
|
|
4958
|
+
this.lifetime = new AbortController();
|
|
4959
|
+
this.startTask = this.open().catch((error) => {
|
|
4960
|
+
const wasDestroyed = this.destroyed;
|
|
4961
|
+
this.destroy();
|
|
4962
|
+
if (wasDestroyed) throw new Error("mengine doc session destroyed");
|
|
4963
|
+
throw error;
|
|
4964
|
+
});
|
|
4965
|
+
return this.startTask;
|
|
4966
|
+
}
|
|
4967
|
+
async open() {
|
|
4968
|
+
this.disposables.add(this.server.subscribeResync(() => this.synchronizer.requestSync(this.docId)));
|
|
4969
|
+
this.disposables.add(this.server.subscribeServerVersion((version) => {
|
|
4970
|
+
this.serverVVValue = version;
|
|
4971
|
+
if (this.getSyncState().confirmation === "confirmed") this.pushErrorValue = null;
|
|
4972
|
+
this.emitSyncState();
|
|
4973
|
+
}));
|
|
4974
|
+
this.disposables.add(this.server.subscribePushOutcome((outcome) => {
|
|
4975
|
+
this.pushErrorValue = outcome.kind === "failed" || outcome.kind === "rejected" ? outcome.error ?? /* @__PURE__ */ new Error(`mengine push ${outcome.kind}`) : null;
|
|
4976
|
+
this.emitSyncState();
|
|
4977
|
+
}));
|
|
4978
|
+
this.disposables.add(this.server.connection.onStatusChanged((status) => {
|
|
4979
|
+
if (status === "connected") this.hasConnected = true;
|
|
4980
|
+
this.emitSyncState();
|
|
4981
|
+
}));
|
|
4759
4982
|
this.disposables.add(() => this.local.connection.disconnect());
|
|
4760
4983
|
this.disposables.add(() => this.server.connection.disconnect());
|
|
4984
|
+
this.local.connection.connect();
|
|
4985
|
+
await this.local.connection.waitForConnected().abortOn(this.lifetime.signal);
|
|
4986
|
+
if (this.destroyed) throw new Error("mengine doc session destroyed");
|
|
4987
|
+
this.server.connection.connect();
|
|
4761
4988
|
this.synchronizer.start();
|
|
4762
4989
|
this.manager.start();
|
|
4763
4990
|
this.disposables.add(() => this.synchronizer.stop());
|
|
4764
4991
|
this.disposables.add(() => this.manager.stop());
|
|
4765
4992
|
const doc = this.manager.connectDoc(this.docId);
|
|
4766
|
-
if (this.options.peerId != null) doc.setPeerId(this.options.peerId);
|
|
4767
|
-
this.adapterValue = new MirrorVideoDocumentAdapter(doc);
|
|
4768
|
-
this.editorValue = new SemanticEditor(this.adapterValue);
|
|
4769
4993
|
this.disposables.add(() => this.manager.disconnectDoc(this.docId));
|
|
4994
|
+
if (this.options.peerId != null) doc.setPeerId(this.options.peerId);
|
|
4995
|
+
this.adapterValue = new MirrorVideoDocumentAdapter(doc, this.mutationGuard);
|
|
4770
4996
|
const unsubscribe = doc.subscribe((event) => {
|
|
4771
4997
|
if (this.adapterValue == null) return;
|
|
4998
|
+
if (event.by === "local") this.localVersionValue = doc.version();
|
|
4999
|
+
this.emitSyncState();
|
|
4772
5000
|
this.events.emit("update", {
|
|
4773
5001
|
source: event.by === "local" ? "local" : "remote",
|
|
4774
5002
|
snapshot: this.adapterValue.snapshot()
|
|
4775
5003
|
});
|
|
4776
5004
|
});
|
|
4777
5005
|
this.disposables.add(unsubscribe);
|
|
4778
|
-
this.disposables.add(this.manager.onDocStateChange(this.docId, (state) =>
|
|
4779
|
-
|
|
4780
|
-
|
|
5006
|
+
this.disposables.add(this.manager.onDocStateChange(this.docId, (state) => {
|
|
5007
|
+
this.events.emit("state", state);
|
|
5008
|
+
this.emitSyncState();
|
|
4781
5009
|
}));
|
|
4782
|
-
await this.
|
|
5010
|
+
await this.waitForInitialLoad();
|
|
5011
|
+
if (this.destroyed) throw new Error("mengine doc session destroyed");
|
|
5012
|
+
assertValidVideoDocument(this.snapshot());
|
|
5013
|
+
this.undoManagerValue = new DocumentUndoManager(doc, this.mutationGuard, () => this.events.emit("undoState"));
|
|
5014
|
+
this.editorValue = new SemanticEditor(this.adapterValue);
|
|
5015
|
+
this.initialVersionValue = doc.version();
|
|
5016
|
+
this.emitSyncState();
|
|
4783
5017
|
return this.snapshot();
|
|
4784
5018
|
}
|
|
5019
|
+
getSyncState() {
|
|
5020
|
+
const state = this.getState();
|
|
5021
|
+
const target = this.confirmationTarget();
|
|
5022
|
+
const confirmation = this.serverVVValue == null || target == null ? this.localVersionValue == null ? "unknown" : "pending" : serverCovers(this.serverVVValue, target) ? "confirmed" : "pending";
|
|
5023
|
+
const connection = !this.started ? "disconnected" : this.server.connection.status === "connected" ? "connected" : this.hasConnected || this.server.connection.error != null ? "disconnected" : "connecting";
|
|
5024
|
+
return {
|
|
5025
|
+
confirmation,
|
|
5026
|
+
phase: connection === "disconnected" ? "offline" : this.pushErrorValue != null || state.error != null ? "failed" : connection === "connecting" ? "connecting" : state.syncing ? "syncing" : "idle"
|
|
5027
|
+
};
|
|
5028
|
+
}
|
|
5029
|
+
confirmationTarget() {
|
|
5030
|
+
return this.localVersionValue ?? this.initialVersionValue;
|
|
5031
|
+
}
|
|
5032
|
+
/** Immediately supplies current state; unsubscribe when the host changes docs. */
|
|
5033
|
+
subscribeSyncState(cb) {
|
|
5034
|
+
const off = this.events.on("syncState", cb);
|
|
5035
|
+
cb(this.getSyncState());
|
|
5036
|
+
return off;
|
|
5037
|
+
}
|
|
5038
|
+
emitSyncState() {
|
|
5039
|
+
this.events.emit("syncState", this.getSyncState());
|
|
5040
|
+
}
|
|
4785
5041
|
subscribe(cb) {
|
|
4786
5042
|
return this.events.on("update", cb);
|
|
4787
5043
|
}
|
|
@@ -4792,40 +5048,48 @@ var MengineDocSession = class {
|
|
|
4792
5048
|
return this.manager.getDocState(this.docId);
|
|
4793
5049
|
}
|
|
4794
5050
|
destroy() {
|
|
5051
|
+
if (this.destroyed) return;
|
|
5052
|
+
this.destroyed = true;
|
|
5053
|
+
this.mutationGuard.close();
|
|
5054
|
+
this.started = false;
|
|
5055
|
+
this.lifetime?.abort();
|
|
5056
|
+
this.undoManagerValue?.dispose();
|
|
5057
|
+
this.undoManagerValue = null;
|
|
5058
|
+
this.events.emit("undoState");
|
|
4795
5059
|
this.disposables.dispose();
|
|
5060
|
+
this.emitSyncState();
|
|
4796
5061
|
this.events.clearAll();
|
|
5062
|
+
this.adapterValue?.dispose();
|
|
4797
5063
|
this.adapterValue = null;
|
|
4798
5064
|
this.editorValue = null;
|
|
4799
5065
|
this.started = false;
|
|
4800
5066
|
}
|
|
4801
|
-
/**
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
5067
|
+
/** Local absence needs a remote sync; empty business content does not. */
|
|
5068
|
+
async waitForInitialLoad() {
|
|
5069
|
+
const signal = this.lifetime.signal;
|
|
5070
|
+
await new Promise((resolve, reject) => {
|
|
5071
|
+
let off = () => {};
|
|
5072
|
+
let offConnection = () => {};
|
|
5073
|
+
const finish = (error) => {
|
|
5074
|
+
off();
|
|
5075
|
+
offConnection();
|
|
5076
|
+
signal.removeEventListener("abort", onAbort);
|
|
5077
|
+
if (error != null) reject(error);
|
|
5078
|
+
else resolve();
|
|
5079
|
+
};
|
|
5080
|
+
const onAbort = () => finish(/* @__PURE__ */ new Error("mengine doc session destroyed"));
|
|
5081
|
+
const check = (state) => {
|
|
5082
|
+
if (state.loaded && (state.loadedFromLocal || state.synced)) finish();
|
|
5083
|
+
else if (state.error != null) finish(new Error(state.error));
|
|
5084
|
+
else if (state.loaded && this.server.connection.error != null) finish(this.server.connection.error);
|
|
5085
|
+
};
|
|
5086
|
+
off = this.onStateChange(check);
|
|
5087
|
+
offConnection = this.server.connection.onStatusChanged(() => check(this.getState()));
|
|
5088
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5089
|
+
if (signal.aborted) onAbort();
|
|
5090
|
+
else check(this.getState());
|
|
4819
5091
|
});
|
|
4820
5092
|
}
|
|
4821
|
-
hasContent() {
|
|
4822
|
-
if (this.adapterValue == null) return false;
|
|
4823
|
-
try {
|
|
4824
|
-
return this.adapterValue.hasContent();
|
|
4825
|
-
} catch {
|
|
4826
|
-
return false;
|
|
4827
|
-
}
|
|
4828
|
-
}
|
|
4829
5093
|
};
|
|
4830
5094
|
/**
|
|
4831
5095
|
* True when the *encoded* server version `serverVV` includes everything in
|
|
@@ -4849,4 +5113,4 @@ function serverCovers(serverVV, target) {
|
|
|
4849
5113
|
return covers(server, target);
|
|
4850
5114
|
}
|
|
4851
5115
|
//#endregion
|
|
4852
|
-
export { DEFAULT_UNIT_TIME_MS,
|
|
5116
|
+
export { DEFAULT_UNIT_TIME_MS, EntityGraphHttpClient, EntityTimelineEditor, EntityTimelineProjectionError, IMPLEMENTED_SEMANTIC_OP_KINDS, LANE_KINDS_IN_STACK_ORDER, LORO_ENTITY_SCHEMA, LoroEntityDocument, LoroEntityDraft, ManualSyncDoc, MedeoHttpDocStorage, MemoryDocStorage, MengineAckFailedError, MengineDocSession, MengineHttpClient, MengineHttpRequestError, MenginePushRejectedError, MirrorVideoDocumentAdapter, PlainMemoryAdapter, SchemaValidator, ScriptCompositionError, SemanticEditor, TIMELINE_SKELETON_DURATION_MS, ValidationError, VideoDocumentValidationError, applyEntityRows, applyFieldChanges, arrangeMainTrackSeamlessly, assembleCaptionContent, assembleEntityContent, assemblePhoneticScriptContent, assembleScriptText, assertCanonicalEditorResources, assertEntityProjectionPreservesLegacy, assertFieldChanges, assertMediaAssetWritePolicy, assertValidVideoDocument, audioScriptAssetContent, audioScriptAssetFields, audioScriptAssetHash, base64ToBytes, buildInitialVideoDocument, buildSpeechHostMap, bytesToBase64, cascadeAfterVideoClipChanges, compileEntityRows, createEditSandbox, createMirrorVideoDocument, createMirrorVideoDocumentAdapter, createPlainMemoryAdapter, decodeDocVersionMark, derivePositionFromAbs, effectiveVideoClipDurationMs, encodeDocVersionMark, ensureEditorFoundation, ensureLaneTrack, fillMainTrackTimeGaps, findComposedAudioScript, findLaneTrack, fromVideoDocument, generatePartId, getAt, hasAudioScriptAsset, hostForAbsMs, importMediaAsset, isEmptyVideoClip, isImplementedSemanticOpKind, isMap, laneTrackId, mainTrackRanges, migrateLegacyTimelineToEntities, partDurationMs, partUnionSchema, prepareCaptionContent, projectEntityTimeline, readDocumentAudioScript, readEntityDocument, readMainTrackItems, readMengineEventStream, readPart, readPartDurationMs, readVideoDocumentFromDraft, reassignSpeechesToVideoClipsByTime, recalculateTimelineDuration, relativePositionForAbs, replayJournal, resolveAllSpeechOverlaps, resolveEntityTimelineLayout, resolveFieldPath, resolveSpeechOverlapByShiftingVideos, safeDurationMs, schemas_exports as schemas, selectAudioScriptSegment, snapshotToPlain, solveVideoDocument, speedOf, syncAggregatedClipsTimePosition, toVideoDocument, validateVideoDocument, videoDocumentMirrorSchema, videoDocumentSchema };
|