@mengine/medeo-client 1.3.1-alpha.1 → 1.3.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.
@@ -1,7 +1,9 @@
1
1
  import { u as volumeSchema } from "./shared-iWnU2osE.js";
2
2
  import { Mirror, schema } from "loro-mirror";
3
3
  import { z } from "zod";
4
- import { LoroDoc } from "loro-crdt";
4
+ import { LoroDoc, LoroMap, LoroMovableList, LoroText, UndoManager } from "loro-crdt";
5
+ import { sha256 } from "@noble/hashes/sha2.js";
6
+ import { bytesToHex } from "@noble/hashes/utils.js";
5
7
  import { produce } from "immer";
6
8
  //#region src/client/base64.ts
7
9
  function bytesToBase64(bytes) {
@@ -1181,7 +1183,7 @@ function toVideoDocument(draft) {
1181
1183
  owner_id: draft.owner_id,
1182
1184
  thumbnail_storage_key: draft.thumbnail_storage_key,
1183
1185
  chat_session_id: draft.chat_session_id,
1184
- video_creation_settings: clone(draft.video_creation_settings),
1186
+ video_creation_settings: clone$1(draft.video_creation_settings),
1185
1187
  version: draft.version
1186
1188
  },
1187
1189
  timeline: draft.timeline == null ? void 0 : { unit_time_ms: draft.timeline.unit_time_ms },
@@ -1327,7 +1329,7 @@ function fromVideoDocument(document) {
1327
1329
  duration_ms: view.durationMs,
1328
1330
  unit_time_ms: document.timeline?.unit_time_ms ?? 33.333
1329
1331
  },
1330
- video_creation_settings: clone(document.meta.video_creation_settings),
1332
+ video_creation_settings: clone$1(document.meta.video_creation_settings),
1331
1333
  chat_session_id: document.meta.chat_session_id,
1332
1334
  main_track: draftTrack(mainTrack, view.absByPartId),
1333
1335
  above_main_tracks: aboveTracks.map((t) => draftTrack(t, view.absByPartId)),
@@ -1359,25 +1361,25 @@ function toReadViewPartLibrary(partLibrary, durationMs, derivedFillerPartIds) {
1359
1361
  for (const [partId, part] of Object.entries(partLibrary)) {
1360
1362
  if (derivedFillerPartIds.has(partId)) continue;
1361
1363
  if (part.video_clip != null) {
1362
- const clip = clone(part.video_clip);
1364
+ const clip = clone$1(part.video_clip);
1363
1365
  out[partId] = { video_clip: {
1364
1366
  ...clip,
1365
1367
  duration_ms: effectiveVideoClipDurationMs(clip)
1366
1368
  } };
1367
1369
  } else if (part.speech != null) {
1368
- const speech = clone(part.speech);
1370
+ const speech = clone$1(part.speech);
1369
1371
  out[partId] = { speech: {
1370
1372
  ...speech,
1371
1373
  duration_ms: speech.media_duration_ms
1372
1374
  } };
1373
1375
  } else if (part.caption != null) {
1374
- const caption = clone(part.caption);
1376
+ const caption = clone$1(part.caption);
1375
1377
  out[partId] = { caption: {
1376
1378
  ...caption,
1377
1379
  duration_ms: caption.initial_duration_ms
1378
1380
  } };
1379
1381
  } else if (part.bgm != null) out[partId] = { bgm: {
1380
- ...clone(part.bgm),
1382
+ ...clone$1(part.bgm),
1381
1383
  duration_ms: durationMs
1382
1384
  } };
1383
1385
  }
@@ -1385,7 +1387,7 @@ function toReadViewPartLibrary(partLibrary, durationMs, derivedFillerPartIds) {
1385
1387
  }
1386
1388
  /** Deep-clone a part payload, dropping the derived read-view `duration_ms`. */
1387
1389
  function omitDurationMs(part) {
1388
- const { duration_ms: _drop, ...rest } = clone(part);
1390
+ const { duration_ms: _drop, ...rest } = clone$1(part);
1389
1391
  return rest;
1390
1392
  }
1391
1393
  /**
@@ -1410,7 +1412,7 @@ function withTrimWindowFromDuration(clip) {
1410
1412
  }
1411
1413
  /** Deep-clone a part payload, returning its `duration_ms` separately from the rest. */
1412
1414
  function splitDurationMs(part) {
1413
- const { duration_ms, ...rest } = clone(part);
1415
+ const { duration_ms, ...rest } = clone$1(part);
1414
1416
  return {
1415
1417
  duration_ms,
1416
1418
  rest
@@ -1428,11 +1430,11 @@ function draftTrack(track, absByPartId) {
1428
1430
  }))
1429
1431
  };
1430
1432
  }
1431
- function clone(value) {
1432
- if (Array.isArray(value)) return value.map((item) => clone(item));
1433
+ function clone$1(value) {
1434
+ if (Array.isArray(value)) return value.map((item) => clone$1(item));
1433
1435
  if (value != null && typeof value === "object") {
1434
1436
  const result = {};
1435
- for (const [key, child] of Object.entries(value)) result[key] = clone(child);
1437
+ for (const [key, child] of Object.entries(value)) result[key] = clone$1(child);
1436
1438
  return result;
1437
1439
  }
1438
1440
  return value;
@@ -1524,13 +1526,49 @@ function isRecord$1(value) {
1524
1526
  return typeof value === "object" && value != null && !Array.isArray(value);
1525
1527
  }
1526
1528
  //#endregion
1529
+ //#region ../medeo-dsl/src/ids.ts
1530
+ function createEntityId(value) {
1531
+ return createId(value, "EntityId");
1532
+ }
1533
+ function createRelationId(value) {
1534
+ return createId(value, "RelationId");
1535
+ }
1536
+ function createId(value, label) {
1537
+ if (value.length === 0 || value.trim() !== value) throw new Error(`${label} must be a non-empty trimmed string`);
1538
+ return value;
1539
+ }
1540
+ //#endregion
1541
+ //#region ../medeo-dsl/src/json-values.ts
1542
+ const nativeObjectConstructorSource = Function.prototype.toString.call(Object);
1543
+ function isJsonObject(value) {
1544
+ return typeof value === "object" && value !== null && !Array.isArray(value) && isJsonValue(value, /* @__PURE__ */ new Set());
1545
+ }
1546
+ function isJsonValue(value, ancestors) {
1547
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
1548
+ if (typeof value === "number") return Number.isFinite(value);
1549
+ if (typeof value !== "object") return false;
1550
+ if (!Array.isArray(value) && !isPlainObject(value)) return false;
1551
+ if (ancestors.has(value)) return false;
1552
+ ancestors.add(value);
1553
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, ancestors)) : Object.values(value).every((item) => isJsonValue(item, ancestors));
1554
+ ancestors.delete(value);
1555
+ return valid;
1556
+ }
1557
+ /** Recognize an ordinary object from any VM realm without admitting class instances. */
1558
+ function isPlainObject(value) {
1559
+ try {
1560
+ const prototype = Object.getPrototypeOf(value);
1561
+ if (prototype === null) return true;
1562
+ if (Object.getPrototypeOf(prototype) !== null) return false;
1563
+ const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
1564
+ return typeof constructor === "function" && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === nativeObjectConstructorSource;
1565
+ } catch {
1566
+ return false;
1567
+ }
1568
+ }
1569
+ //#endregion
1527
1570
  //#region ../medeo-dsl/src/composition.ts
1528
- /**
1529
- * Raised when a variant's complete content cannot be assembled from its
1530
- * required composition Relation. Assembly never falls back to variant-local
1531
- * text: a missing, ambiguous, or dangling source is an error, not an empty
1532
- * read.
1533
- */
1571
+ /** Assembly fails explicitly when declared bases cannot provide valid, unambiguous content. */
1534
1572
  var ScriptCompositionError = class extends Error {
1535
1573
  code;
1536
1574
  entityId;
@@ -1541,23 +1579,111 @@ var ScriptCompositionError = class extends Error {
1541
1579
  this.name = "ScriptCompositionError";
1542
1580
  }
1543
1581
  };
