@mengine/medeo-client 1.3.1-alpha.10 → 1.3.1-alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -44,7 +44,7 @@ boundary already fails rows whose media variants lack `external` or carry a
44
44
  media is an explicit caller decision, and no implicit historical merge or
45
45
  identity rewriting occurs. The
46
46
  compatibility reader reads physical facts directly from the media variant;
47
- Caption text is assembled from its required AudioScript composition and never stored in an inline Asset.
47
+ Caption text is assembled from its required AudioScript composition. The host may associate Caption with a real text Asset through `physical-asset`, keyed by Caption identity; the sandbox does not manage Assets. The association is optional and does not replace composition. Editing inherited text through Caption advances the AudioScript version and its base reference, preserving the Caption ID unless its own fields change.
48
48
 
49
49
  `SequenceMarker` owns source/target ranges, duration, and time remapping. `Clip`
50
50
  owns clip properties such as volume; `Track` owns track properties. References
@@ -80,7 +80,7 @@ with `assembleCaptionContent` or `assemblePhoneticScriptContent`. Broken composi
80
80
  fails explicitly. Same-name fields from different bases are rejected even for equal values and even if the variant declares that field. After base fields are unambiguous, explicit own fields may override them without mutating the bases. Recorded Audio/Video/Voice can be the ASR source via
81
81
  `audio-script-source`, without creating a synthesis variant.
82
82
 
83
- `insertCaptionClip` requires `baseEntityIds` and `selections`. Each selection
83
+ `insertCaptionClip` requires `baseEntityIds` and `selections`; its optional `captionEntityId` preserves a generation-provided business identity independently of `captionClipEntityId`. Each selection
84
84
  names a source `segmentId` and optionally a half-open Unicode code-point
85
85
  `textRange: {start, end}`. This permits display re-segmentation without rewriting
86
86
  the original script. `upsertVoiceoverTake` requires the real
