@mengine/medeo-client 2.0.1-alpha.6 → 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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { $ as ensureLaneTrack, A as assembleEntityContent, B as hasSequence, C as projectEntityTimeline, Ct as base64ToBytes, D as decodeEntityRelationRows, E as EntityTimelineProjectionError, F as updateEntityFields, G as fromVideoDocument, H as buildInitialVideoDocument, I as variantBaseEntityIds, J as assertValidVideoDocument, K as toVideoDocument, L as isJsonObject, M as assembleScriptText, N as findComposedAudioScript, O as ScriptCompositionError, P as selectAudioScriptSegment, Q as LANE_KINDS_IN_STACK_ORDER, R as createEntityId, S as resolveFieldPath, St as videoDocumentMirrorSchema, T as resolveEntityTimelineLayout, U as buildSpeechHostMap, V as isMediaAssetVariantKind, W as derivePositionFromAbs, X as partUnionSchema, Y as validateVideoDocument, Z as videoDocumentSchema, _ as audioScriptAssetHash, _t as VIDEO_DOCUMENT_SCHEMA_VERSION, a as applyEntityTimelineProjection, at as fillMainTrackTimeGaps, b as applyFieldChanges, bt as partUnionToDraft, c as writeVideoDocumentToDraft, ct as resolveAllSpeechOverlaps, d as LoroEntityDocument, dt as TIMELINE_SKELETON_DURATION_MS, et as findLaneTrack, f as LoroEntityDraft, ft as isEmptyVideoClip, g as audioScriptAssetFields, gt as ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION, h as audioScriptAssetContent, ht as DEFAULT_UNIT_TIME_MS, i as MirrorVideoDocumentAdapter, it as arrangeMainTrackSeamlessly, j as assemblePhoneticScriptContent, k as assembleCaptionContent, l as readVideoDocumentFromDraft, lt as resolveSpeechOverlapByShiftingVideos, m as entityOrderGroups, mt as safeDurationMs, n as createPlainMemoryAdapter, nt as solveVideoDocument, o as createMirrorVideoDocument, ot as reassignSpeechesToVideoClipsByTime, p as readEntityDocument, pt as partDurationMs, q as VideoDocumentValidationError, r as generatePartId, rt as cascadeAfterVideoClipChanges, s as createMirrorVideoDocumentAdapter, st as recalculateTimelineDuration, t as PlainMemoryAdapter, tt as laneTrackId, u as LORO_ENTITY_SCHEMA, ut as syncAggregatedClipsTimePosition, v as hasAudioScriptAsset, vt as effectiveVideoClipDurationMs, w as EntityProjectionGraph, wt as bytesToBase64, x as assertFieldChanges, xt as recordEntries, y as equalJson, yt as speedOf, z as createRelationId } from "./document-N-8GwtOX.js";
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, onUpdate);
183
+ handleSseFrame(frame, options);
193
184
  splitAt = findSseFrameBoundary(buffer);
194
185
  }
195
186
  }
196
187
  }
197
- function handleSseFrame(frame, onUpdate) {
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.meta.schema_version === "video-document/entity-projection-v1") fail("An entity projection cannot be migrated as legacy state");
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, legacy.meta), 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.meta.schema_version !== "video-document/entity-projection-v1") fail("Expected an entity projection for migration");
2229
+ if (!hasResolvedPlacement(projection)) fail("Expected an entity projection for migration");
2235
2230
  const normalizedMainRanges = overlapNormalizedMainRanges(legacy);
2236
- const compatibilityFacts = (document) => {
2237
- const { version: _version, ...view } = fromVideoDocument(document);
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
- const { schema_version: _schemaVersion, version: _metaVersion, ...meta } = document.meta;
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, instead of `MengineDocSession.waitForContent()`'s behavior of
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.import(base64ToBytes(response.snapshot));
4086
- const manualSyncDoc = new ManualSyncDoc(options.client, doc);
4087
- manualSyncDoc.watermark = manualSyncDoc.serverVVFrom(response.version.server_vv) ?? doc.oplogVersion();
4088
- return manualSyncDoc;
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 document projected to the legacy `VideoDraft` read shape. */
4108
- draft() {
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". Under mengine `meta.version` never
4130
- * changes, so that detector would go permanently silent; comparing version
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
- * Each SSE frame's updates are forwarded verbatim via `onUpdate`; this
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 = decodeServerVV(response.version?.server_vv);
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
- * It assembles the engine sync stack around one Medeo document:
4606
- *
4607
- * local DocStorage ─┐
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 a push outcome. Lets `waitForServerAck`
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 document state as of
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 doc's current version and resolves on the first push outcome whose
4671
- * `serverVV` covers that version, i.e. the server log is at least as advanced as
4672
- * the local doc was when this was called.
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
- let off = () => {};
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
- off = this.server.subscribePushOutcome((outcome) => {
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
- if (outcome.serverVV != null && serverCovers(outcome.serverVV, target)) settle(void 0);
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 once the local snapshot has loaded into the Loro doc so callers can
4751
- * read an initial snapshot. Remote convergence continues in the background and
4752
- * surfaces through `subscribe`.
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
- async start() {
4755
- if (this.started) return this.snapshot();
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.local.connection.connect();
4758
- this.server.connection.connect();
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) => this.events.emit("state", state)));
4779
- this.disposables.add(this.server.subscribePushOutcome((outcome) => {
4780
- if (outcome.serverVV != null) this.serverVVValue = outcome.serverVV;
5006
+ this.disposables.add(this.manager.onDocStateChange(this.docId, (state) => {
5007
+ this.events.emit("state", state);
5008
+ this.emitSyncState();
4781
5009
  }));
4782
- await this.waitForContent();
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
- * Resolve once the Loro doc holds the document root.
4803
- *
4804
- * `DocManager` loads from local storage, which starts empty for a fresh
4805
- * client; the server snapshot arrives asynchronously via the first sync job.
4806
- * `loaded` only means the local load ran, so we wait for actual content
4807
- * (populated schema roots) instead, surfaced by the doc subscription set up
4808
- * in `start()`.
4809
- */
4810
- async waitForContent() {
4811
- if (this.hasContent()) return;
4812
- await new Promise((resolve) => {
4813
- const off = this.events.on("update", () => {
4814
- if (this.hasContent()) {
4815
- off();
4816
- resolve();
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, ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION, 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, VIDEO_DOCUMENT_SCHEMA_VERSION, ValidationError, VideoDocumentValidationError, applyEntityRows, applyEntityTimelineProjection, 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, writeVideoDocumentToDraft };
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 };