1544
- function compositionRelationKind(variantKind) {
1545
- return variantKind === "caption" ? "caption-composition" : "phonetic-script-composition";
1582
+ /** Direct base IDs are structural fields, never ordinary Relation endpoints. */
1583
+ function variantBaseEntityIds(entity) {
1584
+ if (!Object.hasOwn(entity.payload, "baseEntityIds")) return [];
1585
+ const ids = entity.payload.baseEntityIds;
1586
+ if (!Array.isArray(ids) || ids.length === 0 || !ids.every((id) => typeof id === "string" && id.trim() === id && id.length > 0) || new Set(ids).size !== ids.length) throw new ScriptCompositionError("invalid_bases", entity.entityId, `Entity "${entity.entityId}" requires non-empty, unique baseEntityIds`);
1587
+ return ids.map(createEntityId);
1588
+ }
1589
+ /** Validate all base providers before applying explicit own fields; ordering cannot resolve ambiguity. */
1590
+ function assembleEntityContent(rows, entityId) {
1591
+ const byId = /* @__PURE__ */ new Map();
1592
+ for (const row of rows.entities) {
1593
+ if (byId.has(row.entityId)) throw new ScriptCompositionError("composition_ambiguous", row.entityId, `duplicate entity id "${row.entityId}"`);
1594
+ byId.set(row.entityId, row);
1595
+ }
1596
+ const active = /* @__PURE__ */ new Set();
1597
+ const cache = /* @__PURE__ */ new Map();
1598
+ const visit = (id) => {
1599
+ const cached = cache.get(id);
1600
+ if (cached !== void 0) return cached;
1601
+ if (active.has(id)) throw new ScriptCompositionError("composition_cycle", id, `Cyclic baseEntityIds at "${id}"`);
1602
+ const row = byId.get(id);
1603
+ if (row === void 0) throw new ScriptCompositionError("composition_dangling", id, `Missing base entity "${id}" in this document`);
1604
+ if (!isJsonObject(row.payload) || Object.hasOwn(row.payload, "entityId") || Object.hasOwn(row.payload, "entityKind")) throw new ScriptCompositionError("invalid_bases", id, `Entity "${id}" requires JSON own fields without reserved identity fields`);
1605
+ active.add(id);
1606
+ const inherited = {};
1607
+ const providers = /* @__PURE__ */ new Map();
1608
+ for (const baseId of variantBaseEntityIds(row)) {
1609
+ const base = visit(baseId);
1610
+ for (const [field, value] of Object.entries(base.payload)) {
1611
+ if (field === "baseEntityIds") continue;
1612
+ const previous = providers.get(field);
1613
+ if (previous !== void 0) throw new ScriptCompositionError("field_conflict", id, `Entity "${id}" field "${field}" conflicts between bases "${previous}" and "${baseId}"; own fields cannot resolve base ambiguity`);
1614
+ providers.set(field, baseId);
1615
+ Object.defineProperty(inherited, field, {
1616
+ value,
1617
+ enumerable: true,
1618
+ configurable: true,
1619
+ writable: true
1620
+ });
1621
+ }
1622
+ }
1623
+ const assembled = {
1624
+ ...row,
1625
+ payload: JSON.parse(JSON.stringify({
1626
+ ...inherited,
1627
+ ...row.payload
1628
+ }))
1629
+ };
1630
+ active.delete(id);
1631
+ cache.set(id, assembled);
1632
+ return assembled;
1633
+ };
1634
+ return visit(entityId);
1635
+ }
1636
+ /** Resolve a field's declaring entity after validating the entire composition. */
1637
+ function resolveEntityFieldOwner(rows, entityId, field) {
1638
+ assembleEntityContent(rows, entityId);
1639
+ const byId = new Map(rows.entities.map((row) => [row.entityId, row]));
1640
+ const find = (id) => {
1641
+ const row = byId.get(id);
1642
+ if (Object.hasOwn(row.payload, field)) return id;
1643
+ if (field === "baseEntityIds") return void 0;
1644
+ for (const baseId of variantBaseEntityIds(row)) {
1645
+ const owner = find(baseId);
1646
+ if (owner !== void 0) return owner;
1647
+ }
1648
+ };
1649
+ return find(entityId) ?? entityId;
1650
+ }
1651
+ /** Patch assembled fields without copying inherited fields into the variant's stored payload. */
1652
+ function updateEntityFields(rows, entityId, fields) {
1653
+ assembleEntityContent(rows, entityId);
1654
+ if (!isJsonObject(fields) || Object.hasOwn(fields, "entityId") || Object.hasOwn(fields, "entityKind")) throw new ScriptCompositionError("invalid_bases", entityId, "Field updates require JSON without reserved identity fields");
1655
+ const updates = /* @__PURE__ */ new Map();
1656
+ for (const [field, value] of Object.entries(fields)) {
1657
+ const ownerId = resolveEntityFieldOwner(rows, entityId, field);
1658
+ const owner = updates.get(ownerId) ?? rows.entities.find((row) => row.entityId === ownerId);
1659
+ updates.set(ownerId, {
1660
+ ...owner,
1661
+ payload: {
1662
+ ...owner.payload,
1663
+ [field]: value
1664
+ }
1665
+ });
1666
+ }
1667
+ return [...updates.values()].map((row) => JSON.parse(JSON.stringify(row)));
1546
1668
  }
1547
- /**
1548
- * Resolve the one AudioScript a variant composes. Relations are queried from
1549
- * both endpoints, so persisted endpoint positions do not matter.
1550
- */
1669
+ /** Find the actual AudioScript text owner through direct variant bases. */
1551
1670
  function findComposedAudioScript(rows, variantEntityId, variantKind) {
1552
- const kind = compositionRelationKind(variantKind);
1553
- const edges = rows.relations.filter((relation) => relation.relationKind === kind && (relation.endpoint0EntityId === variantEntityId || relation.endpoint1EntityId === variantEntityId));
1554
- if (edges.length === 0) throw new ScriptCompositionError("composition_missing", variantEntityId, `${variantKind} "${variantEntityId}" has no ${kind} Relation; it has no content without its AudioScript`);
1555
- if (edges.length > 1) throw new ScriptCompositionError("composition_ambiguous", variantEntityId, `${variantKind} "${variantEntityId}" has ${edges.length} ${kind} Relations; exactly one AudioScript is required`);
1556
- const edge = edges[0];
1557
- const otherId = edge.endpoint0EntityId === variantEntityId ? edge.endpoint1EntityId : edge.endpoint0EntityId;
1558
- const script = rows.entities.find((entity) => entity.entityId === otherId);
1559
- if (script === void 0 || script.entityKind !== "audio-script") throw new ScriptCompositionError("composition_dangling", variantEntityId, `${variantKind} "${variantEntityId}" ${kind} Relation must resolve a live AudioScript, got "${script?.entityKind ?? "missing entity"}"`);
1560
- return script;
1671
+ const variant = requireEntityKind(rows, variantEntityId, variantKind);
1672
+ assembleEntityContent(rows, variantEntityId);
1673
+ const sources = /* @__PURE__ */ new Map();
1674
+ const seen = /* @__PURE__ */ new Set();
1675
+ const visit = (row) => {
1676
+ for (const id of variantBaseEntityIds(row)) {
1677
+ if (seen.has(id)) continue;
1678
+ seen.add(id);
1679
+ const base = rows.entities.find((candidate) => candidate.entityId === id);
1680
+ if (base.entityKind === "audio-script") sources.set(id, assembleEntityContent(rows, id));
1681
+ else visit(base);
1682
+ }
1683
+ };
1684
+ visit(variant);
1685
+ if (sources.size !== 1) throw new ScriptCompositionError(sources.size === 0 ? "composition_missing" : "composition_ambiguous", variantEntityId, `${variantKind} "${variantEntityId}" baseEntityIds must resolve one AudioScript text owner, got ${sources.size}`);
1686
+ return [...sources.values()][0];
1561
1687
  }
1562
1688
  /** Join the whole AudioScript text in segment order. */
1563
1689
  function assembleScriptText(script) {
@@ -1574,16 +1700,15 @@ function isScriptSegment(value) {
1574
1700
  const segment = value;
1575
1701
  return typeof segment.segmentId === "string" && segment.segmentId.trim() !== "" && typeof segment.text === "string" && (segment.language === void 0 || typeof segment.language === "string");
1576
1702
  }
1577
- /**
1578
- * Assemble the complete Caption content from its AudioScript: the selected
1579
- * segments in selection order, joined as display text. The Caption payload
1580
- * never carries text, so a broken composition fails here instead of silently
1581
- * rendering an empty caption.
1582
- */
1703
+ /** Assemble selected text after base-field validation and explicit variant overrides. */
1583
1704
  function assembleCaptionContent(rows, captionEntityId) {
1584
- const caption = requireEntityKind(rows, captionEntityId, "caption");
1705
+ requireEntityKind(rows, captionEntityId, "caption");
1706
+ const caption = assembleEntityContent(rows, captionEntityId);
1585
1707
  const script = findComposedAudioScript(rows, captionEntityId, "caption");
1586
- const segments = selectAudioScriptSegments(script, captionSelections(caption));
1708
+ const segments = selectAudioScriptSegments({
1709
+ ...script,
1710
+ payload: caption.payload
1711
+ }, captionSelections(caption));
1587
1712
  return {
1588
1713
  caption,
1589
1714
  audioScript: script,
@@ -1596,9 +1721,13 @@ function assembleCaptionContent(rows, captionEntityId) {
1596
1721
  * AudioScript's base text plus the variant's own phoneme and prosody fields.
1597
1722
  */
1598
1723
  function assemblePhoneticScriptContent(rows, phoneticScriptEntityId) {
1599
- const phoneticScript = requireEntityKind(rows, phoneticScriptEntityId, "phonetic-script");
1724
+ requireEntityKind(rows, phoneticScriptEntityId, "phonetic-script");
1725
+ const phoneticScript = assembleEntityContent(rows, phoneticScriptEntityId);
1600
1726
  const audioScript = findComposedAudioScript(rows, phoneticScriptEntityId, "phonetic-script");
1601
- const segments = scriptSegments(audioScript);
1727
+ const segments = scriptSegments({
1728
+ ...audioScript,
1729
+ payload: phoneticScript.payload
1730
+ });
1602
1731
  return {
1603
1732
  phoneticScript,
1604
1733
  audioScript,
@@ -1641,16 +1770,15 @@ function requireEntityKind(rows, entityIdValue, entityKind) {
1641
1770
  return entity;
1642
1771
  }
1643
1772
  //#endregion
1644
- //#region ../medeo-dsl/src/ids.ts
1645
- function createEntityId(value) {
1646
- return createId(value, "EntityId");
1647
- }
1648
- function createRelationId(value) {
1649
- return createId(value, "RelationId");
1650
- }
1651
- function createId(value, label) {
1652
- if (value.length === 0 || value.trim() !== value) throw new Error(`${label} must be a non-empty trimmed string`);
1653
- return value;
1773
+ //#region ../medeo-dsl/src/rows.ts
1774
+ function entityToRow(entity) {
1775
+ const { entityId, entityKind, ...payload } = entity;
1776
+ if (!isJsonObject(payload)) throw new Error(`Entity "${entityId}" payload must contain only JSON values`);
1777
+ return {
1778
+ entityId,
1779
+ entityKind,
1780
+ payload
1781
+ };
1654
1782
  }
1655
1783
  //#endregion
1656
1784
  //#region ../medeo-dsl/src/invariants.ts
@@ -1671,6 +1799,19 @@ function validateMediaAssetIdentity(entity, index) {
1671
1799
  function validateEntityRelationSet(entityRefs, index, options) {
1672
1800
  const { entities, issues } = collectRelatedEntities(entityRefs, index);
1673
1801
  const entityIds = new Set(entities.map((entity) => entity.entityId));
1802
+ const rows = {
1803
+ entities: entities.map((entity) => entityToRow(entity.current())),
1804
+ relations: []
1805
+ };
1806
+ for (const entity of entities) try {
1807
+ assembleEntityContent(rows, entity.entityId);
1808
+ } catch (error) {
1809
+ issues.push({
1810
+ code: "invalid_variant_composition",
1811
+ entityId: entity.entityId,
1812
+ message: error instanceof Error ? error.message : "Invalid variant composition"
1813
+ });
1814
+ }
1674
1815
  for (const entity of entities) {
1675
1816
  const current = entity.current();
1676
1817
  const entityIssues = validateEntity(entity, entityIds);
@@ -1691,8 +1832,8 @@ function validateEntityRelationSet(entityRefs, index, options) {
1691
1832
  issues.push(...validateClipPlacement(entity, index));
1692
1833
  }
1693
1834
  if (current.entityKind === "axvideo") issues.push(...validateAXVideoAdmission(entity, index));
1694
- if (current.entityKind === "caption") issues.push(...validateCaptionComposition(entity, index));
1695
- if (current.entityKind === "phonetic-script") issues.push(...validatePhoneticComposition(entity, index));
1835
+ if (current.entityKind === "caption") issues.push(...validateCaptionComposition(entity, index, entities));
1836
+ if (current.entityKind === "phonetic-script") issues.push(...validatePhoneticComposition(entity, index, entities));
1696
1837
  issues.push(...validateSequenceComposition(entity));
1697
1838
  }
1698
1839
  issues.push(...validateClipAnchorCycles(entities, index));
@@ -1795,60 +1936,32 @@ function validateAXVideoAdmission(axVideo, index) {
1795
1936
  message: "AXVideo must have exactly one live AXVideo-Marker Relation"
1796
1937
  }];
1797
1938
  }
1798
- /**
1799
- * Variant composition is the content contract, not an optional label: a
1800
- * Caption must compose exactly one live AudioScript and its segment selection
1801
- * must name that script's own segments. Text is never stored on the variant,
1802
- * so a missing source means no valid caption content and fails validation.
1803
- */
1804
- function validateCaptionComposition(caption, index) {
1805
- return validateVariantComposition(caption, index, "caption", "caption-composition", "caption_composition_required");
1939
+ /** Validate a Caption against the complete entity set, including its directly held bases. */
1940
+ function validateCaptionComposition(caption, index, entities = []) {
1941
+ return validateVariantComposition(caption, index, entities, "caption");
1806
1942
  }
1807
- /** Same required-composition contract for the pronunciation variant. */
1808
- function validatePhoneticComposition(phonetic, index) {
1809
- return validateVariantComposition(phonetic, index, "phonetic-script", "phonetic-script-composition", "phonetic_script_composition_required");
1943
+ function validatePhoneticComposition(phonetic, index, entities = []) {
1944
+ return validateVariantComposition(phonetic, index, entities, "phonetic-script");
1810
1945
  }
1811
- function validateVariantComposition(variant, index, variantKind, compositionKind, issueCode) {
1812
- const relations = [...index.relationsOf(variant)];
1946
+ function validateVariantComposition(variant, index, entities, kind) {
1813
1947
  const issues = [];
1814
- const physicalEdges = ofKind(relations, "physical-asset");
1815
- if (variantKind === "caption" && physicalEdges.length > 0) issues.push({
1948
+ if (kind === "caption" && ofKind([...index.relationsOf(variant)], "physical-asset").length > 0) issues.push({
1816
1949
  code: "caption_physical_asset_forbidden",
1817
1950
  entityId: variant.entityId,
1818
1951
  message: "Caption text is composed from its AudioScript; Caption must not bind a Physical Asset"
1819
1952
  });
1820
- const compositionEdges = ofKind(relations, compositionKind);
1821
- if (compositionEdges.length !== 1) {
1822
- issues.push({
1823
- code: issueCode,
1824
- entityId: variant.entityId,
1825
- message: `${variantKind} must have exactly one ${compositionKind} Relation to a live AudioScript`
1826
- });
1827
- return issues;
1828
- }
1829
- const script = compositionEdges[0]?.other(variant)?.deref()?.current();
1830
- if (script?.entityKind !== "audio-script") {
1831
- issues.push({
1832
- code: issueCode,
1833
- entityId: variant.entityId,
1834
- message: `${variantKind} ${compositionKind} Relation must resolve a live AudioScript`
1835
- });
1836
- return issues;
1837
- }
1838
- if (variantKind !== "caption") return issues;
1839
- const selections = variant.current().selections;
1840
- if (!Array.isArray(selections)) return issues;
1953
+ const rows = {
1954
+ entities: collectRelatedEntities([...entities, variant], index).entities.map((ref) => entityToRow(ref.current())),
1955
+ relations: []
1956
+ };
1841
1957
  try {
1842
- selectAudioScriptSegments({
1843
- entityId: script.entityId,
1844
- entityKind: "audio-script",
1845
- payload: { segments: script.segments.map((segment) => ({ ...segment })) }
1846
- }, selections);
1958
+ if (kind === "caption") assembleCaptionContent(rows, variant.entityId);
1959
+ else assemblePhoneticScriptContent(rows, variant.entityId);
1847
1960
  } catch (error) {
1848
1961
  issues.push({
1849
- code: "caption_selection_invalid",
1962
+ code: kind === "caption" ? "caption_composition_required" : "phonetic_script_composition_required",
1850
1963
  entityId: variant.entityId,
1851
- message: error instanceof Error ? error.message : "Invalid Caption selection"
1964
+ message: error instanceof Error ? error.message : "Invalid variant composition"
1852
1965
  });
1853
1966
  }
1854
1967
  return issues;
@@ -2123,7 +2236,6 @@ function validateVoicePayload(value, problems) {
2123
2236
  if (voice.name !== void 0 && typeof voice.name !== "string") problems.push("voice.name must be a string when present");
2124
2237
  }
2125
2238
  function validateCaptionPayload(value, problems) {
2126
- if (value.text !== void 0) problems.push("text is composed from the AudioScript; Caption must not store a text copy");
2127
2239
  const selections = value.selections;
2128
2240
  if (!Array.isArray(selections)) problems.push("selections must be a non-empty array of AudioScript segment selections");
2129
2241
  else {
@@ -2180,8 +2292,7 @@ function validateRange(value, path, required, problems) {
2180
2292
  }
2181
2293
  /** Phonetic variants store pronunciation fields only; base text stays in the AudioScript. */
2182
2294
  function validatePhoneticScriptPayload(value, problems) {
2183
- if (value.text !== void 0) problems.push("text is composed from AudioScript; PhoneticScript must not store a text copy");
2184
- if (value.segments !== void 0) problems.push("segments carry the base text owned by the AudioScript; PhoneticScript stores pronunciation fields only");
2295
+ if (value.segments !== void 0) validateScriptPayload(value, problems);
2185
2296
  if (value.phonemeScript !== void 0 && (typeof value.phonemeScript !== "string" || value.phonemeScript.trim() === "")) problems.push("phonemeScript must be a non-empty string when present");
2186
2297
  if (value.prosody !== void 0 && !isRecord(value.prosody)) problems.push("prosody must be an object when present");
2187
2298
  }
@@ -2227,6 +2338,7 @@ function visitPeerEntityIdPaths(value, entityKind, parentPath, ancestors, paths)
2227
2338
  if (Array.isArray(value)) for (const [index, item] of value.entries()) visitPeerEntityIdPaths(item, entityKind, `${parentPath}[${index}]`, ancestors, paths);
2228
2339
  else for (const [key, child] of Object.entries(value)) {
2229
2340
  const path = parentPath.length === 0 ? key : `${parentPath}.${key}`;
2341
+ if (parentPath.length === 0 && key === "baseEntityIds") continue;
2230
2342
  if (!(parentPath.length === 0 && key === "entityId") && !isOwnedLocalIdPath(entityKind, path) && isEntityIdFieldName(key)) paths.push(path);
2231
2343
  visitPeerEntityIdPaths(child, entityKind, path, ancestors, paths);
2232
2344
  }
@@ -2246,7 +2358,7 @@ function visitPeerEntityValues(value, entityKind, ownEntityId, entityIds, path,
2246
2358
  ancestors.add(value);
2247
2359
  if (Array.isArray(value)) for (const [index, item] of value.entries()) visitPeerEntityValues(item, entityKind, ownEntityId, entityIds, `${path}[${index}]`, ancestors, paths);
2248
2360
  else for (const [key, child] of Object.entries(value)) {
2249
- if (path.length === 0 && (key === "entityId" || key === "entityKind")) continue;
2361
+ if (path.length === 0 && (key === "entityId" || key === "entityKind" || key === "baseEntityIds")) continue;
2250
2362
  visitPeerEntityValues(child, entityKind, ownEntityId, entityIds, path.length === 0 ? key : `${path}.${key}`, ancestors, paths);
2251
2363
  }
2252
2364
  ancestors.delete(value);
@@ -2267,12 +2379,20 @@ function isOwnedLocalEntityValuePath(entityKind, path) {
2267
2379
  if (isMediaAssetVariantKind(entityKind) && /^(?:external\.(?:system|key)|storageKey)$/.test(path)) return true;
2268
2380
  if (entityKind === "voice" && /^voice\.(?:system|key|name)$/.test(path)) return true;
2269
2381
  if (entityKind === "caption" && (path.startsWith("style.") || /^selections\[\d+\]\.segmentId$/.test(path))) return true;
2270
- if (entityKind === "audio-script" && /^segments\[\d+\]\.(?:segmentId|text|language)$/.test(path)) return true;
2382
+ if ([
2383
+ "audio-script",
2384
+ "caption",
2385
+ "phonetic-script"
2386
+ ].includes(entityKind) && /^segments\[\d+\]\.(?:segmentId|text|language)$/.test(path)) return true;
2271
2387
  return false;
2272
2388
  }
2273
2389
  function isOwnedLocalIdPath(entityKind, path) {
2274
2390
  if (path === "lifecycle.actorId") return true;
2275
- if (entityKind === "audio-script" && /^segments\[\d+\]\.segmentId$/.test(path)) return true;
2391
+ if ([
2392
+ "audio-script",
2393
+ "caption",
2394
+ "phonetic-script"
2395
+ ].includes(entityKind) && /^segments\[\d+\]\.segmentId$/.test(path)) return true;
2276
2396
  if (entityKind === "caption" && /^selections\[\d+\]\.segmentId$/.test(path)) return true;
2277
2397
  if (entityKind === "sequence-marker" && /^segmentRanges\[\d+\]\.segmentId$/.test(path)) return true;
2278
2398
  if (entityKind === "asset" && /^(?:tracks\[\d+\]\.trackId|renditions\[\d+\]\.renditionId)$/.test(path)) return true;
@@ -2329,35 +2449,6 @@ function ofKind(relations, kind) {
2329
2449
  return relations.filter((relation) => relation.kind === kind);
2330
2450
  }
2331
2451
  //#endregion
2332
- //#region ../medeo-dsl/src/json-values.ts
2333
- const nativeObjectConstructorSource = Function.prototype.toString.call(Object);
2334
- function isJsonObject(value) {
2335
- return isJsonValue(value, /* @__PURE__ */ new Set()) && !Array.isArray(value) && value !== null;
2336
- }
2337
- function isJsonValue(value, ancestors) {
2338
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
2339
- if (typeof value === "number") return Number.isFinite(value);
2340
- if (typeof value !== "object") return false;
2341
- if (!Array.isArray(value) && !isPlainObject(value)) return false;
2342
- if (ancestors.has(value)) return false;
2343
- ancestors.add(value);
2344
- const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, ancestors)) : Object.values(value).every((item) => isJsonValue(item, ancestors));
2345
- ancestors.delete(value);
2346
- return valid;
2347
- }
2348
- /** Recognize an ordinary object from any VM realm without admitting class instances. */
2349
- function isPlainObject(value) {
2350
- try {
2351
- const prototype = Object.getPrototypeOf(value);
2352
- if (prototype === null) return true;
2353
- if (Object.getPrototypeOf(prototype) !== null) return false;
2354
- const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
2355
- return typeof constructor === "function" && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === nativeObjectConstructorSource;
2356
- } catch {
2357
- return false;
2358
- }
2359
- }
2360
- //#endregion
2361
2452
  //#region ../medeo-dsl/src/relation-specs.ts
2362
2453
  const timelineTrackRelationSpec = emptySpec("timeline-track", "timeline", "track");
2363
2454
  const trackClipRelationSpec = emptySpec("track-clip", "track", "clip");
@@ -2380,15 +2471,6 @@ const generatedRelationSpec = Object.freeze({
2380
2471
  validateEndpoints: (endpoints) => endpoints.every((endpoint) => isGeneratedMedia(endpoint.current())),
2381
2472
  validateMetadata: isEmptyMetadata
2382
2473
  });
2383
- /**
2384
- * Variant composition: the required content relation between a variant and its
2385
- * AudioScript. This is the assembly source for the variant's base text — never
2386
- * a downgradeable provenance label. A variant without exactly one live
2387
- * composition Relation has no valid content and fails validation.
2388
- */
2389
- const phoneticScriptCompositionRelationSpec = metadataSpec("phonetic-script-composition", "phonetic-script", "audio-script", isSegmentAlignmentMetadata);
2390
- /** See {@link phoneticScriptCompositionRelationSpec}; Caption is the display variant. */
2391
- const captionCompositionRelationSpec = metadataSpec("caption-composition", "caption", "audio-script", isSegmentAlignmentMetadata);
2392
2474
  const captionAlignmentRelationSpec = Object.freeze({
2393
2475
  kind: "caption-alignment",
2394
2476
  validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["caption"]), new Set(["audio", "voice"])),
@@ -2437,8 +2519,6 @@ const builtInRelationSpecs = Object.freeze([
2437
2519
  markerTimelineRelationSpec,
2438
2520
  physicalAssetRelationSpec,
2439
2521
  generatedRelationSpec,
2440
- phoneticScriptCompositionRelationSpec,
2441
- captionCompositionRelationSpec,
2442
2522
  captionAlignmentRelationSpec,
2443
2523
  clipAnchorRelationSpec,
2444
2524
  phoneticScriptRenderRelationSpec,
@@ -2481,9 +2561,6 @@ function isAssetBinding(value) {
2481
2561
  const orderingKey = /(order|ordinal|position|rank|index|z[_-]?index)/i;
2482
2562
  return Object.keys(value).every((key) => !orderingKey.test(key));
2483
2563
  }
2484
- function isSegmentAlignmentMetadata(value) {
2485
- return isJsonObject(value) && Object.hasOwn(value, "segmentAlignment");
2486
- }
2487
2564
  function isCaptionAlignmentMetadata(value) {
2488
2565
  return isJsonObject(value) && Object.hasOwn(value, "alignment");
2489
2566
  }
@@ -2669,7 +2746,13 @@ function decodeEntityRelationRows(rows, options) {
2669
2746
  const entitiesById = /* @__PURE__ */ new Map();
2670
2747
  const extensionEntityKinds = new Set(options.entityKinds ?? []);
2671
2748
  for (const row of rows.entities) {
2672
- const entity = decodeEntityRow(row, extensionEntityKinds, issues);
2749
+ let entity = decodeEntityRow(row, extensionEntityKinds, issues);
2750
+ if (entity != null) try {
2751
+ entity = decodeEntityRow(assembleEntityContent(rows, row.entityId), extensionEntityKinds, issues);
2752
+ } catch (error) {
2753
+ issues.push(errorMessage(error));
2754
+ continue;
2755
+ }
2673
2756
  if (entity == null) continue;
2674
2757
  const ref = createEntityRef(entity);
2675
2758
  if (entitiesById.has(ref.entityId)) {
@@ -2849,9 +2932,9 @@ var EntityProjectionGraph = class {
2849
2932
  timeline;
2850
2933
  constructor(rows) {
2851
2934
  this.rows = rows;
2852
- decodeEntityRelationRows(rows, { compareMarkerPoints: (_marker, _range, left, right) => finite(left, "range point") - finite(right, "range point") });
2853
- this.entities = new Map(rows.entities.map((row) => [String(row.entityId), row]));
2854
- const roots = rows.entities.filter((row) => row.entityKind === "timeline");
2935
+ const decoded = decodeEntityRelationRows(rows, { compareMarkerPoints: (_marker, _range, left, right) => finite(left, "range point") - finite(right, "range point") });
2936
+ this.entities = new Map([...decoded.entitiesById.values()].map((ref) => [String(ref.entityId), entityToRow(ref.current())]));
2937
+ const roots = [...this.entities.values()].filter((row) => row.entityKind === "timeline");
2855
2938
  if (roots.length !== 1) fail("compatibility projection requires exactly one Timeline");
2856
2939
  this.timeline = roots[0];
2857
2940
  }
@@ -3226,6 +3309,670 @@ function videoSpeed(content, marker, placement) {
3226
3309
  };
3227
3310
  }
3228
3311
  //#endregion
3312
+ //#region src/entity-crdt/json-containers.ts
3313
+ const atomicFields = new Set([
3314
+ "sequence",
3315
+ "sourceRange",
3316
+ "targetRange",
3317
+ "duration",
3318
+ "extent",
3319
+ "timeRemapping",
3320
+ "textRange",
3321
+ "external"
3322
+ ]);
3323
+ const textFields = new Set(["text", "phonemeScript"]);
3324
+ const orderedFields = new Set([
3325
+ "segments",
3326
+ "selections",
3327
+ "baseEntityIds"
3328
+ ]);
3329
+ function equalJson(a, b) {
3330
+ return canonicalJson(a) === canonicalJson(b);
3331
+ }
3332
+ function canonicalJson(value) {
3333
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
3334
+ if (value !== null && typeof value === "object") return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
3335
+ return JSON.stringify(value);
3336
+ }
3337
+ /** Compile only the changed fields; untouched containers retain their operation identity. */
3338
+ function writeObject(map, before, after) {
3339
+ for (const key of Object.keys(before)) if (!Object.hasOwn(after, key)) map.delete(key);
3340
+ for (const [key, value] of Object.entries(after)) {
3341
+ if (equalJson(before[key], value)) continue;
3342
+ if (atomicFields.has(key)) map.set(key, value);
3343
+ else if (typeof value === "string" && textFields.has(key)) map.ensureMergeableText(key).update(value);
3344
+ else if (Array.isArray(value) && orderedFields.has(key)) writeList(map.ensureMergeableMovableList(key), value);
3345
+ else if (isJsonObject(value)) writeObject(map.ensureMergeableMap(key), isJsonObject(before[key]) ? before[key] : {}, value);
3346
+ else map.set(key, value);
3347
+ }
3348
+ }
3349
+ function itemKey(value) {
3350
+ if (typeof value === "string") return value;
3351
+ if (isJsonObject(value) && typeof value.segmentId === "string") return value.segmentId;
3352
+ throw new Error("Collaborative lists require stable segmentId or unique string identities");
3353
+ }
3354
+ function readItem(list, index) {
3355
+ const value = list.get(index);
3356
+ return value instanceof LoroMap ? value.toJSON() : value;
3357
+ }
3358
+ function writeList(list, values) {
3359
+ const wanted = values.map(itemKey);
3360
+ if (new Set(wanted).size !== wanted.length) throw new Error("Duplicate collaborative list identity");
3361
+ for (let index = list.length - 1; index >= 0; index--) if (!wanted.includes(itemKey(readItem(list, index)))) list.delete(index, 1);
3362
+ for (let index = 0; index < values.length; index++) {
3363
+ const value = values[index];
3364
+ const key = wanted[index];
3365
+ let found = -1;
3366
+ for (let cursor = index; cursor < list.length; cursor++) if (itemKey(readItem(list, cursor)) === key) {
3367
+ found = cursor;
3368
+ break;
3369
+ }
3370
+ if (found === -1) if (isJsonObject(value)) writeObject(list.insertContainer(index, new LoroMap()), {}, value);
3371
+ else list.insert(index, value);
3372
+ else {
3373
+ if (found !== index) list.move(found, index);
3374
+ const current = list.get(index);
3375
+ if (isJsonObject(value)) {
3376
+ if (!(current instanceof LoroMap)) throw new Error("Collaborative list item changed type");
3377
+ writeObject(current, current.toJSON(), value);
3378
+ }
3379
+ }
3380
+ }
3381
+ }
3382
+ function requireText(map, path) {
3383
+ let value = map;
3384
+ for (const part of path) if (value instanceof LoroMap && typeof part === "string") value = value.get(part);
3385
+ else if (value instanceof LoroMovableList && typeof part === "number") value = value.get(part);
3386
+ else throw new Error("Invalid collaborative text path");
3387
+ if (!(value instanceof LoroText)) throw new Error("Field is not collaborative text");
3388
+ return value;
3389
+ }
3390
+ //#endregion
3391
+ //#region src/entity-crdt/ordering.ts
3392
+ /** Domain order is scoped by membership; relation storage has no global order. */
3393
+ function entityOrderGroups(rows) {
3394
+ const entities = new Map(rows.entities.map((row) => [String(row.entityId), row]));
3395
+ const groups = /* @__PURE__ */ new Map();
3396
+ for (const relation of rows.relations) {
3397
+ const childKind = relation.relationKind === "timeline-track" ? "track" : relation.relationKind === "track-clip" ? "clip" : null;
3398
+ if (!childKind) continue;
3399
+ const endpoints = [relation.endpoint0EntityId, relation.endpoint1EntityId];
3400
+ const childId = endpoints.find((id) => entities.get(id)?.entityKind === childKind);
3401
+ if (!childId) continue;
3402
+ const child = entities.get(childId);
3403
+ if (typeof child.payload.order !== "number") continue;
3404
+ const parent = endpoints.find((id) => id !== childId);
3405
+ const values = groups.get(parent) ?? [];
3406
+ values.push({
3407
+ id: childId,
3408
+ order: child.payload.order
3409
+ });
3410
+ groups.set(parent, values);
3411
+ }
3412
+ return new Map([...groups].map(([parent, values]) => [parent, values.sort((a, b) => a.order - b.order || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)).map((item) => item.id)]));
3413
+ }
3414
+ function ordering(doc) {
3415
+ const map = doc.getMap("medeo").get("ordering");
3416
+ if (!(map instanceof LoroMap)) throw new Error("Missing native entity ordering");
3417
+ return map;
3418
+ }
3419
+ /** Returns a derived ordinal while the stable element retains its native move identity. */
3420
+ function entityOrdinal(doc, root) {
3421
+ let found;
3422
+ for (const key of ordering(doc).keys()) {
3423
+ const list = ordering(doc).get(key);
3424
+ if (!(list instanceof LoroMovableList)) throw new Error("Invalid entity order container");
3425
+ const index = list.toArray().indexOf(root);
3426
+ if (index < 0) continue;
3427
+ if (found !== void 0) throw new Error("Entity belongs to multiple ordered groups");
3428
+ found = index;
3429
+ }
3430
+ return found;
3431
+ }
3432
+ /** Move existing elements, insert new identities and delete removed members on the causal list. */
3433
+ function writeEntityOrder(doc, group, roots, token) {
3434
+ if (new Set(roots).size !== roots.length) throw new Error("Duplicate ordered entity identity");
3435
+ const list = ordering(doc).ensureMergeableMovableList(group);
3436
+ const before = list.toArray();
3437
+ for (let index = list.length - 1; index >= 0; index--) if (!roots.includes(String(list.get(index)))) list.delete(index, 1);
3438
+ roots.forEach((root, index) => {
3439
+ const current = list.toArray().indexOf(root);
3440
+ if (current < 0) list.insert(index, root);
3441
+ else if (current !== index) list.move(current, index);
3442
+ });
3443
+ if (token) {
3444
+ const editing = doc.getMap("medeo").get("editing");
3445
+ for (const root of new Set([...before, ...roots])) {
3446
+ if (before.indexOf(root) < 0 || before.indexOf(root) === roots.indexOf(root)) continue;
3447
+ editing.get(root).get("changes").set(token, true);
3448
+ }
3449
+ }
3450
+ }
3451
+ //#endregion
3452
+ //#region src/entity-crdt/entity-document.ts
3453
+ const LORO_ENTITY_SCHEMA = "medeo.entities.loro.v1";
3454
+ const MATERIALIZE_ORIGIN = "medeo.materialize";
3455
+ function hash(value) {
3456
+ return bytesToHex(sha256(new TextEncoder().encode(canonicalJson(value))));
3457
+ }
3458
+ function clone(value) {
3459
+ return structuredClone(value);
3460
+ }
3461
+ function root(doc) {
3462
+ return doc.getMap("medeo");
3463
+ }
3464
+ function requireMap(parent, key) {
3465
+ const value = parent.get(key);
3466
+ if (!(value instanceof LoroMap)) throw new Error(`Missing Loro map ${key}`);
3467
+ return value;
3468
+ }
3469
+ function section(doc, key) {
3470
+ return requireMap(root(doc), key);
3471
+ }
3472
+ function workspace(doc, id) {
3473
+ const value = section(doc, "editing").get(id);
3474
+ if (!(value instanceof LoroMap)) throw new Error(`Unknown entity editing identity ${id}`);
3475
+ return value;
3476
+ }
3477
+ function content(map) {
3478
+ return requireMap(map, "content").toJSON();
3479
+ }
3480
+ function entityContent(doc, key) {
3481
+ const payload = content(workspace(doc, key));
3482
+ const order = entityOrdinal(doc, key);
3483
+ return order === void 0 ? payload : {
3484
+ ...payload,
3485
+ order
3486
+ };
3487
+ }
3488
+ function bases(map) {
3489
+ const links = requireMap(map, "bases");
3490
+ return links.keys().sort((a, b) => a < b ? -1 : a > b ? 1 : 0).map((key) => String(links.get(key)));
3491
+ }
3492
+ function owned(payload) {
3493
+ const { baseEntityIds: _bases, ...fields } = payload;
3494
+ return fields;
3495
+ }
3496
+ function baseIds(payload) {
3497
+ const ids = payload.baseEntityIds ?? [];
3498
+ if (!Array.isArray(ids) || !ids.every((id) => typeof id === "string") || new Set(ids).size !== ids.length) throw new Error("baseEntityIds must contain unique entity IDs");
3499
+ return ids;
3500
+ }
3501
+ function family(doc, id) {
3502
+ const value = section(doc, "origins").get(id);
3503
+ if (typeof value === "string") return value;
3504
+ for (const [key, value] of currentIds(doc)) if (value === id) return key;
3505
+ throw new Error(`Unknown entity ${id}`);
3506
+ }
3507
+ function currentIds(doc) {
3508
+ const result = /* @__PURE__ */ new Map();
3509
+ for (const key of section(doc, "editing").keys().sort((a, b) => a < b ? -1 : a > b ? 1 : 0)) {
3510
+ const map = workspace(doc, key);
3511
+ if (map.get("deleted") === true) continue;
3512
+ const tokens = requireMap(map, "changes").keys().sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
3513
+ const initial = section(doc, "entities").get(key);
3514
+ const { id: _id, kind: _kind, ...initialPayload } = isJsonObject(initial) ? initial : {};
3515
+ const payload = entityContent(doc, key);
3516
+ const changed = tokens.length > 0 || initial !== void 0 && !equalJson(owned(initialPayload), payload);
3517
+ result.set(key, changed ? `entity_${hash([
3518
+ key,
3519
+ tokens,
3520
+ payload
3521
+ ])}` : key);
3522
+ }
3523
+ return result;
3524
+ }
3525
+ function currentId(doc, id, ids = currentIds(doc)) {
3526
+ const resolved = ids.get(family(doc, id));
3527
+ if (resolved === void 0) throw new Error(`Referenced entity ${id} was deleted`);
3528
+ return resolved;
3529
+ }
3530
+ /** Plain domain objects assembled from native Loro state, never a second writable authority. */
3531
+ function readEntityDocument(doc, allowSelectionConflicts = false) {
3532
+ if (root(doc).get("schema") !== "medeo.entities.loro.v1") throw new Error("Document has no Loro entity state");
3533
+ const ids = currentIds(doc);
3534
+ const entities = [...ids].map(([key, entityId]) => {
3535
+ const map = workspace(doc, key);
3536
+ const baseEntityIds = bases(map).map((id) => currentId(doc, id, ids));
3537
+ return {
3538
+ entityId: createEntityId(entityId),
3539
+ entityKind: map.get("kind"),
3540
+ payload: {
3541
+ ...entityContent(doc, key),
3542
+ ...baseEntityIds.length ? { baseEntityIds } : {}
3543
+ }
3544
+ };
3545
+ });
3546
+ const relations = [];
3547
+ for (const key of section(doc, "relations").keys().sort((a, b) => a < b ? -1 : a > b ? 1 : 0)) {
3548
+ const map = section(doc, "relations").get(key);
3549
+ if (!(map instanceof LoroMap) || map.get("deleted") === true) continue;
3550
+ const original = map.get("identity");
3551
+ if (original.endpoints.some((id) => !ids.has(family(doc, id)))) continue;
3552
+ const endpoints = original.endpoints.map((id) => currentId(doc, id, ids));
3553
+ const relationId = equalJson(endpoints, original.endpoints) ? key : `relation_${hash([key, endpoints])}`;
3554
+ relations.push({
3555
+ relationId: createRelationId(relationId),
3556
+ relationKind: original.kind,
3557
+ endpoint0EntityId: createEntityId(endpoints[0]),
3558
+ endpoint1EntityId: createEntityId(endpoints[1]),
3559
+ metadata: requireMap(map, "metadata").toJSON(),
3560
+ trace: requireMap(map, "trace").toJSON()
3561
+ });
3562
+ }
3563
+ const project = {};
3564
+ for (const [key, value] of Object.entries(section(doc, "attachments").toJSON())) {
3565
+ if (typeof value !== "string") throw new Error(`Invalid project attachment ${key}`);
3566
+ project[key] = currentId(doc, value, ids);
3567
+ }
3568
+ const state = {
3569
+ rows: {
3570
+ entities,
3571
+ relations
3572
+ },
3573
+ project
3574
+ };
3575
+ if (!allowSelectionConflicts) for (const [key, id] of ids) {
3576
+ const fingerprint = selectionFingerprint(state.rows, id);
3577
+ if (fingerprint !== void 0 && workspace(doc, key).get("selectionBaseline") !== fingerprint) throw new Error(`Caption text selection conflict at ${id}: source text changed; revise its text ranges explicitly`);
3578
+ }
3579
+ return state;
3580
+ }
3581
+ /** Native editing containers survive domain version changes and merge independently. */
3582
+ var LoroEntityDocument = class LoroEntityDocument {
3583
+ doc;
3584
+ validate;
3585
+ undoManager;
3586
+ constructor(doc, validate) {
3587
+ this.doc = doc;
3588
+ this.validate = validate;
3589
+ this.undoManager = new UndoManager(doc, {
3590
+ mergeInterval: 0,
3591
+ excludeOriginPrefixes: [MATERIALIZE_ORIGIN]
3592
+ });
3593
+ }
3594
+ /** Open a causal snapshot using the same Loro runtime as the compiler. */
3595
+ static fromSnapshot(snapshot, validate) {
3596
+ const doc = new LoroDoc();
3597
+ doc.import(snapshot);
3598
+ readEntityDocument(doc);
3599
+ return new LoroEntityDocument(doc, validate);
3600
+ }
3601
+ static create(rows, project = {}, peerId) {
3602
+ const doc = new LoroDoc();
3603
+ if (peerId !== void 0) doc.setPeerId(peerId);
3604
+ root(doc).set("schema", LORO_ENTITY_SCHEMA);
3605
+ for (const name of [
3606
+ "editing",
3607
+ "entities",
3608
+ "origins",
3609
+ "relations",
3610
+ "project",
3611
+ "attachments",
3612
+ "ordering"
3613
+ ]) root(doc).ensureMergeableMap(name);
3614
+ for (const row of rows.entities) apply(doc, {
3615
+ type: "create",
3616
+ row
3617
+ });
3618
+ for (const row of rows.relations) apply(doc, {
3619
+ type: "relation",
3620
+ row
3621
+ });
3622
+ for (const [slot, entityId] of Object.entries(project)) apply(doc, {
3623
+ type: "attach",
3624
+ slot,
3625
+ entityId
3626
+ });
3627
+ for (const [group, ids] of entityOrderGroups(rows)) writeEntityOrder(doc, group, ids);
3628
+ const initialState = readEntityDocument(doc, true);
3629
+ for (const row of initialState.rows.entities) {
3630
+ const fingerprint = selectionFingerprint(initialState.rows, row.entityId);
3631
+ if (fingerprint !== void 0) workspace(doc, row.entityId).set("selectionBaseline", fingerprint);
3632
+ }
3633
+ assertState(readEntityDocument(doc));
3634
+ materialize(doc);
3635
+ doc.commit({ origin: MATERIALIZE_ORIGIN });
3636
+ return new LoroEntityDocument(doc);
3637
+ }
3638
+ snapshot() {
3639
+ return readEntityDocument(this.doc);
3640
+ }
3641
+ relationsOf(entityId) {
3642
+ const id = currentIds(this.doc).get(family(this.doc, entityId));
3643
+ if (id === void 0) return [];
3644
+ return this.snapshot().rows.relations.filter((row) => row.endpoint0EntityId === id || row.endpoint1EntityId === id);
3645
+ }
3646
+ getVersion(id) {
3647
+ const entity = section(this.doc, "entities").get(id);
3648
+ if (!isJsonObject(entity)) return void 0;
3649
+ const { id: entityId, kind, ...payload } = entity;
3650
+ if (typeof entityId !== "string") throw new Error("Invalid stored entity ID");
3651
+ return {
3652
+ entityId: createEntityId(entityId),
3653
+ entityKind: kind,
3654
+ payload
3655
+ };
3656
+ }
3657
+ /** The callback and every domain validation execute on a causally complete fork first. */
3658
+ transact(edit) {
3659
+ const probe = this.doc.fork();
3660
+ const draft = new LoroEntityDraft(probe);
3661
+ edit(draft);
3662
+ draft.captureSelectionBaselines();
3663
+ const state = readEntityDocument(probe);
3664
+ assertState(state);
3665
+ this.validate?.(state);
3666
+ materialize(probe);
3667
+ const from = this.doc.oplogVersion();
3668
+ for (const command of draft.commands) apply(this.doc, command);
3669
+ this.doc.commit({ origin: "medeo.edit" });
3670
+ materialize(this.doc);
3671
+ this.doc.commit({ origin: MATERIALIZE_ORIGIN });
3672
+ return this.doc.export({
3673
+ mode: "update",
3674
+ from
3675
+ });
3676
+ }
3677
+ /** Import preserves native operation identities; projection errors never discard remote operations. */
3678
+ import(update) {
3679
+ this.doc.import(update);
3680
+ const state = this.snapshot();
3681
+ assertState(state);
3682
+ this.validate?.(state);
3683
+ materialize(this.doc);
3684
+ this.doc.commit({ origin: MATERIALIZE_ORIGIN });
3685
+ }
3686
+ canUndo() {
3687
+ return this.undoManager.canUndo();
3688
+ }
3689
+ canRedo() {
3690
+ return this.undoManager.canRedo();
3691
+ }
3692
+ undo() {
3693
+ return this.historyEdit(() => this.undoManager.undo());
3694
+ }
3695
+ redo() {
3696
+ return this.historyEdit(() => this.undoManager.redo());
3697
+ }
3698
+ historyEdit(run) {
3699
+ const before = this.snapshot();
3700
+ if (!run()) return false;
3701
+ const after = this.snapshot();
3702
+ for (const row of after.rows.entities) {
3703
+ const id = family(this.doc, row.entityId);
3704
+ const previous = before.rows.entities.find((item) => family(this.doc, item.entityId) === id);
3705
+ if (previous && !equalJson(owned(previous.payload), owned(row.payload))) workspace(this.doc, id).ensureMergeableMap("changes").set(globalThis.crypto.randomUUID(), true);
3706
+ }
3707
+ materialize(this.doc);
3708
+ this.doc.commit({ origin: MATERIALIZE_ORIGIN });
3709
+ return true;
3710
+ }
3711
+ };
3712
+ var LoroEntityDraft = class {
3713
+ doc;
3714
+ commands = [];
3715
+ selectedRoots = /* @__PURE__ */ new Set();
3716
+ constructor(doc) {
3717
+ this.doc = doc;
3718
+ }
3719
+ snapshot() {
3720
+ return readEntityDocument(this.doc, true);
3721
+ }
3722
+ order(groupId, entityIds) {
3723
+ this.record({
3724
+ type: "order",
3725
+ group: family(this.doc, groupId),
3726
+ roots: entityIds.map((id) => family(this.doc, id)),
3727
+ token: globalThis.crypto.randomUUID()
3728
+ });
3729
+ }
3730
+ create(row) {
3731
+ this.record({
3732
+ type: "create",
3733
+ row: clone(row)
3734
+ });
3735
+ }
3736
+ delete(entityId) {
3737
+ this.record({
3738
+ type: "delete",
3739
+ root: family(this.doc, entityId)
3740
+ });
3741
+ }
3742
+ attach(slot, entityId) {
3743
+ this.record({
3744
+ type: "attach",
3745
+ slot,
3746
+ entityId
3747
+ });
3748
+ }
3749
+ link(row) {
3750
+ this.record({
3751
+ type: "relation",
3752
+ row: clone(row)
3753
+ });
3754
+ }
3755
+ patchRelation(relationId, metadata, trace) {
3756
+ this.record({
3757
+ type: "relation-fields",
3758
+ root: this.relationRoot(relationId),
3759
+ metadata: clone(metadata),
3760
+ trace: clone(trace)
3761
+ });
3762
+ }
3763
+ unlink(relationId) {
3764
+ this.record({
3765
+ type: "unlink",
3766
+ root: this.relationRoot(relationId)
3767
+ });
3768
+ }
3769
+ relationRoot(relationId) {
3770
+ const ids = currentIds(this.doc);
3771
+ for (const key of section(this.doc, "relations").keys()) {
3772
+ const map = section(this.doc, "relations").get(key);
3773
+ if (!(map instanceof LoroMap) || map.get("deleted") === true) continue;
3774
+ const identity = map.get("identity");
3775
+ if (identity.endpoints.some((id) => !ids.has(family(this.doc, id)))) continue;
3776
+ const endpoints = identity.endpoints.map((id) => currentId(this.doc, id, ids));
3777
+ if ((equalJson(endpoints, identity.endpoints) ? key : `relation_${hash([key, endpoints])}`) === relationId) return key;
3778
+ }
3779
+ throw new Error(`Unknown relation ${relationId}`);
3780
+ }
3781
+ update(entityId, fields) {
3782
+ const state = this.snapshot();
3783
+ const id = createEntityId(currentId(this.doc, entityId));
3784
+ const updates = /* @__PURE__ */ new Map();
3785
+ for (const [field, value] of Object.entries(fields)) {
3786
+ const owner = resolveEntityFieldOwner(state.rows, id, field);
3787
+ const payload = updates.get(owner) ?? clone(state.rows.entities.find((row) => row.entityId === owner).payload);
3788
+ updates.set(owner, {
3789
+ ...payload,
3790
+ [field]: clone(value)
3791
+ });
3792
+ }
3793
+ for (const [owner, payload] of updates) this.replaceOwned(owner, payload);
3794
+ }
3795
+ declare(entityId, fields) {
3796
+ const row = this.snapshot().rows.entities.find((row) => row.entityId === currentId(this.doc, entityId));
3797
+ if (!row) throw new Error(`Unknown entity ${entityId}`);
3798
+ this.replaceOwned(entityId, {
3799
+ ...row.payload,
3800
+ ...fields
3801
+ });
3802
+ }
3803
+ replaceOwned(entityId, payload) {
3804
+ this.record({
3805
+ type: "write",
3806
+ root: family(this.doc, entityId),
3807
+ payload: clone(payload),
3808
+ token: globalThis.crypto.randomUUID()
3809
+ });
3810
+ }
3811
+ editText(entityId, path, start, deleteCount, text) {
3812
+ if (typeof path[0] !== "string") throw new Error("Text path must start with a field");
3813
+ const owner = resolveEntityFieldOwner(this.snapshot().rows, createEntityId(currentId(this.doc, entityId)), path[0]);
3814
+ this.record({
3815
+ type: "text",
3816
+ root: family(this.doc, owner),
3817
+ path: [...path],
3818
+ start,
3819
+ deleteCount,
3820
+ text,
3821
+ token: globalThis.crypto.randomUUID()
3822
+ });
3823
+ }
3824
+ captureSelectionBaselines() {
3825
+ const state = this.snapshot();
3826
+ for (const key of this.selectedRoots) {
3827
+ const id = currentIds(this.doc).get(key);
3828
+ if (id === void 0) continue;
3829
+ const fingerprint = selectionFingerprint(state.rows, id);
3830
+ if (fingerprint !== void 0) this.record({
3831
+ type: "selection-baseline",
3832
+ root: key,
3833
+ fingerprint
3834
+ });
3835
+ }
3836
+ }
3837
+ record(command) {
3838
+ if (command.type === "create") this.selectedRoots.add(command.row.entityId);
3839
+ if (command.type === "write" && !equalJson(content(workspace(this.doc, command.root)).selections, command.payload.selections)) this.selectedRoots.add(command.root);
3840
+ apply(this.doc, command);
3841
+ this.commands.push(command);
3842
+ }
3843
+ };
3844
+ function apply(doc, command) {
3845
+ switch (command.type) {
3846
+ case "selection-baseline":
3847
+ workspace(doc, command.root).set("selectionBaseline", command.fingerprint);
3848
+ break;
3849
+ case "order":
3850
+ writeEntityOrder(doc, command.group, command.roots, command.token);
3851
+ break;
3852
+ case "create": {
3853
+ const row = command.row;
3854
+ if (section(doc, "origins").get(row.entityId) !== void 0) throw new Error(`Duplicate entity ${row.entityId}`);
3855
+ const map = section(doc, "editing").ensureMergeableMap(row.entityId);
3856
+ map.set("kind", row.entityKind);
3857
+ map.ensureMergeableMap("changes");
3858
+ section(doc, "origins").set(row.entityId, row.entityId);
3859
+ writeObject(map.ensureMergeableMap("content"), {}, owned(row.payload));
3860
+ writeBases(doc, map, baseIds(row.payload));
3861
+ break;
3862
+ }
3863
+ case "write": {
3864
+ const map = workspace(doc, command.root);
3865
+ const before = content(map);
3866
+ const after = owned(command.payload);
3867
+ if (!equalJson(before, after)) {
3868
+ writeObject(map.ensureMergeableMap("content"), before, after);
3869
+ map.ensureMergeableMap("changes").set(command.token, true);
3870
+ }
3871
+ writeBases(doc, map, baseIds(command.payload));
3872
+ break;
3873
+ }
3874
+ case "text": {
3875
+ const map = workspace(doc, command.root);
3876
+ const text = requireText(map.ensureMergeableMap("content"), command.path);
3877
+ if (!Number.isSafeInteger(command.start) || !Number.isSafeInteger(command.deleteCount) || command.start < 0 || command.deleteCount < 0 || command.start + command.deleteCount > Array.from(text.toString()).length) throw new Error("Invalid text edit range");
3878
+ const start = text.convertPos(command.start, "unicode", "utf16");
3879
+ const end = text.convertPos(command.start + command.deleteCount, "unicode", "utf16");
3880
+ text.splice(start, end - start, command.text);
3881
+ map.ensureMergeableMap("changes").set(command.token, true);
3882
+ break;
3883
+ }
3884
+ case "delete":
3885
+ workspace(doc, command.root).set("deleted", true);
3886
+ break;
3887
+ case "relation": {
3888
+ const row = command.row;
3889
+ const map = section(doc, "relations").ensureMergeableMap(row.relationId);
3890
+ const identity = {
3891
+ kind: row.relationKind,
3892
+ endpoints: [row.endpoint0EntityId, row.endpoint1EntityId]
3893
+ };
3894
+ const previous = map.get("identity");
3895
+ if (previous !== void 0 && !equalJson(previous, identity)) throw new Error("Relation identity cannot change kind or endpoint positions");
3896
+ if (previous === void 0) map.set("identity", identity);
3897
+ if (map.get("deleted") === true) throw new Error("Deleted relation IDs cannot be reused");
3898
+ const metadata = map.ensureMergeableMap("metadata");
3899
+ const trace = map.ensureMergeableMap("trace");
3900
+ writeObject(metadata, metadata.toJSON(), row.metadata);
3901
+ writeObject(trace, trace.toJSON(), row.trace);
3902
+ break;
3903
+ }
3904
+ case "unlink": {
3905
+ const map = section(doc, "relations").get(command.root);
3906
+ if (!(map instanceof LoroMap)) throw new Error("Unknown relation");
3907
+ map.set("deleted", true);
3908
+ break;
3909
+ }
3910
+ case "relation-fields": {
3911
+ const map = section(doc, "relations").get(command.root);
3912
+ if (!(map instanceof LoroMap)) throw new Error("Unknown relation");
3913
+ const metadata = map.ensureMergeableMap("metadata");
3914
+ const trace = map.ensureMergeableMap("trace");
3915
+ writeObject(metadata, metadata.toJSON(), command.metadata);
3916
+ writeObject(trace, trace.toJSON(), command.trace);
3917
+ break;
3918
+ }
3919
+ case "attach":
3920
+ section(doc, "attachments").set(command.slot, command.entityId);
3921
+ break;
3922
+ }
3923
+ }
3924
+ function writeBases(doc, map, ids) {
3925
+ const links = map.ensureMergeableMap("bases");
3926
+ const wanted = new Map(ids.map((id) => {
3927
+ const origin = section(doc, "origins").get(id);
3928
+ return [typeof origin === "string" ? origin : [...currentIds(doc)].find(([, current]) => current === id)?.[0] ?? id, id];
3929
+ }));
3930
+ if (wanted.size !== ids.length) throw new Error("Duplicate base entity family");
3931
+ for (const key of links.keys()) if (!wanted.has(key)) links.delete(key);
3932
+ for (const [key, id] of wanted) if (links.get(key) === void 0) links.set(key, id);
3933
+ }
3934
+ /** Numeric sub-spans must not silently quote different text after a collaborative edit. */
3935
+ function selectionFingerprint(rows, id) {
3936
+ const row = rows.entities.find((row) => row.entityId === id);
3937
+ if (row?.entityKind !== "caption" || !Array.isArray(row.payload.selections)) return void 0;
3938
+ const ranged = row.payload.selections.filter((selection) => isJsonObject(selection) && selection.textRange !== void 0);
3939
+ if (ranged.length === 0) return void 0;
3940
+ const segments = assembleEntityContent(rows, row.entityId).payload.segments;
3941
+ if (!Array.isArray(segments)) throw new Error("Caption text selection has no source segments");
3942
+ return hash(ranged.map((selection) => {
3943
+ if (!isJsonObject(selection)) throw new Error("Invalid Caption text selection");
3944
+ const source = segments.find((segment) => isJsonObject(segment) && segment.segmentId === selection.segmentId);
3945
+ if (!isJsonObject(source) || typeof source.text !== "string") throw new Error("Caption text selection source is missing");
3946
+ return [selection, source.text];
3947
+ }));
3948
+ }
3949
+ function assertState(state) {
3950
+ for (const row of state.rows.entities) assembleEntityContent(state.rows, row.entityId);
3951
+ const ids = new Set(state.rows.entities.map((row) => String(row.entityId)));
3952
+ for (const row of state.rows.relations) if (!ids.has(row.endpoint0EntityId) || !ids.has(row.endpoint1EntityId)) throw new Error("Relation has a missing endpoint");
3953
+ }
3954
+ function materialize(doc) {
3955
+ const state = readEntityDocument(doc);
3956
+ const byId = section(doc, "entities");
3957
+ const ids = currentIds(doc);
3958
+ for (const [source, id] of ids) {
3959
+ const origins = section(doc, "origins");
3960
+ if (origins.get(id) !== source) origins.set(id, source);
3961
+ }
3962
+ for (const row of state.rows.entities) {
3963
+ const next = {
3964
+ id: row.entityId,
3965
+ kind: row.entityKind,
3966
+ ...row.payload
3967
+ };
3968
+ const previous = byId.get(row.entityId);
3969
+ if (isJsonObject(previous) && !equalJson(owned(previous), owned(next))) throw new Error("Immutable entity version collision");
3970
+ if (!equalJson(previous, next)) byId.set(row.entityId, next);
3971
+ }
3972
+ const project = section(doc, "project");
3973
+ for (const [slot, id] of Object.entries(state.project)) if (project.get(slot) !== id) project.set(slot, id);
3974
+ }
3975
+ //#endregion
3229
3976
  //#region src/document/mirror-read.ts
3230
3977
  /**
3231
3978
  * Project the mirror state (`VideoDocumentDraft`) into the authoritative
@@ -3317,7 +4064,9 @@ var MirrorVideoDocumentAdapter = class {
3317
4064
  });
3318
4065
  }
3319
4066
  snapshot() {
3320
- return readVideoDocumentFromDraft(this.mirror.getState());
4067
+ const document = readVideoDocumentFromDraft(this.mirror.getState());
4068
+ if (this.doc.getMap("medeo").get("schema") === "medeo.entities.loro.v1") return projectEntityTimeline(readEntityDocument(this.doc).rows, document.meta);
4069
+ return document;
3321
4070
  }
3322
4071
  /**
3323
4072
  * True once the doc holds real document content. A fresh mirror over an empty
@@ -3539,4 +4288,4 @@ function createPlainMemoryAdapter(document, options) {
3539
4288
  return new PlainMemoryAdapter(document, options);
3540
4289
  }
3541
4290
  //#endregion
3542
- export { ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION as $, VideoDocumentValidationError as A, cascadeAfterVideoClipChanges as B, hasSequence as C, derivePositionFromAbs as D, buildSpeechHostMap as E, LANE_KINDS_IN_STACK_ORDER as F, resolveAllSpeechOverlaps as G, fillMainTrackTimeGaps as H, ensureLaneTrack as I, TIMELINE_SKELETON_DURATION_MS as J, resolveSpeechOverlapByShiftingVideos as K, findLaneTrack as L, validateVideoDocument as M, partUnionSchema as N, fromVideoDocument as O, videoDocumentSchema as P, DEFAULT_UNIT_TIME_MS as Q, laneTrackId as R, selectAudioScriptSegments as S, buildInitialVideoDocument as T, reassignSpeechesToVideoClipsByTime as U, arrangeMainTrackSeamlessly as V, recalculateTimelineDuration as W, partDurationMs as X, isEmptyVideoClip as Y, safeDurationMs as Z, ScriptCompositionError as _, applyEntityTimelineProjection as a, videoDocumentMirrorSchema as at, assembleScriptText as b, writeVideoDocumentToDraft as c, resolveEntityTimelineLayout as d, VIDEO_DOCUMENT_SCHEMA_VERSION as et, EntityTimelineProjectionError as f, createRelationId as g, createEntityId as h, MirrorVideoDocumentAdapter as i, recordEntries as it, assertValidVideoDocument as j, toVideoDocument as k, readVideoDocumentFromDraft as l, isJsonObject as m, createPlainMemoryAdapter as n, speedOf as nt, createMirrorVideoDocument as o, base64ToBytes as ot, decodeEntityRelationRows as p, syncAggregatedClipsTimePosition as q, generatePartId as r, partUnionToDraft as rt, createMirrorVideoDocumentAdapter as s, bytesToBase64 as st, PlainMemoryAdapter as t, effectiveVideoClipDurationMs as tt, projectEntityTimeline as u, assembleCaptionContent as v, isMediaAssetVariantKind as w, findComposedAudioScript as x, assemblePhoneticScriptContent as y, solveVideoDocument as z };
4291
+ export { reassignSpeechesToVideoClipsByTime as $, isJsonObject as A, VideoDocumentValidationError as B, assembleEntityContent as C, selectAudioScriptSegments as D, findComposedAudioScript as E, buildInitialVideoDocument as F, LANE_KINDS_IN_STACK_ORDER as G, validateVideoDocument as H, buildSpeechHostMap as I, laneTrackId as J, ensureLaneTrack as K, derivePositionFromAbs as L, createRelationId as M, hasSequence as N, updateEntityFields as O, isMediaAssetVariantKind as P, fillMainTrackTimeGaps as Q, fromVideoDocument as R, assembleCaptionContent as S, assembleScriptText as T, partUnionSchema as U, assertValidVideoDocument as V, videoDocumentSchema as W, cascadeAfterVideoClipChanges as X, solveVideoDocument as Y, arrangeMainTrackSeamlessly as Z, EntityProjectionGraph as _, bytesToBase64 as _t, applyEntityTimelineProjection as a, isEmptyVideoClip as at, decodeEntityRelationRows as b, writeVideoDocumentToDraft as c, DEFAULT_UNIT_TIME_MS as ct, LoroEntityDocument as d, effectiveVideoClipDurationMs as dt, recalculateTimelineDuration as et, LoroEntityDraft as f, speedOf as ft, projectEntityTimeline as g, base64ToBytes as gt, equalJson as h, videoDocumentMirrorSchema as ht, MirrorVideoDocumentAdapter as i, TIMELINE_SKELETON_DURATION_MS as it, createEntityId as j, variantBaseEntityIds as k, readVideoDocumentFromDraft as l, ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION as lt, entityOrderGroups as m, recordEntries as mt, createPlainMemoryAdapter as n, resolveSpeechOverlapByShiftingVideos as nt, createMirrorVideoDocument as o, partDurationMs as ot, readEntityDocument as p, partUnionToDraft as pt, findLaneTrack as q, generatePartId as r, syncAggregatedClipsTimePosition as rt, createMirrorVideoDocumentAdapter as s, safeDurationMs as st, PlainMemoryAdapter as t, resolveAllSpeechOverlaps as tt, LORO_ENTITY_SCHEMA as u, VIDEO_DOCUMENT_SCHEMA_VERSION as ut, resolveEntityTimelineLayout as v, assemblePhoneticScriptContent as w, ScriptCompositionError as x, EntityTimelineProjectionError as y, toVideoDocument as z };