@mengine/medeo-client 1.2.1-alpha.1 → 1.2.1-alpha.11

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.
@@ -87,8 +87,7 @@ function jsonTransform() {
87
87
  const trackItem = schema.LoroMap({
88
88
  part_id: schema.String(),
89
89
  time_position: schema.String().transform(jsonTransform()),
90
- fallback_abs_ms: schema.Number({ required: false }),
91
- duration_override_ms: schema.Number({ required: false })
90
+ fallback_abs_ms: schema.Number({ required: false })
92
91
  });
93
92
  const track = schema.LoroMap({
94
93
  id: schema.String(),
@@ -774,8 +773,7 @@ const trackItemTimePositionSchema = z.discriminatedUnion("mode", [
774
773
  const trackItemSchema = z.object({
775
774
  part_id: z.string(),
776
775
  time_position: trackItemTimePositionSchema,
777
- fallback_abs_ms: finiteNumber.optional(),
778
- duration_override_ms: z.number().finite().positive().optional()
776
+ fallback_abs_ms: finiteNumber.optional()
779
777
  }).passthrough();
780
778
  const trackSchema = z.object({
781
779
  id: z.string().optional(),
@@ -1328,7 +1326,7 @@ function fromVideoDocument(document) {
1328
1326
  above_main_tracks: aboveTracks.map((t) => draftTrack(t, view.absByPartId)),
1329
1327
  below_main_tracks: belowTracks.map((t) => draftTrack(t, view.absByPartId)),
1330
1328
  part_aggregations: view.aggregations,
1331
- part_library: toReadViewPartLibrary(view.partLibrary, view.durationMs, view.derivedFillerPartIds, captionDurationOverrides(document)),
1329
+ part_library: toReadViewPartLibrary(view.partLibrary, view.durationMs, view.derivedFillerPartIds),
1332
1330
  version: document.meta.version
1333
1331
  };
1334
1332
  }
@@ -1349,7 +1347,7 @@ function fromVideoDocument(document) {
1349
1347
  * and they are not part of the document (see `fromVideoDocument`). An empty clip a
1350
1348
  * writer placed deliberately is NOT in that set and is projected normally.
1351
1349
  */
1352
- function toReadViewPartLibrary(partLibrary, durationMs, derivedFillerPartIds, captionDurationByPartId) {
1350
+ function toReadViewPartLibrary(partLibrary, durationMs, derivedFillerPartIds) {
1353
1351
  const out = {};
1354
1352
  for (const [partId, part] of Object.entries(partLibrary)) {
1355
1353
  if (derivedFillerPartIds.has(partId)) continue;
@@ -1369,7 +1367,7 @@ function toReadViewPartLibrary(partLibrary, durationMs, derivedFillerPartIds, ca
1369
1367
  const caption = clone(part.caption);
1370
1368
  out[partId] = { caption: {
1371
1369
  ...caption,
1372
- duration_ms: captionDurationByPartId.get(partId) ?? caption.initial_duration_ms
1370
+ duration_ms: caption.initial_duration_ms
1373
1371
  } };
1374
1372
  } else if (part.bgm != null) out[partId] = { bgm: {
1375
1373
  ...clone(part.bgm),
@@ -1378,14 +1376,6 @@ function toReadViewPartLibrary(partLibrary, durationMs, derivedFillerPartIds, ca
1378
1376
  }
1379
1377
  return out;
1380
1378
  }
1381
- function captionDurationOverrides(document) {
1382
- const result = /* @__PURE__ */ new Map();
1383
- for (const track of document.tracks ?? []) {
1384
- if (track.parts_kind !== "caption") continue;
1385
- for (const item of track.items ?? []) if (item.duration_override_ms != null) result.set(item.part_id, item.duration_override_ms);
1386
- }
1387
- return result;
1388
- }
1389
1379
  /** Deep-clone a part payload, dropping the derived read-view `duration_ms`. */
1390
1380
  function omitDurationMs(part) {
1391
1381
  const { duration_ms: _drop, ...rest } = clone(part);
@@ -1548,7 +1538,6 @@ function rowToTrack(row) {
1548
1538
  time_position: item.time_position
1549
1539
  };
1550
1540
  if (item.fallback_abs_ms != null) result.fallback_abs_ms = item.fallback_abs_ms;
1551
- if (item.duration_override_ms != null) result.duration_override_ms = item.duration_override_ms;
1552
1541
  return result;
1553
1542
  })
1554
1543
  };
@@ -1657,8 +1646,7 @@ function toTrackRow(track) {
1657
1646
  items: (track.items ?? []).map((item) => ({
1658
1647
  part_id: item.part_id,
1659
1648
  time_position: item.time_position,
1660
- fallback_abs_ms: item.fallback_abs_ms,
1661
- duration_override_ms: item.duration_override_ms
1649
+ fallback_abs_ms: item.fallback_abs_ms
1662
1650
  }))
1663
1651
  };
1664
1652
  }
@@ -545,8 +545,6 @@ interface TrackItem {
545
545
  part_id: string;
546
546
  time_position: TrackItemTimePosition;
547
547
  fallback_abs_ms?: number | undefined;
548
- /** Presentation window for this placement; currently used by caption resize. */
549
- duration_override_ms?: number | undefined;
550
548
  }
551
549
  interface Track {
552
550
  id: string | undefined;
@@ -1322,14 +1320,6 @@ declare const replaceVideoClipSequenceInputSchema: z.ZodObject<{
1322
1320
  }, z.core.$strip>;
1323
1321
  type ReplaceVideoClipSequenceInput = z.infer<typeof replaceVideoClipSequenceInputSchema>;
1324
1322
  //#endregion
1325
- //#region src/editor/schemas/resize-caption.d.ts
1326
- /** Resize one caption placement without changing its cue/source duration. */
1327
- declare const resizeCaptionInputSchema: z.ZodObject<{
1328
- caption_id: z.ZodString;
1329
- duration_ms: z.ZodNumber;
1330
- }, z.core.$strip>;
1331
- type ResizeCaptionInput = z.infer<typeof resizeCaptionInputSchema>;
1332
- //#endregion
1333
1323
  //#region src/editor/schemas/set-bgm.d.ts
1334
1324
  /**
1335
1325
  * Set the document BGM. The media's stable result (storage key) arrives
@@ -1548,7 +1538,7 @@ type SpeechAsset = z.infer<typeof speechAssetSchema>;
1548
1538
  type CaptionAsset = z.infer<typeof captionAssetSchema>;
1549
1539
  type SpeechAssets = z.infer<typeof speechAssetsSchema>;
1550
1540
  declare namespace index_d_exports {
1551
- export { AddSpeechesInput, AddVideoClipsInput, AdjustBgmVolumeInput, AdjustSpeechVolumeInput, AdjustVideoClipDurationInput, AdjustVideoClipVolumeInput, AnchoredDeletePolicy, AnchoredReplacePolicy, CaptionAsset, ChangeSpeechScriptInput, ChangeSpeechVoiceInput, DeleteBgmInput, DeleteSpeechesInput, DeleteVideoClipsInput, MoveAnchor, MoveSpeechesInput, MoveVideoClipsByAnchorInput, MoveVideoClipsInput, MovedClipAnchoredPolicy, ReplaceVideoClipContentInput, ReplaceVideoClipSequenceInput, ResizeCaptionInput, SetBgmInput, SetCaptionStyleInput, SetCaptionVisibilityInput, SetVideoClipSpeedShiftInput, SpeechAsset, SpeechAssets, addSpeechesInputSchema, addVideoClipsInputSchema, adjustBgmVolumeInputSchema, adjustSpeechVolumeInputSchema, adjustVideoClipDurationInputSchema, adjustVideoClipVolumeInputSchema, anchoredDeletePolicySchema, anchoredReplacePolicySchema, changeSpeechScriptInputSchema, changeSpeechVoiceInputSchema, clipIdSchema, clipIdsSchema, deleteBgmInputSchema, deleteSpeechesInputSchema, deleteVideoClipsInputSchema, mediaIdSchema, moveAnchorSchema, moveSpeechesInputSchema, moveVideoClipsByAnchorInputSchema, moveVideoClipsInputSchema, movedClipAnchoredPolicySchema, positiveMsSchema, replaceVideoClipContentInputSchema, replaceVideoClipSequenceInputSchema, resizeCaptionInputSchema, setBgmInputSchema, setCaptionStyleInputSchema, setCaptionVisibilityInputSchema, setVideoClipSpeedShiftInputSchema, speechAssetsSchema, speechIdSchema, speechIdsSchema, speedShiftSchema, timelineMsSchema, voiceSchema, volumeSchema };
1541
+ export { AddSpeechesInput, AddVideoClipsInput, AdjustBgmVolumeInput, AdjustSpeechVolumeInput, AdjustVideoClipDurationInput, AdjustVideoClipVolumeInput, AnchoredDeletePolicy, AnchoredReplacePolicy, CaptionAsset, ChangeSpeechScriptInput, ChangeSpeechVoiceInput, DeleteBgmInput, DeleteSpeechesInput, DeleteVideoClipsInput, MoveAnchor, MoveSpeechesInput, MoveVideoClipsByAnchorInput, MoveVideoClipsInput, MovedClipAnchoredPolicy, ReplaceVideoClipContentInput, ReplaceVideoClipSequenceInput, SetBgmInput, SetCaptionStyleInput, SetCaptionVisibilityInput, SetVideoClipSpeedShiftInput, SpeechAsset, SpeechAssets, addSpeechesInputSchema, addVideoClipsInputSchema, adjustBgmVolumeInputSchema, adjustSpeechVolumeInputSchema, adjustVideoClipDurationInputSchema, adjustVideoClipVolumeInputSchema, anchoredDeletePolicySchema, anchoredReplacePolicySchema, changeSpeechScriptInputSchema, changeSpeechVoiceInputSchema, clipIdSchema, clipIdsSchema, deleteBgmInputSchema, deleteSpeechesInputSchema, deleteVideoClipsInputSchema, mediaIdSchema, moveAnchorSchema, moveSpeechesInputSchema, moveVideoClipsByAnchorInputSchema, moveVideoClipsInputSchema, movedClipAnchoredPolicySchema, positiveMsSchema, replaceVideoClipContentInputSchema, replaceVideoClipSequenceInputSchema, setBgmInputSchema, setCaptionStyleInputSchema, setCaptionVisibilityInputSchema, setVideoClipSpeedShiftInputSchema, speechAssetsSchema, speechIdSchema, speechIdsSchema, speedShiftSchema, timelineMsSchema, voiceSchema, volumeSchema };
1552
1542
  }
1553
1543
  //#endregion
1554
1544
  //#region src/editor/schema-validator.d.ts
@@ -1600,7 +1590,6 @@ declare class SchemaValidator {
1600
1590
  validateSetBgm(input: SetBgmInput, _doc: SnapshotReadable): void;
1601
1591
  validateDeleteBgm(input: DeleteBgmInput, _doc: SnapshotReadable): void;
1602
1592
  validateSetCaptionVisibility(input: SetCaptionVisibilityInput, _doc: SnapshotReadable): void;
1603
- validateResizeCaption(input: ResizeCaptionInput, doc: SnapshotReadable): void;
1604
1593
  validateSetCaptionStyle(input: SetCaptionStyleInput, _doc: SnapshotReadable): void;
1605
1594
  private parse;
1606
1595
  private mainTrackIds;
@@ -1649,7 +1638,7 @@ declare class SchemaValidator {
1649
1638
  * side-effect payload contract in
1650
1639
  * `docs/projects/medeo-integration/results/phase-4-side-effect-payload-contract.md`.
1651
1640
  */
1652
- type ImplementedSemanticOpKind = 'MoveVideoClips' | 'MoveVideoClipsByAnchor' | 'DeleteVideoClips' | 'AddVideoClips' | 'AdjustVideoClipVolume' | 'SetVideoClipSpeedShift' | 'ReplaceVideoClipContent' | 'ReplaceVideoClipSequence' | 'AdjustVideoClipDuration' | 'AddSpeeches' | 'DeleteSpeeches' | 'MoveSpeeches' | 'ChangeSpeechScript' | 'ChangeSpeechVoice' | 'AdjustSpeechVolume' | 'ResizeCaption' | 'SetCaptionVisibility' | 'SetCaptionStyle' | 'SetBgm' | 'DeleteBgm' | 'AdjustBgmVolume';
1641
+ type ImplementedSemanticOpKind = 'MoveVideoClips' | 'MoveVideoClipsByAnchor' | 'DeleteVideoClips' | 'AddVideoClips' | 'AdjustVideoClipVolume' | 'SetVideoClipSpeedShift' | 'ReplaceVideoClipContent' | 'ReplaceVideoClipSequence' | 'AdjustVideoClipDuration' | 'AddSpeeches' | 'DeleteSpeeches' | 'MoveSpeeches' | 'ChangeSpeechScript' | 'ChangeSpeechVoice' | 'AdjustSpeechVolume' | 'SetCaptionVisibility' | 'SetCaptionStyle' | 'SetBgm' | 'DeleteBgm' | 'AdjustBgmVolume';
1653
1642
  /**
1654
1643
  * Operations on the roadmap but NOT yet implemented by `SemanticEditor`. Empty:
1655
1644
  * Phase 4 covers every op with a real entry point. Kept as a named type so the
@@ -1711,7 +1700,7 @@ interface CommitOptions {
1711
1700
  /** Author of this op; omitted for writes with no acting user (bootstrap, repair). */
1712
1701
  actor?: OpActor;
1713
1702
  }
1714
- type SemanticOpInput = MoveVideoClipsInput | MoveVideoClipsByAnchorInput | DeleteVideoClipsInput | AddVideoClipsInput | AdjustVideoClipVolumeInput | AdjustSpeechVolumeInput | AdjustBgmVolumeInput | SetVideoClipSpeedShiftInput | ReplaceVideoClipContentInput | ReplaceVideoClipSequenceInput | AdjustVideoClipDurationInput | AddSpeechesInput | DeleteSpeechesInput | MoveSpeechesInput | ResizeCaptionInput | SetCaptionVisibilityInput | SetCaptionStyleInput | SetBgmInput | DeleteBgmInput;
1703
+ type SemanticOpInput = MoveVideoClipsInput | MoveVideoClipsByAnchorInput | DeleteVideoClipsInput | AddVideoClipsInput | AdjustVideoClipVolumeInput | AdjustSpeechVolumeInput | AdjustBgmVolumeInput | SetVideoClipSpeedShiftInput | ReplaceVideoClipContentInput | ReplaceVideoClipSequenceInput | AdjustVideoClipDurationInput | AddSpeechesInput | DeleteSpeechesInput | MoveSpeechesInput | SetCaptionVisibilityInput | SetCaptionStyleInput | SetBgmInput | DeleteBgmInput;
1715
1704
  type SemanticOpName = ImplementedSemanticOpKind;
1716
1705
  /** Audit metadata committed alongside an op's writes. */
1717
1706
  interface TransactAudit {
@@ -1857,8 +1846,6 @@ declare class SemanticEditor {
1857
1846
  deleteBgm(input: DeleteBgmInput, options?: CommitOptions): Promise<void>;
1858
1847
  /** Toggle caption visibility (caption track `is_hidden`). */
1859
1848
  setCaptionVisibility(input: SetCaptionVisibilityInput, options?: CommitOptions): Promise<void>;
1860
- /** Resize the caption placement window; cue/source duration stays immutable. */
1861
- resizeCaption(input: ResizeCaptionInput, options?: CommitOptions): Promise<void>;
1862
1849
  /**
1863
1850
  * Set the document-wide caption style. GLOBAL (no `caption_id`): the patch is
1864
1851
  * merged onto EVERY caption part's `style`, mirroring the FE, which applies a
@@ -2043,7 +2030,6 @@ declare const videoDocumentSchema: z.ZodObject<{
2043
2030
  offsetMs: z.ZodNumber;
2044
2031
  }, z.core.$loose>], "mode">;
2045
2032
  fallback_abs_ms: z.ZodOptional<z.ZodNumber>;
2046
- duration_override_ms: z.ZodOptional<z.ZodNumber>;
2047
2033
  }, z.core.$loose>>>;
2048
2034
  }, z.core.$loose>>>;
2049
2035
  part_library: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as VideoDocumentSchemaVersion, A as assertValidVideoDocument, B as BgmPart, C as index_d_exports, D as VideoDocumentMirrorSchema, E as VideoDocumentDraft, F as SpeechHostMap, G as SpeechPart, H as DEFAULT_UNIT_TIME_MS, I as buildSpeechHostMap, J as TrackItem, K as Timeline, L as derivePositionFromAbs, M as InitialDocumentFacts, N as buildInitialVideoDocument, O as videoDocumentMirrorSchema, P as DerivedItemPosition, Q as VideoDocument, R as fromVideoDocument, S as ValidationError, T as TrackItemDraft, U as PartKind, V as CaptionPart, W as PartUnion, X as VIDEO_DOCUMENT_SCHEMA_VERSION, Y as TrackItemTimePosition, Z as VideoClipPart, _ as PlannedSemanticOpKind, a as MirrorVideoDocumentOptions, at as CaptionPart$1, b as SchemaValidator, c as CommitOptions, ct as SpeedShift, d as SemanticEditor, dt as TrackItem$1, et as VideoDocumentValidationIssue, f as SemanticOpInput, g as ImplementedSemanticOpKind, h as IMPLEMENTED_SEMANTIC_OP_KINDS, i as MirrorVideoDocumentAdapter, it as Attachment, j as validateVideoDocument, k as VideoDocumentValidationError, l as OpActor, lt as Timeline$1, m as TransactAudit, n as videoDocumentSchema, nt as VideoDraft, o as createMirrorVideoDocument, ot as CaptionStyle, p as SemanticOpName, q as Track, r as readVideoDocumentFromDraft, rt as VideoDraftPartUnion, s as createMirrorVideoDocumentAdapter, st as PartAggregation, t as partUnionSchema, tt as VideoDocumentValidationIssueCode, u as SemanticDocumentAdapter, ut as Track$1, v as SemanticOpKind, w as TrackDraft, x as SnapshotReadable, y as isImplementedSemanticOpKind, z as toVideoDocument } from "./index-CZlTzzrm.js";
1
+ import { $ as VideoDocumentSchemaVersion, A as assertValidVideoDocument, B as BgmPart, C as index_d_exports, D as VideoDocumentMirrorSchema, E as VideoDocumentDraft, F as SpeechHostMap, G as SpeechPart, H as DEFAULT_UNIT_TIME_MS, I as buildSpeechHostMap, J as TrackItem, K as Timeline, L as derivePositionFromAbs, M as InitialDocumentFacts, N as buildInitialVideoDocument, O as videoDocumentMirrorSchema, P as DerivedItemPosition, Q as VideoDocument, R as fromVideoDocument, S as ValidationError, T as TrackItemDraft, U as PartKind, V as CaptionPart, W as PartUnion, X as VIDEO_DOCUMENT_SCHEMA_VERSION, Y as TrackItemTimePosition, Z as VideoClipPart, _ as PlannedSemanticOpKind, a as MirrorVideoDocumentOptions, at as CaptionPart$1, b as SchemaValidator, c as CommitOptions, ct as SpeedShift, d as SemanticEditor, dt as TrackItem$1, et as VideoDocumentValidationIssue, f as SemanticOpInput, g as ImplementedSemanticOpKind, h as IMPLEMENTED_SEMANTIC_OP_KINDS, i as MirrorVideoDocumentAdapter, it as Attachment, j as validateVideoDocument, k as VideoDocumentValidationError, l as OpActor, lt as Timeline$1, m as TransactAudit, n as videoDocumentSchema, nt as VideoDraft, o as createMirrorVideoDocument, ot as CaptionStyle, p as SemanticOpName, q as Track, r as readVideoDocumentFromDraft, rt as VideoDraftPartUnion, s as createMirrorVideoDocumentAdapter, st as PartAggregation, t as partUnionSchema, tt as VideoDocumentValidationIssueCode, u as SemanticDocumentAdapter, ut as Track$1, v as SemanticOpKind, w as TrackDraft, x as SnapshotReadable, y as isImplementedSemanticOpKind, z as toVideoDocument } from "./index-DWVYLTjv.js";
2
2
  import { PeerID } from "loro-crdt";
3
3
  import { DocState } from "@mengine/sync";
4
4
  import { BaseDocStorage, Connection, DocDiff, DocPushReceipt, DocSnapshotRecord, DocStorage, DocStorageOptions, DocUpdate, DocUpdateRecord } from "@mengine/storage";
@@ -296,7 +296,9 @@ type PushResultKind = 'ack' | 'duplicate' | 'rejected' | 'failed' | 'nothing_to_
296
296
  */
297
297
  interface PushResult {
298
298
  kind: PushResultKind;
299
- /** Server-allocated sequence number; only present for `ack`. */
299
+ /** Server document sequence observed after a successful `ack` or `duplicate`. */
300
+ mengineUpdateSeq?: number | undefined;
301
+ /** @deprecated Ack-only sequence retained until consumers adopt `mengineUpdateSeq`. */
300
302
  updateSeq?: number | undefined;
301
303
  /** Whether the server held ops the local document lacked. See above. */
302
304
  collaborated: boolean;
@@ -575,6 +577,9 @@ declare class MedeoHttpDocStorage implements DocStorage {
575
577
  * tool-level ack wait) subscribe here.
576
578
  */
577
579
  subscribePushOutcome(callback: (outcome: PushOutcome) => void): () => void;
580
+ /** Authoritative HTTP reads and accepted pushes, never inferred from SSE bytes. */
581
+ subscribeServerVersion(callback: (version: Uint8Array) => void): () => void;
582
+ private reportServerVersion;
578
583
  deleteDoc(_docId: string): Promise<void>;
579
584
  subscribeDocUpdate(callback: (update: DocUpdate, origin: unknown) => void): () => void;
580
585
  private assertDocId;
@@ -623,6 +628,14 @@ interface WaitForServerAckOptions {
623
628
  /** Deadline in ms. Rejects with reason `timeout` when it elapses. */
624
629
  timeoutMs?: number;
625
630
  }
631
+ /** Document-level confirmation is independent of local operation completion. */
632
+ interface MengineDocSyncState {
633
+ /** Remote-only imports do not create a new local confirmation obligation. */
634
+ readonly confirmation: 'unknown' | 'pending' | 'confirmed';
635
+ /** Transport/activity phase. Idle does not imply confirmation; failure details
636
+ * remain available through push outcomes and confirmation wait errors. */
637
+ readonly phase: 'connecting' | 'idle' | 'syncing' | 'offline' | 'failed';
638
+ }
626
639
  interface MengineDocSessionOptions {
627
640
  docId: string;
628
641
  client: MengineHttpClient;
@@ -668,8 +681,14 @@ declare class MengineDocSession {
668
681
  private adapterValue;
669
682
  private editorValue;
670
683
  private started;
684
+ private destroyed;
685
+ private lifetime;
686
+ private hasConnected;
687
+ private initialVersionValue;
688
+ private localVersionValue;
689
+ private pushErrorValue;
671
690
  /**
672
- * Latest server oplog version seen on a push outcome. Lets `waitForServerAck`
691
+ * Latest server oplog version seen on an authoritative HTTP response. Lets `waitForServerAck`
673
692
  * return without waiting when the server is already known to cover the local
674
693
  * doc (e.g. nothing was edited since the last confirmed push).
675
694
  */
@@ -684,14 +703,14 @@ declare class MengineDocSession {
684
703
  */
685
704
  subscribePushOutcome(callback: (outcome: PushOutcome) => void): () => void;
686
705
  /**
687
- * Resolve once the server has durably accepted the local document state as of
706
+ * Resolve once the server has durably accepted the local commits as of
688
707
  * *now* — the capability an Agent tool needs to answer "did my edit land?"
689
708
  * before reporting success.
690
709
  *
691
710
  * Call it right after the editor ops whose durability matters. It snapshots the
692
- * local doc's current version and resolves on the first push outcome whose
693
- * `serverVV` covers that version, i.e. the server log is at least as advanced as
694
- * the local doc was when this was called.
711
+ * latest local commit's version (or the loaded baseline before any edit) and
712
+ * resolves when an authoritative server version covers it. Remote-only
713
+ * imports do not extend this target, matching the displayed confirmation state.
695
714
  *
696
715
  * Version coverage, not byte identity, is the acceptance test — for two reasons:
697
716
  * the ops may reach the server inside a merged blob rather than as the exact
@@ -723,6 +742,11 @@ declare class MengineDocSession {
723
742
  * surfaces through `subscribe`.
724
743
  */
725
744
  start(): Promise<VideoDocument>;
745
+ getSyncState(): MengineDocSyncState;
746
+ private confirmationTarget;
747
+ /** Immediately supplies current state; unsubscribe when the host changes docs. */
748
+ subscribeSyncState(cb: (state: MengineDocSyncState) => void): () => void;
749
+ private emitSyncState;
726
750
  subscribe(cb: (event: MengineDocSessionUpdateEvent) => void): () => void;
727
751
  onStateChange(cb: (state: DocState) => void): () => void;
728
752
  getState(): DocState;
@@ -1064,4 +1088,4 @@ declare function hostForAbsMs(ranges: MainClipRange[], absMs: number): MainClipR
1064
1088
  */
1065
1089
  declare function relativePositionForAbs(ranges: MainClipRange[], absMs: number): TrackItemTimePosition;
1066
1090
  //#endregion
1067
- 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, type InitialDocumentFacts, 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 MengineDocumentVersion, type MengineEventStreamOptions, MengineHttpClient, type MengineHttpClientOptions, MengineHttpRequestError, MenginePushRejectedError, type MenginePushResponse, type MenginePushUpdateResponse, type MengineRejectedResponse, type MengineSnapshotResponse, type MengineSseUpdateEvent, type MengineSyncResponse, 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, VIDEO_DOCUMENT_SCHEMA_VERSION, ValidationError, type VideoClipPart, type VideoDocument, type VideoDocumentDraft, type VideoDocumentMirrorSchema, type VideoDocumentSchemaVersion, VideoDocumentValidationError, type VideoDocumentValidationIssue, type VideoDocumentValidationIssueCode, type VideoDraft, type CaptionPart$1 as VideoDraftCaptionPart, 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 };
1091
+ 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, type InitialDocumentFacts, 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 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, VIDEO_DOCUMENT_SCHEMA_VERSION, ValidationError, type VideoClipPart, type VideoDocument, type VideoDocumentDraft, type VideoDocumentMirrorSchema, type VideoDocumentSchemaVersion, VideoDocumentValidationError, type VideoDocumentValidationIssue, type VideoDocumentValidationIssueCode, type VideoDraft, type CaptionPart$1 as VideoDraftCaptionPart, 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 partDurationMs, C as reassignSpeechesToVideoClipsByTime, D as syncAggregatedClipsTimePosition, E as resolveSpeechOverlapByShiftingVideos, F as partUnionToDraft, I as recordEntries, L as videoDocumentMirrorSchema, M as DEFAULT_UNIT_TIME_MS, N as VIDEO_DOCUMENT_SCHEMA_VERSION, O as TIMELINE_SKELETON_DURATION_MS, P as effectiveVideoClipDurationMs, R as base64ToBytes, S as fillMainTrackTimeGaps, T as resolveAllSpeechOverlaps, _ as findLaneTrack, a as buildInitialVideoDocument, b as cascadeAfterVideoClipChanges, c as fromVideoDocument, d as assertValidVideoDocument, f as validateVideoDocument, g as ensureLaneTrack, h as LANE_KINDS_IN_STACK_ORDER, i as readVideoDocumentFromDraft, j as safeDurationMs, k as isEmptyVideoClip, l as toVideoDocument, m as videoDocumentSchema, n as createMirrorVideoDocument, o as buildSpeechHostMap, p as partUnionSchema, r as createMirrorVideoDocumentAdapter, s as derivePositionFromAbs, t as MirrorVideoDocumentAdapter, u as VideoDocumentValidationError, v as laneTrackId, w as recalculateTimelineDuration, x as arrangeMainTrackSeamlessly, y as solveVideoDocument, z as bytesToBase64 } from "./document-Dcu87api.js";
2
+ import { A as partDurationMs, C as reassignSpeechesToVideoClipsByTime, D as syncAggregatedClipsTimePosition, E as resolveSpeechOverlapByShiftingVideos, F as partUnionToDraft, I as recordEntries, L as videoDocumentMirrorSchema, M as DEFAULT_UNIT_TIME_MS, N as VIDEO_DOCUMENT_SCHEMA_VERSION, O as TIMELINE_SKELETON_DURATION_MS, P as effectiveVideoClipDurationMs, R as base64ToBytes, S as fillMainTrackTimeGaps, T as resolveAllSpeechOverlaps, _ as findLaneTrack, a as buildInitialVideoDocument, b as cascadeAfterVideoClipChanges, c as fromVideoDocument, d as assertValidVideoDocument, f as validateVideoDocument, g as ensureLaneTrack, h as LANE_KINDS_IN_STACK_ORDER, i as readVideoDocumentFromDraft, j as safeDurationMs, k as isEmptyVideoClip, l as toVideoDocument, m as videoDocumentSchema, n as createMirrorVideoDocument, o as buildSpeechHostMap, p as partUnionSchema, r as createMirrorVideoDocumentAdapter, s as derivePositionFromAbs, t as MirrorVideoDocumentAdapter, u as VideoDocumentValidationError, v as laneTrackId, w as recalculateTimelineDuration, x as arrangeMainTrackSeamlessly, y as solveVideoDocument, z as bytesToBase64 } from "./document-C0o_cjAH.js";
3
3
  import { z } from "zod";
4
4
  import { LoroDoc, VersionVector } from "loro-crdt";
5
5
  import { ClientServerSynchronizer, DocManager } from "@mengine/sync";
@@ -670,13 +670,6 @@ const replaceVideoClipSequenceInputSchema = z.object({
670
670
  }
671
671
  });
672
672
  //#endregion
673
- //#region src/editor/schemas/resize-caption.ts
674
- /** Resize one caption placement without changing its cue/source duration. */
675
- const resizeCaptionInputSchema = z.object({
676
- caption_id: z.string().min(1),
677
- duration_ms: z.number().finite().positive()
678
- });
679
- //#endregion
680
673
  //#region src/editor/schemas/set-bgm.ts
681
674
  /**
682
675
  * Set the document BGM. The media's stable result (storage key) arrives
@@ -772,7 +765,6 @@ var schemas_exports = /* @__PURE__ */ __exportAll({
772
765
  positiveMsSchema: () => positiveMsSchema,
773
766
  replaceVideoClipContentInputSchema: () => replaceVideoClipContentInputSchema,
774
767
  replaceVideoClipSequenceInputSchema: () => replaceVideoClipSequenceInputSchema,
775
- resizeCaptionInputSchema: () => resizeCaptionInputSchema,
776
768
  setBgmInputSchema: () => setBgmInputSchema,
777
769
  setCaptionStyleInputSchema: () => setCaptionStyleInputSchema,
778
770
  setCaptionVisibilityInputSchema: () => setCaptionVisibilityInputSchema,
@@ -1023,18 +1015,6 @@ var SchemaValidator = class {
1023
1015
  validateSetCaptionVisibility(input, _doc) {
1024
1016
  this.parse(setCaptionVisibilityInputSchema, input, "set_caption_visibility_invalid_input");
1025
1017
  }
1026
- validateResizeCaption(input, doc) {
1027
- const parsed = this.parse(resizeCaptionInputSchema, input, "resize_caption_invalid_input");
1028
- const snapshot = doc.snapshot();
1029
- this.assertPartKind(snapshot, parsed.caption_id, "caption", "resize_caption");
1030
- if (snapshot.tracks?.find((track) => track.parts_kind === "caption")?.items?.find((row) => row.part_id === parsed.caption_id) == null) throw new ValidationError("resize_caption_placement_not_found", `caption_id "${parsed.caption_id}" is not placed on the caption track`, { caption_id: parsed.caption_id });
1031
- const cueDuration = snapshot.part_library?.[parsed.caption_id]?.caption?.initial_duration_ms ?? 0;
1032
- if (parsed.duration_ms < cueDuration) throw new ValidationError("resize_caption_below_cue", `duration_ms ${parsed.duration_ms} is shorter than cue duration ${cueDuration}`, {
1033
- caption_id: parsed.caption_id,
1034
- duration_ms: parsed.duration_ms,
1035
- cue_duration_ms: cueDuration
1036
- });
1037
- }
1038
1018
  validateSetCaptionStyle(input, _doc) {
1039
1019
  this.parse(setCaptionStyleInputSchema, input, "set_caption_style_invalid_input");
1040
1020
  }
@@ -1240,8 +1220,7 @@ var SemanticEditor = class {
1240
1220
  track.items.splice(at, 0, {
1241
1221
  part_id: partId,
1242
1222
  time_position: { mode: "sequential" },
1243
- fallback_abs_ms: void 0,
1244
- duration_override_ms: void 0
1223
+ fallback_abs_ms: void 0
1245
1224
  });
1246
1225
  at += 1;
1247
1226
  }
@@ -1288,8 +1267,7 @@ var SemanticEditor = class {
1288
1267
  newItems.push({
1289
1268
  part_id: partId,
1290
1269
  time_position: { mode: "sequential" },
1291
- fallback_abs_ms: void 0,
1292
- duration_override_ms: void 0
1270
+ fallback_abs_ms: void 0
1293
1271
  });
1294
1272
  newClipIds.push(partId);
1295
1273
  }
@@ -1502,8 +1480,7 @@ var SemanticEditor = class {
1502
1480
  mode: "absolute",
1503
1481
  offsetMs: 0
1504
1482
  },
1505
- fallback_abs_ms: void 0,
1506
- duration_override_ms: void 0
1483
+ fallback_abs_ms: void 0
1507
1484
  }];
1508
1485
  }, audit("SetBgm", input, options));
1509
1486
  }
@@ -1526,15 +1503,6 @@ var SemanticEditor = class {
1526
1503
  track.is_hidden = input.is_hidden;
1527
1504
  }, audit("SetCaptionVisibility", input, options));
1528
1505
  }
1529
- /** Resize the caption placement window; cue/source duration stays immutable. */
1530
- async resizeCaption(input, options) {
1531
- this.validator.validateResizeCaption(input, this.doc);
1532
- this.doc.transact((draft) => {
1533
- const item = findLaneTrack(draft, "caption")?.items?.find((row) => row.part_id === input.caption_id);
1534
- if (item == null) return;
1535
- item.duration_override_ms = input.duration_ms;
1536
- }, audit("ResizeCaption", input, options));
1537
- }
1538
1506
  /**
1539
1507
  * Set the document-wide caption style. GLOBAL (no `caption_id`): the patch is
1540
1508
  * merged onto EVERY caption part's `style`, mirroring the FE, which applies a
@@ -1781,8 +1749,7 @@ function placeRelative(items, partId, anchorPartId, offsetMs, fallbackAbsMs) {
1781
1749
  items.push({
1782
1750
  part_id: partId,
1783
1751
  time_position: timePosition,
1784
- fallback_abs_ms: fallbackAbsMs,
1785
- duration_override_ms: void 0
1752
+ fallback_abs_ms: fallbackAbsMs
1786
1753
  });
1787
1754
  }
1788
1755
  /**
@@ -1926,7 +1893,6 @@ const IMPLEMENTED_SEMANTIC_OP_KINDS = [
1926
1893
  "ChangeSpeechScript",
1927
1894
  "ChangeSpeechVoice",
1928
1895
  "AdjustSpeechVolume",
1929
- "ResizeCaption",
1930
1896
  "SetCaptionVisibility",
1931
1897
  "SetCaptionStyle",
1932
1898
  "SetBgm",
@@ -2194,6 +2160,7 @@ var ManualSyncDoc = class ManualSyncDoc {
2194
2160
  if (serverVV != null) this.watermark = serverVV;
2195
2161
  return {
2196
2162
  kind: response.kind,
2163
+ mengineUpdateSeq: response.version?.update_seq ?? response.update_seq ?? void 0,
2197
2164
  updateSeq: response.update_seq ?? void 0,
2198
2165
  collaborated: serverVV != null && !covers(localVersion, serverVV)
2199
2166
  };
@@ -2312,6 +2279,7 @@ var SseConnection = class {
2312
2279
  for (const update of event.updates) this.onUpdate(update);
2313
2280
  }
2314
2281
  });
2282
+ if (!signal.aborted) this.setStatus("connecting", /* @__PURE__ */ new Error("SSE stream closed"));
2315
2283
  } catch (error) {
2316
2284
  if (signal.aborted) break;
2317
2285
  this.setStatus("connecting", error instanceof Error ? error : new Error(String(error)));
@@ -2364,6 +2332,7 @@ var MedeoHttpDocStorage = class {
2364
2332
  async getDoc(docId) {
2365
2333
  this.assertDocId(docId);
2366
2334
  const snapshot = await this.client.fetchSnapshot();
2335
+ this.reportServerVersion(snapshot.version?.server_vv);
2367
2336
  const now = /* @__PURE__ */ new Date();
2368
2337
  return {
2369
2338
  docId,
@@ -2375,6 +2344,7 @@ var MedeoHttpDocStorage = class {
2375
2344
  async getDocDiff(docId, knownVersion) {
2376
2345
  this.assertDocId(docId);
2377
2346
  const response = await this.client.sync(knownVersion);
2347
+ this.reportServerVersion(response.server_vv);
2378
2348
  return {
2379
2349
  docId,
2380
2350
  missing: base64ToBytes(response.update),
@@ -2405,7 +2375,7 @@ var MedeoHttpDocStorage = class {
2405
2375
  if (update.data.byteLength === 0) return {};
2406
2376
  try {
2407
2377
  const response = await this.client.pushUpdate(update.data);
2408
- const serverVV = decodeServerVV(response.version?.server_vv);
2378
+ const serverVV = this.reportServerVersion(response.version?.server_vv);
2409
2379
  this.events.emit("pushOutcome", {
2410
2380
  kind: response.kind,
2411
2381
  updateSeq: response.update_seq ?? void 0,
@@ -2433,6 +2403,15 @@ var MedeoHttpDocStorage = class {
2433
2403
  subscribePushOutcome(callback) {
2434
2404
  return this.events.on("pushOutcome", callback);
2435
2405
  }
2406
+ /** Authoritative HTTP reads and accepted pushes, never inferred from SSE bytes. */
2407
+ subscribeServerVersion(callback) {
2408
+ return this.events.on("serverVersion", callback);
2409
+ }
2410
+ reportServerVersion(encoded) {
2411
+ const version = decodeServerVV(encoded);
2412
+ if (version != null) this.events.emit("serverVersion", version);
2413
+ return version;
2414
+ }
2436
2415
  async deleteDoc(_docId) {
2437
2416
  throw new Error("MedeoHttpDocStorage does not support deleteDoc");
2438
2417
  }
@@ -2614,8 +2593,14 @@ var MengineDocSession = class {
2614
2593
  adapterValue = null;
2615
2594
  editorValue = null;
2616
2595
  started = false;
2596
+ destroyed = false;
2597
+ lifetime = null;
2598
+ hasConnected = false;
2599
+ initialVersionValue = null;
2600
+ localVersionValue = null;
2601
+ pushErrorValue = null;
2617
2602
  /**
2618
- * Latest server oplog version seen on a push outcome. Lets `waitForServerAck`
2603
+ * Latest server oplog version seen on an authoritative HTTP response. Lets `waitForServerAck`
2619
2604
  * return without waiting when the server is already known to cover the local
2620
2605
  * doc (e.g. nothing was edited since the last confirmed push).
2621
2606
  */
@@ -2643,14 +2628,14 @@ var MengineDocSession = class {
2643
2628
  return this.server.subscribePushOutcome(callback);
2644
2629
  }
2645
2630
  /**
2646
- * Resolve once the server has durably accepted the local document state as of
2631
+ * Resolve once the server has durably accepted the local commits as of
2647
2632
  * *now* — the capability an Agent tool needs to answer "did my edit land?"
2648
2633
  * before reporting success.
2649
2634
  *
2650
2635
  * Call it right after the editor ops whose durability matters. It snapshots the
2651
- * local doc's current version and resolves on the first push outcome whose
2652
- * `serverVV` covers that version, i.e. the server log is at least as advanced as
2653
- * the local doc was when this was called.
2636
+ * latest local commit's version (or the loaded baseline before any edit) and
2637
+ * resolves when an authoritative server version covers it. Remote-only
2638
+ * imports do not extend this target, matching the displayed confirmation state.
2654
2639
  *
2655
2640
  * Version coverage, not byte identity, is the acceptance test — for two reasons:
2656
2641
  * the ops may reach the server inside a merged blob rather than as the exact
@@ -2665,31 +2650,39 @@ var MengineDocSession = class {
2665
2650
  * @throws {MengineAckFailedError} on timeout, rejection, or transport failure.
2666
2651
  */
2667
2652
  async waitForServerAck(options = {}) {
2668
- if (this.adapterValue == null) throw new Error("mengine doc session is not started");
2653
+ if (this.adapterValue == null || this.lifetime == null) throw new Error("mengine doc session is not started");
2669
2654
  const timeoutMs = options.timeoutMs ?? 15e3;
2670
- const target = this.manager.connectDoc(this.docId).version();
2655
+ const target = this.confirmationTarget() ?? this.manager.connectDoc(this.docId).version();
2656
+ const signal = this.lifetime.signal;
2671
2657
  if (this.serverVVValue != null && serverCovers(this.serverVVValue, target)) return;
2672
2658
  await new Promise((resolve, reject) => {
2673
- let off = () => {};
2659
+ const cleanup = [];
2674
2660
  let done = false;
2675
2661
  const settle = (error) => {
2676
2662
  if (done) return;
2677
2663
  done = true;
2678
2664
  clearTimeout(timer);
2679
- off();
2665
+ cleanup.forEach((off) => off());
2680
2666
  if (error == null) resolve();
2681
2667
  else reject(error);
2682
2668
  };
2683
2669
  const timer = setTimeout(() => {
2684
2670
  settle(new MengineAckFailedError("timeout", void 0, void 0, `mengine did not acknowledge the update within ${timeoutMs}ms`));
2685
2671
  }, timeoutMs);
2686
- off = this.server.subscribePushOutcome((outcome) => {
2672
+ const onAbort = () => settle(new MengineAckFailedError("failed", void 0, void 0, "mengine doc session destroyed"));
2673
+ signal.addEventListener("abort", onAbort, { once: true });
2674
+ cleanup.push(() => signal.removeEventListener("abort", onAbort));
2675
+ cleanup.push(this.events.on("syncState", () => {
2676
+ if (this.serverVVValue != null && serverCovers(this.serverVVValue, target)) settle(void 0);
2677
+ }));
2678
+ cleanup.push(this.server.subscribePushOutcome((outcome) => {
2687
2679
  if (outcome.kind === "rejected" || outcome.kind === "failed") {
2688
2680
  settle(new MengineAckFailedError(outcome.kind, outcome.code, outcome.error, outcome.error?.message ?? `mengine push ${outcome.kind}`));
2689
2681
  return;
2690
2682
  }
2691
- if (outcome.serverVV != null && serverCovers(outcome.serverVV, target)) settle(void 0);
2692
- });
2683
+ }));
2684
+ if (signal.aborted) onAbort();
2685
+ else this.synchronizer.requestSync(this.docId);
2693
2686
  });
2694
2687
  }
2695
2688
  /**
@@ -2715,8 +2708,23 @@ var MengineDocSession = class {
2715
2708
  * surfaces through `subscribe`.
2716
2709
  */
2717
2710
  async start() {
2711
+ if (this.destroyed) throw new Error("mengine doc session destroyed");
2718
2712
  if (this.started) return this.snapshot();
2719
2713
  this.started = true;
2714
+ this.lifetime = new AbortController();
2715
+ this.disposables.add(this.server.subscribeServerVersion((version) => {
2716
+ this.serverVVValue = version;
2717
+ if (this.getSyncState().confirmation === "confirmed") this.pushErrorValue = null;
2718
+ this.emitSyncState();
2719
+ }));
2720
+ this.disposables.add(this.server.subscribePushOutcome((outcome) => {
2721
+ this.pushErrorValue = outcome.kind === "failed" || outcome.kind === "rejected" ? outcome.error ?? /* @__PURE__ */ new Error(`mengine push ${outcome.kind}`) : null;
2722
+ this.emitSyncState();
2723
+ }));
2724
+ this.disposables.add(this.server.connection.onStatusChanged((status) => {
2725
+ if (status === "connected") this.hasConnected = true;
2726
+ this.emitSyncState();
2727
+ }));
2720
2728
  this.local.connection.connect();
2721
2729
  this.server.connection.connect();
2722
2730
  this.disposables.add(() => this.local.connection.disconnect());
@@ -2732,19 +2740,46 @@ var MengineDocSession = class {
2732
2740
  this.disposables.add(() => this.manager.disconnectDoc(this.docId));
2733
2741
  const unsubscribe = doc.subscribe((event) => {
2734
2742
  if (this.adapterValue == null) return;
2743
+ if (this.initialVersionValue == null && this.hasContent()) this.initialVersionValue = doc.version();
2744
+ if (event.by === "local") this.localVersionValue = doc.version();
2745
+ this.emitSyncState();
2735
2746
  this.events.emit("update", {
2736
2747
  source: event.by === "local" ? "local" : "remote",
2737
2748
  snapshot: this.adapterValue.snapshot()
2738
2749
  });
2739
2750
  });
2740
2751
  this.disposables.add(unsubscribe);
2741
- this.disposables.add(this.manager.onDocStateChange(this.docId, (state) => this.events.emit("state", state)));
2742
- this.disposables.add(this.server.subscribePushOutcome((outcome) => {
2743
- if (outcome.serverVV != null) this.serverVVValue = outcome.serverVV;
2752
+ this.disposables.add(this.manager.onDocStateChange(this.docId, (state) => {
2753
+ this.events.emit("state", state);
2754
+ this.emitSyncState();
2744
2755
  }));
2745
2756
  await this.waitForContent();
2757
+ this.initialVersionValue ??= doc.version();
2758
+ this.emitSyncState();
2746
2759
  return this.snapshot();
2747
2760
  }
2761
+ getSyncState() {
2762
+ const state = this.getState();
2763
+ const target = this.confirmationTarget();
2764
+ const confirmation = this.serverVVValue == null || target == null ? this.localVersionValue == null ? "unknown" : "pending" : serverCovers(this.serverVVValue, target) ? "confirmed" : "pending";
2765
+ const connection = !this.started ? "disconnected" : this.server.connection.status === "connected" ? "connected" : this.hasConnected || this.server.connection.error != null ? "disconnected" : "connecting";
2766
+ return {
2767
+ confirmation,
2768
+ phase: connection === "disconnected" ? "offline" : this.pushErrorValue != null || state.error != null ? "failed" : connection === "connecting" ? "connecting" : state.syncing ? "syncing" : "idle"
2769
+ };
2770
+ }
2771
+ confirmationTarget() {
2772
+ return this.localVersionValue ?? this.initialVersionValue;
2773
+ }
2774
+ /** Immediately supplies current state; unsubscribe when the host changes docs. */
2775
+ subscribeSyncState(cb) {
2776
+ const off = this.events.on("syncState", cb);
2777
+ cb(this.getSyncState());
2778
+ return off;
2779
+ }
2780
+ emitSyncState() {
2781
+ this.events.emit("syncState", this.getSyncState());
2782
+ }
2748
2783
  subscribe(cb) {
2749
2784
  return this.events.on("update", cb);
2750
2785
  }
@@ -2755,7 +2790,12 @@ var MengineDocSession = class {
2755
2790
  return this.manager.getDocState(this.docId);
2756
2791
  }
2757
2792
  destroy() {
2793
+ if (this.destroyed) return;
2794
+ this.destroyed = true;
2795
+ this.started = false;
2796
+ this.lifetime?.abort();
2758
2797
  this.disposables.dispose();
2798
+ this.emitSyncState();
2759
2799
  this.events.clearAll();
2760
2800
  this.adapterValue = null;
2761
2801
  this.editorValue = null;
@@ -2772,13 +2812,21 @@ var MengineDocSession = class {
2772
2812
  */
2773
2813
  async waitForContent() {
2774
2814
  if (this.hasContent()) return;
2775
- await new Promise((resolve) => {
2815
+ const signal = this.lifetime.signal;
2816
+ await new Promise((resolve, reject) => {
2817
+ const onAbort = () => {
2818
+ off();
2819
+ reject(/* @__PURE__ */ new Error("mengine doc session destroyed"));
2820
+ };
2776
2821
  const off = this.events.on("update", () => {
2777
2822
  if (this.hasContent()) {
2778
2823
  off();
2824
+ signal.removeEventListener("abort", onAbort);
2779
2825
  resolve();
2780
2826
  }
2781
2827
  });
2828
+ signal.addEventListener("abort", onAbort, { once: true });
2829
+ if (signal.aborted) onAbort();
2782
2830
  });
2783
2831
  }
2784
2832
  hasContent() {
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as MirrorVideoDocumentAdapter, nt as VideoDraft } from "./index-CZlTzzrm.js";
1
+ import { i as MirrorVideoDocumentAdapter, nt as VideoDraft } from "./index-DWVYLTjv.js";
2
2
 
3
3
  //#region src/testing/in-memory-mengine-server.d.ts
4
4
  /**
package/dist/testing.js CHANGED
@@ -1,4 +1,4 @@
1
- import { N as VIDEO_DOCUMENT_SCHEMA_VERSION, R as base64ToBytes, r as createMirrorVideoDocumentAdapter, z as bytesToBase64 } from "./document-Dcu87api.js";
1
+ import { N as VIDEO_DOCUMENT_SCHEMA_VERSION, R as base64ToBytes, r as createMirrorVideoDocumentAdapter, z as bytesToBase64 } from "./document-C0o_cjAH.js";
2
2
  import { t as classifyUpdate } from "./loro-relay-doc-ssdYpuef.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": "1.2.1-alpha.1",
3
+ "version": "1.2.1-alpha.11",
4
4
  "license": "UNLICENSED",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,9 +25,9 @@
25
25
  "dependencies": {
26
26
  "loro-mirror": "^2.2.0",
27
27
  "zod": "^4.4.3",
28
- "@mengine/storage": "1.2.1-alpha.1",
29
- "@mengine/sync": "1.2.1-alpha.1",
30
- "@mengine/utils": "1.2.1-alpha.1"
28
+ "@mengine/sync": "1.2.1-alpha.11",
29
+ "@mengine/storage": "1.2.1-alpha.11",
30
+ "@mengine/utils": "1.2.1-alpha.11"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^25.9.1",