@@ -1784,9 +1784,8 @@ function entityToRow(entity) {
1784
1784
  //#region ../medeo-dsl/src/invariants.ts
1785
1785
  /**
1786
1786
  * Media variants own their Asset identity directly: a `physical-asset`
1787
- * Relation may not bind them. Caption text is composed domain content, so
1788
- * Captions do not bind Physical Assets either; the Relation vocabulary stays
1789
- * open for future physical pipeline semantics.
1787
+ * Relation may not bind them. Caption may associate with a physical text Asset
1788
+ * independently of its AudioScript composition.
1790
1789
  */
1791
1790
  function validateMediaAssetIdentity(entity, index) {
1792
1791
  return [...index.relationsOf(entity)].filter((relation) => relation.kind === "physical-asset").map(() => ({
@@ -1945,11 +1944,6 @@ function validatePhoneticComposition(phonetic, index, entities = []) {
1945
1944
  }
1946
1945
  function validateVariantComposition(variant, index, entities, kind) {
1947
1946
  const issues = [];
1948
- if (kind === "caption" && ofKind([...index.relationsOf(variant)], "physical-asset").length > 0) issues.push({
1949
- code: "caption_physical_asset_forbidden",
1950
- entityId: variant.entityId,
1951
- message: "Caption text is composed from its AudioScript; Caption must not bind a Physical Asset"
1952
- });
1953
1947
  const rows = {
1954
1948
  entities: collectRelatedEntities([...entities, variant], index).entities.map((ref) => entityToRow(ref.current())),
1955
1949
  relations: []
@@ -3487,7 +3481,7 @@ function entityContent(doc, key) {
3487
3481
  }
3488
3482
  function bases(map) {
3489
3483
  const links = requireMap(map, "bases");
3490
- return links.keys().sort((a, b) => Number(a) - Number(b)).map((key) => String(links.get(key)));
3484
+ return links.keys().sort((a, b) => a < b ? -1 : a > b ? 1 : 0).map((key) => String(links.get(key)));
3491
3485
  }
3492
3486
  function owned(payload) {
3493
3487
  const { baseEntityIds: _bases, ...fields } = payload;
@@ -3561,7 +3555,7 @@ function readEntityDocument(doc, allowSelectionConflicts = false) {
3561
3555
  });
3562
3556
  }
3563
3557
  const project = {};
3564
- for (const [key, value] of Object.entries(section(doc, "project").toJSON())) {
3558
+ for (const [key, value] of Object.entries(section(doc, "attachments").toJSON())) {
3565
3559
  if (typeof value !== "string") throw new Error(`Invalid project attachment ${key}`);
3566
3560
  project[key] = currentId(doc, value, ids);
3567
3561
  }
@@ -3608,6 +3602,7 @@ var LoroEntityDocument = class LoroEntityDocument {
3608
3602
  "origins",
3609
3603
  "relations",
3610
3604
  "project",
3605
+ "attachments",
3611
3606
  "ordering"
3612
3607
  ]) root(doc).ensureMergeableMap(name);
3613
3608
  for (const row of rows.entities) apply(doc, {
@@ -3856,7 +3851,7 @@ function apply(doc, command) {
3856
3851
  map.ensureMergeableMap("changes");
3857
3852
  section(doc, "origins").set(row.entityId, row.entityId);
3858
3853
  writeObject(map.ensureMergeableMap("content"), {}, owned(row.payload));
3859
- writeBases(map, baseIds(row.payload));
3854
+ writeBases(doc, map, baseIds(row.payload));
3860
3855
  break;
3861
3856
  }
3862
3857
  case "write": {
@@ -3867,7 +3862,7 @@ function apply(doc, command) {
3867
3862
  writeObject(map.ensureMergeableMap("content"), before, after);
3868
3863
  map.ensureMergeableMap("changes").set(command.token, true);
3869
3864
  }
3870
- writeBases(map, baseIds(command.payload));
3865
+ writeBases(doc, map, baseIds(command.payload));
3871
3866
  break;
3872
3867
  }
3873
3868
  case "text": {
@@ -3916,16 +3911,19 @@ function apply(doc, command) {
3916
3911
  break;
3917
3912
  }
3918
3913
  case "attach":
3919
- section(doc, "project").set(command.slot, command.entityId);
3914
+ section(doc, "attachments").set(command.slot, command.entityId);
3920
3915
  break;
3921
3916
  }
3922
3917
  }
3923
- function writeBases(map, ids) {
3918
+ function writeBases(doc, map, ids) {
3924
3919
  const links = map.ensureMergeableMap("bases");
3925
- for (const key of links.keys()) if (Number(key) >= ids.length) links.delete(key);
3926
- ids.forEach((id, index) => {
3927
- if (links.get(String(index)) !== id) links.set(String(index), id);
3928
- });
3920
+ const wanted = new Map(ids.map((id) => {
3921
+ const origin = section(doc, "origins").get(id);
3922
+ return [typeof origin === "string" ? origin : [...currentIds(doc)].find(([, current]) => current === id)?.[0] ?? id, id];
3923
+ }));
3924
+ if (wanted.size !== ids.length) throw new Error("Duplicate base entity family");
3925
+ for (const key of links.keys()) if (!wanted.has(key)) links.delete(key);
3926
+ for (const [key, id] of wanted) if (links.get(key) === void 0) links.set(key, id);
3929
3927
  }
3930
3928
  /** Numeric sub-spans must not silently quote different text after a collaborative edit. */
3931
3929
  function selectionFingerprint(rows, id) {
@@ -3964,7 +3962,6 @@ function materialize(doc) {
3964
3962
  const previous = byId.get(row.entityId);
3965
3963
  if (isJsonObject(previous) && !equalJson(owned(previous), owned(next))) throw new Error("Immutable entity version collision");
3966
3964
  if (!equalJson(previous, next)) byId.set(row.entityId, next);
3967
- writeBases(workspace(doc, family(doc, row.entityId)), baseIds(row.payload));
3968
3965
  }
3969
3966
  const project = section(doc, "project");
3970
3967
  for (const [slot, id] of Object.entries(state.project)) if (project.get(slot) !== id) project.set(slot, id);
package/dist/index.d.ts CHANGED
@@ -454,6 +454,8 @@ interface PatchCaptionStyleInput {
454
454
  }
455
455
  interface InsertCaptionClipInput {
456
456
  readonly timelineEntityId: string;
457
+ /** Existing generation identity for newly materialized Caption content, distinct from its Clip. */
458
+ readonly captionEntityId?: string;
457
459
  /** Stable placed caption identity, distinct from the Caption content identity. */
458
460
  readonly captionClipEntityId?: string;
459
461
  /** Existing bases composed by this variant; includes an AudioScript text owner. */
@@ -592,7 +594,7 @@ interface EditorFoundation {
592
594
  readonly rows: EntityRelationRows;
593
595
  readonly timelineEntityId: string;
594
596
  readonly trackEntityIds: Readonly<Record<EditorTrackRole, string>>;
595
- /** Fixed identity of the project's one AudioScript, minted here when missing. */
597
+ /** Initial or current AudioScript content ID selected by the foundation. */
596
598
  readonly audioScriptEntityId: string;
597
599
  }
598
600
  /**
@@ -602,7 +604,7 @@ interface EditorFoundation {
602
604
  * Image/Video/Audio/Voice Entity must carry its own external locator, every
603
605
  * external asset id has exactly one claimant, and no media variant may be
604
606
  * bound through a legacy `physical-asset` Relation. Dual-entity media graphs
605
- * are rejected, not migrated. Caption composes AudioScript text and has no physical text Asset.
607
+ * are rejected, not migrated. Caption composes AudioScript text and may associate with a physical text Asset.
606
608
  */
607
609
  declare function assertCanonicalEditorResources(rows: EntityRelationRows): void;
608
610
  /**
@@ -613,7 +615,7 @@ declare function assertCanonicalEditorResources(rows: EntityRelationRows): void;
613
615
  *
614
616
  * 1. every Image/Video/Audio/Voice Entity carries its own Asset `external`
615
617
  * identity;
616
- * 2. no `physical-asset` Relation may bind a media variant or Caption;
618
+ * 2. no `physical-asset` Relation may bind a media variant;
617
619
  * 3. a self-carried media identity is immutable — it cannot be rewritten to
618
620
  * another asset id.
619
621
  *
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as reassignSpeechesToVideoClipsByTime, A as isJsonObject, B as VideoDocumentValidationError, C as assembleEntityContent, D as selectAudioScriptSegments, E as findComposedAudioScript, F as buildInitialVideoDocument, G as LANE_KINDS_IN_STACK_ORDER, H as validateVideoDocument, I as buildSpeechHostMap, J as laneTrackId, K as ensureLaneTrack, L as derivePositionFromAbs, M as createRelationId, N as hasSequence, O as updateEntityFields, P as isMediaAssetVariantKind, Q as fillMainTrackTimeGaps, R as fromVideoDocument, S as assembleCaptionContent, T as assembleScriptText, U as partUnionSchema, V as assertValidVideoDocument, W as videoDocumentSchema, X as cascadeAfterVideoClipChanges, Y as solveVideoDocument, Z as arrangeMainTrackSeamlessly, _ as EntityProjectionGraph, _t as bytesToBase64, a as applyEntityTimelineProjection, at as isEmptyVideoClip, b as decodeEntityRelationRows, c as writeVideoDocumentToDraft, ct as DEFAULT_UNIT_TIME_MS, d as LoroEntityDocument, dt as effectiveVideoClipDurationMs, et as recalculateTimelineDuration, f as LoroEntityDraft, ft as speedOf, g as projectEntityTimeline, gt as base64ToBytes, h as equalJson, ht as videoDocumentMirrorSchema, i as MirrorVideoDocumentAdapter, it as TIMELINE_SKELETON_DURATION_MS, j as createEntityId, k as variantBaseEntityIds, l as readVideoDocumentFromDraft, lt as ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION, m as entityOrderGroups, mt as recordEntries, n as createPlainMemoryAdapter, nt as resolveSpeechOverlapByShiftingVideos, o as createMirrorVideoDocument, ot as partDurationMs, p as readEntityDocument, pt as partUnionToDraft, q as findLaneTrack, r as generatePartId, rt as syncAggregatedClipsTimePosition, s as createMirrorVideoDocumentAdapter, st as safeDurationMs, t as PlainMemoryAdapter, tt as resolveAllSpeechOverlaps, u as LORO_ENTITY_SCHEMA, ut as VIDEO_DOCUMENT_SCHEMA_VERSION, v as resolveEntityTimelineLayout, w as assemblePhoneticScriptContent, x as ScriptCompositionError, y as EntityTimelineProjectionError, z as toVideoDocument } from "./document-BxWxPFTj.js";
1
+ import { $ as reassignSpeechesToVideoClipsByTime, A as isJsonObject, B as VideoDocumentValidationError, C as assembleEntityContent, D as selectAudioScriptSegments, E as findComposedAudioScript, F as buildInitialVideoDocument, G as LANE_KINDS_IN_STACK_ORDER, H as validateVideoDocument, I as buildSpeechHostMap, J as laneTrackId, K as ensureLaneTrack, L as derivePositionFromAbs, M as createRelationId, N as hasSequence, O as updateEntityFields, P as isMediaAssetVariantKind, Q as fillMainTrackTimeGaps, R as fromVideoDocument, S as assembleCaptionContent, T as assembleScriptText, U as partUnionSchema, V as assertValidVideoDocument, W as videoDocumentSchema, X as cascadeAfterVideoClipChanges, Y as solveVideoDocument, Z as arrangeMainTrackSeamlessly, _ as EntityProjectionGraph, _t as bytesToBase64, a as applyEntityTimelineProjection, at as isEmptyVideoClip, b as decodeEntityRelationRows, c as writeVideoDocumentToDraft, ct as DEFAULT_UNIT_TIME_MS, d as LoroEntityDocument, dt as effectiveVideoClipDurationMs, et as recalculateTimelineDuration, f as LoroEntityDraft, ft as speedOf, g as projectEntityTimeline, gt as base64ToBytes, h as equalJson, ht as videoDocumentMirrorSchema, i as MirrorVideoDocumentAdapter, it as TIMELINE_SKELETON_DURATION_MS, j as createEntityId, k as variantBaseEntityIds, l as readVideoDocumentFromDraft, lt as ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION, m as entityOrderGroups, mt as recordEntries, n as createPlainMemoryAdapter, nt as resolveSpeechOverlapByShiftingVideos, o as createMirrorVideoDocument, ot as partDurationMs, p as readEntityDocument, pt as partUnionToDraft, q as findLaneTrack, r as generatePartId, rt as syncAggregatedClipsTimePosition, s as createMirrorVideoDocumentAdapter, st as safeDurationMs, t as PlainMemoryAdapter, tt as resolveAllSpeechOverlaps, u as LORO_ENTITY_SCHEMA, ut as VIDEO_DOCUMENT_SCHEMA_VERSION, v as resolveEntityTimelineLayout, w as assemblePhoneticScriptContent, x as ScriptCompositionError, y as EntityTimelineProjectionError, z as toVideoDocument } from "./document-CF-q_mRy.js";
2
2
  import { addSpeechesInputSchema, addVideoClipsInputSchema, adjustBgmVolumeInputSchema, adjustSpeechVolumeInputSchema, adjustVideoClipDurationInputSchema, adjustVideoClipVolumeInputSchema, changeSpeechScriptInputSchema, changeSpeechVoiceInputSchema, deleteBgmInputSchema, deleteSpeechesInputSchema, deleteVideoClipsInputSchema, moveSpeechesInputSchema, moveVideoClipsByAnchorInputSchema, moveVideoClipsInputSchema, replaceVideoClipContentInputSchema, replaceVideoClipSequenceInputSchema, setBgmInputSchema, setCaptionStyleInputSchema, setCaptionVisibilityInputSchema, setVideoClipSpeedShiftInputSchema, t as schemas_exports } from "./schemas.js";
3
3
  import { LoroDoc, VersionVector } from "loro-crdt";
4
4
  import { ClientServerSynchronizer, DocManager } from "@mengine/sync";
@@ -292,7 +292,7 @@ const roleOrder$1 = {
292
292
  * Image/Video/Audio/Voice Entity must carry its own external locator, every
293
293
  * external asset id has exactly one claimant, and no media variant may be
294
294
  * bound through a legacy `physical-asset` Relation. Dual-entity media graphs
295
- * are rejected, not migrated. Caption composes AudioScript text and has no physical text Asset.
295
+ * are rejected, not migrated. Caption composes AudioScript text and may associate with a physical text Asset.
296
296
  */
297
297
  function assertCanonicalEditorResources(rows) {
298
298
  if (rows.entities.filter((row) => row.entityKind === "timeline").length > 1) throw new EntityTimelineProjectionError("Editor requires exactly one Timeline");
@@ -323,7 +323,7 @@ function assertCanonicalEditorResources(rows) {
323
323
  *
324
324
  * 1. every Image/Video/Audio/Voice Entity carries its own Asset `external`
325
325
  * identity;
326
- * 2. no `physical-asset` Relation may bind a media variant or Caption;
326
+ * 2. no `physical-asset` Relation may bind a media variant;
327
327
  * 3. a self-carried media identity is immutable — it cannot be rewritten to
328
328
  * another asset id.
329
329
  *
@@ -358,10 +358,9 @@ function memotaLocator(value) {
358
358
  };
359
359
  }
360
360
  /**
361
- * The project AudioScript is a fixed structure identity, like the Timeline: at
362
- * most one may exist, and a missing one is minted atomically with empty
363
- * segments. Project initialization and legacy migration share this single
364
- * path, so both produce the same canonical structure.
361
+ * Bootstrap an empty script when the foundation has no candidate. Without an
362
+ * explicit project attachment, multiple candidates are ambiguous. Existing
363
+ * content IDs are reused here; Loro editing advances their immutable versions.
365
364
  */
366
365
  function ensureDocumentAudioScript(entities, idFactory) {
367
366
  const scripts = entities.filter((row) => row.entityKind === "audio-script");
@@ -1377,7 +1376,7 @@ var EntityTimelineEditor = class {
1377
1376
  const selections = captionSelections(input.selections);
1378
1377
  const track = this.ensureRoleTrack(draft, input.timelineEntityId, "caption");
1379
1378
  const content = {
1380
- entityId: this.mintEntityId(draft),
1379
+ entityId: input.captionEntityId === void 0 ? this.mintEntityId(draft) : requireAvailableEntityId(draft, input.captionEntityId),
1381
1380
  entityKind: "caption",
1382
1381
  payload: {
1383
1382
  extent: {
package/dist/testing.js CHANGED
@@ -1,4 +1,4 @@
1
- import { _t as bytesToBase64, gt as base64ToBytes, s as createMirrorVideoDocumentAdapter, ut as VIDEO_DOCUMENT_SCHEMA_VERSION } from "./document-BxWxPFTj.js";
1
+ import { _t as bytesToBase64, gt as base64ToBytes, s as createMirrorVideoDocumentAdapter, ut as VIDEO_DOCUMENT_SCHEMA_VERSION } from "./document-CF-q_mRy.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.3.1-alpha.10",
3
+ "version": "1.3.1-alpha.12",
4
4
  "license": "UNLICENSED",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,9 +28,9 @@
28
28
  "immer": "^10.2.0",
29
29
  "loro-mirror": "^2.2.0",
30
30
  "zod": "^4.4.3",
31
- "@mengine/storage": "1.3.1-alpha.10",
32
- "@mengine/sync": "1.3.1-alpha.10",
33
- "@mengine/utils": "1.3.1-alpha.10"
31
+ "@mengine/storage": "1.3.1-alpha.12",
32
+ "@mengine/sync": "1.3.1-alpha.12",
33
+ "@mengine/utils": "1.3.1-alpha.12"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^25.9.1",
@@ -40,8 +40,8 @@
40
40
  "typescript": "^6.0.3",
41
41
  "vite-plugin-wasm": "^3.6.0",
42
42
  "vite-plus": "^0.1.23",
43
- "@mengine/idl-codegen": "0.0.0",
44
- "@mengine/medeo-dsl": "0.0.0"
43
+ "@mengine/medeo-dsl": "0.0.0",
44
+ "@mengine/idl-codegen": "0.0.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "loro-crdt": "^1.13.6"