@mengine/medeo-client 2.0.1-alpha.28 → 2.0.1-alpha.29

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,9 +1,9 @@
1
1
  import { u as volumeSchema } from "./shared-iWnU2osE.js";
2
2
  import { Mirror, schema, validateSchema } from "loro-mirror";
3
3
  import { z } from "zod";
4
- import { LoroDoc, LoroMap, LoroMovableList, LoroText, UndoManager } from "loro-crdt";
5
4
  import { sha256 } from "@noble/hashes/sha2.js";
6
- import { bytesToHex } from "@noble/hashes/utils.js";
5
+ import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js";
6
+ import { LoroDoc, LoroMap, LoroMovableList, LoroText, UndoManager } from "loro-crdt";
7
7
  import { produce } from "immer";
8
8
  //#region src/client/base64.ts
9
9
  function bytesToBase64(bytes) {
@@ -1508,6 +1508,7 @@ const ENTITY_KINDS = Object.freeze({
1508
1508
  ]),
1509
1509
  voice: Object.freeze(["Container", "IPBound"]),
1510
1510
  "sequence-marker": Object.freeze([]),
1511
+ script: Object.freeze(["SegmentText"]),
1511
1512
  "audio-script": Object.freeze(["SegmentText", "MediaFile"]),
1512
1513
  "phonetic-script": Object.freeze(["SegmentText", "MediaFile"]),
1513
1514
  caption: Object.freeze([
@@ -1809,6 +1810,32 @@ function entityToRow(entity) {
1809
1810
  };
1810
1811
  }
1811
1812
  //#endregion
1813
+ //#region ../medeo-dsl/src/script-content.ts
1814
+ /** Canonical text used to identify one immutable professional Script version. */
1815
+ function normalizeScriptText(text) {
1816
+ return text.normalize("NFC").replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n").map((line) => line.trimEnd()).join("\n");
1817
+ }
1818
+ /** Preserve segment identity, boundaries and order while removing textual encoding noise. */
1819
+ function normalizeScriptSegments(segments) {
1820
+ return segments.map(({ segmentId, text, language }) => ({
1821
+ segmentId,
1822
+ text: normalizeScriptText(text),
1823
+ ...language === void 0 ? {} : { language: language.normalize("NFC").trim() }
1824
+ }));
1825
+ }
1826
+ /** Content identity: ordered canonical text with array boundaries, never local ids or language metadata. */
1827
+ function scriptContentHash(segments) {
1828
+ return `sha256:${bytesToHex(sha256(utf8ToBytes(JSON.stringify(normalizeScriptSegments(segments).map((segment) => segment.text)))))}`;
1829
+ }
1830
+ //#endregion
1831
+ //#region ../medeo-dsl/src/script-revision.ts
1832
+ const HASH = /^sha256:[0-9a-f]{64}$/;
1833
+ function readScriptRevisionTrace(value) {
1834
+ if (!isJsonObject(value)) return null;
1835
+ if (Object.keys(value).length !== 4 || value.command !== "revise-script" || typeof value.editId !== "string" || value.editId.trim() !== value.editId || value.editId === "" || typeof value.sourceContentHash !== "string" || !HASH.test(value.sourceContentHash) || typeof value.resultContentHash !== "string" || !HASH.test(value.resultContentHash)) return null;
1836
+ return value;
1837
+ }
1838
+ //#endregion
1812
1839
  //#region ../medeo-dsl/src/invariants.ts
1813
1840
  /** An entity is made from at most one Asset; two would leave its bytes ambiguous. */
1814
1841
  /** Validates one complete set of entities and its authoritative Relation rows. */
@@ -1848,6 +1875,38 @@ function validateEntityRelationSet(entityRefs, index) {
1848
1875
  issues.push(...validateSequenceComposition(entity));
1849
1876
  }
1850
1877
  issues.push(...validateClipAnchorCycles(entities, index));
1878
+ issues.push(...validateScriptRevisionRelations(entities, index));
1879
+ return issues;
1880
+ }
1881
+ function validateScriptRevisionRelations(entities, index) {
1882
+ const byId = new Map(entities.map((entity) => [entity.entityId, entity.current()]));
1883
+ const seenRelations = /* @__PURE__ */ new Set();
1884
+ const editIds = /* @__PURE__ */ new Set();
1885
+ const parentCounts = /* @__PURE__ */ new Map();
1886
+ const issues = [];
1887
+ for (const entity of entities) for (const relation of index.relationsOf(entity)) {
1888
+ const row = relation.toRow();
1889
+ if (row == null || seenRelations.has(row.relationId)) continue;
1890
+ seenRelations.add(row.relationId);
1891
+ const result = byId.get(row.endpoint0EntityId);
1892
+ const source = byId.get(row.endpoint1EntityId);
1893
+ if (row.relationKind !== "derived-from" || result?.entityKind !== "script" || source?.entityKind !== "script") continue;
1894
+ const trace = readScriptRevisionTrace(row.trace);
1895
+ const invalid = trace == null || trace.resultContentHash !== result.contentHash || trace.sourceContentHash !== source.contentHash || trace.resultContentHash === trace.sourceContentHash || trace != null && editIds.has(trace.editId);
1896
+ if (trace != null) editIds.add(trace.editId);
1897
+ const parentCount = (parentCounts.get(row.endpoint0EntityId) ?? 0) + 1;
1898
+ parentCounts.set(row.endpoint0EntityId, parentCount);
1899
+ if (invalid) issues.push({
1900
+ code: "invalid_script_revision",
1901
+ entityId: row.endpoint0EntityId,
1902
+ message: "Script revision requires one canonical revise-script trace matching distinct result and source text"
1903
+ });
1904
+ if (parentCount > 1) issues.push({
1905
+ code: "invalid_script_revision",
1906
+ entityId: row.endpoint0EntityId,
1907
+ message: "A Script revision may name exactly one source Script"
1908
+ });
1909
+ }
1851
1910
  return issues;
1852
1911
  }
1853
1912
  function validateMarkerUse(marker, index) {
@@ -2068,7 +2127,7 @@ function validateKnownEntityPayload(entity) {
2068
2127
  const value = entity;
2069
2128
  const problems = [];
2070
2129
  if (value.lifecycle !== void 0 && !isRecord(value.lifecycle)) problems.push("lifecycle must be an object when present");
2071
- if (entity.entityKind === "audio-script" || entity.entityKind === "phonetic-script") {
2130
+ if (entity.entityKind === "script" || entity.entityKind === "audio-script" || entity.entityKind === "phonetic-script") {
2072
2131
  for (const key of [
2073
2132
  "durationMs",
2074
2133
  "extent",
@@ -2109,6 +2168,9 @@ function validateKnownEntityPayload(entity) {
2109
2168
  case "audio-script":
2110
2169
  validateScriptPayload(value, problems);
2111
2170
  break;
2171
+ case "script":
2172
+ validateProfessionalScriptPayload(value, problems);
2173
+ break;
2112
2174
  case "phonetic-script":
2113
2175
  validatePhoneticScriptPayload(value, problems);
2114
2176
  break;
@@ -2284,6 +2346,24 @@ function validateScriptPayload(value, problems) {
2284
2346
  if (segment.language !== void 0 && typeof segment.language !== "string") problems.push(`segments[${index}].language must be a string when present`);
2285
2347
  }
2286
2348
  }
2349
+ function validateProfessionalScriptPayload(value, problems) {
2350
+ validateScriptPayload(value, problems);
2351
+ if (!Array.isArray(value.segments) || !value.segments.every(isCompleteScriptSegment)) return;
2352
+ const segments = value.segments;
2353
+ if (segments.length === 0) problems.push("segments must contain at least one screenplay segment");
2354
+ const ids = segments.map((segment) => segment.segmentId);
2355
+ if (ids.some((id) => id.trim() === "")) problems.push("segmentId must be a non-empty trimmed string");
2356
+ if (ids.some((id) => id.trim() !== id)) problems.push("segmentId must be a non-empty trimmed string");
2357
+ if (new Set(ids).size !== ids.length) problems.push("segmentId must be unique within the Script");
2358
+ const normalized = normalizeScriptSegments(segments);
2359
+ if (normalized.some((segment) => segment.text === "")) problems.push("segment text must be non-empty after normalization");
2360
+ if (segments.some((segment, index) => segment.text !== normalized[index].text)) problems.push("segment text must use canonical NFC, LF line endings, and trimmed line endings");
2361
+ if (segments.some((segment, index) => segment.language !== normalized[index].language)) problems.push("segment language must be canonical NFC and trimmed when present");
2362
+ if (typeof value.contentHash !== "string" || value.contentHash !== scriptContentHash(segments)) problems.push("contentHash must equal the SHA-256 of the canonical ordered segment texts");
2363
+ }
2364
+ function isCompleteScriptSegment(value) {
2365
+ return isRecord(value) && typeof value.segmentId === "string" && typeof value.text === "string" && (value.language === void 0 || typeof value.language === "string");
2366
+ }
2287
2367
  function validateOptionalField(value, key, expectedType, problems) {
2288
2368
  if (value[key] !== void 0 && typeof value[key] !== expectedType) problems.push(`${key} must be a ${expectedType} when present`);
2289
2369
  }
@@ -2342,6 +2422,7 @@ function isOwnedLocalEntityValuePath(entityKind, path) {
2342
2422
  if (entityKind === "voice" && /^voice\.(?:system|key|name)$/.test(path)) return true;
2343
2423
  if (entityKind === "caption" && (path.startsWith("style.") || path === "selection.segmentId")) return true;
2344
2424
  if ([
2425
+ "script",
2345
2426
  "audio-script",
2346
2427
  "caption",
2347
2428
  "phonetic-script"
@@ -2351,6 +2432,7 @@ function isOwnedLocalEntityValuePath(entityKind, path) {
2351
2432
  function isOwnedLocalIdPath(entityKind, path) {
2352
2433
  if (path === "lifecycle.actorId") return true;
2353
2434
  if ([
2435
+ "script",
2354
2436
  "audio-script",
2355
2437
  "caption",
2356
2438
  "phonetic-script"
@@ -2517,7 +2599,7 @@ const derivedFromRelationSpec = Object.freeze({
2517
2599
  kind: "derived-from",
2518
2600
  ordered: true,
2519
2601
  sequenced: false,
2520
- validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(MEDIA_VARIANT_KINDS), new Set(MEDIA_VARIANT_KINDS)) || hasKinds(endpoints, new Set(["voiceover-recording"]), new Set(["phonetic-script"])) || hasKinds(endpoints, new Set(["audio-script"]), new Set(MEDIA_AUDIBLE_KINDS)) || hasKinds(endpoints, new Set(["audio-script"]), new Set(["audio-script"])),
2602
+ validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(MEDIA_VARIANT_KINDS), new Set(MEDIA_VARIANT_KINDS)) || hasKinds(endpoints, new Set(["voiceover-recording"]), new Set(["phonetic-script"])) || hasKinds(endpoints, new Set(["video"]), new Set(["script"])) || hasKinds(endpoints, new Set(["script"]), new Set(["script"])) || hasKinds(endpoints, new Set(["audio-script"]), new Set(MEDIA_AUDIBLE_KINDS)) || hasKinds(endpoints, new Set(["audio-script"]), new Set(["audio-script"])),
2521
2603
  validateMetadata: isEmptyMetadata
2522
2604
  });
2523
2605
  const captionAlignmentRelationSpec = Object.freeze({
@@ -2652,6 +2734,7 @@ var BiRelationIndex = class {
2652
2734
  }
2653
2735
  /** Author `derived-from(product, source)` without exposing positional arguments. */
2654
2736
  linkDerivedFrom(input) {
2737
+ if (input.product.current().entityKind === "script" && input.source.current().entityKind === "script") throw new Error("Script revision provenance is host-authored by reviseScript");
2655
2738
  return this.linkValidated({
2656
2739
  relationId: input.relationId,
2657
2740
  spec: derivedFromRelationSpec,
@@ -2900,10 +2983,27 @@ var InvalidEntityRelationTransitionError = class extends Error {
2900
2983
  function validateEntityRelationTransition(before, candidate) {
2901
2984
  const issues = [];
2902
2985
  const previous = new Map(before.relations.map((row) => [row.relationId, row]));
2986
+ const beforeEntities = new Map(before.entities.map((row) => [row.entityId, row]));
2987
+ const candidateEntities = new Map(candidate.entities.map((row) => [row.entityId, row]));
2988
+ const revisionEditIds = new Set(before.relations.filter((relation) => isScriptRevision(relation, beforeEntities)).map((relation) => readScriptRevisionTrace(relation.trace)?.editId).filter((editId) => editId !== void 0));
2989
+ const revisionParents = /* @__PURE__ */ new Map();
2990
+ for (const entity of before.entities) if (entity.entityKind === "script" && !candidateEntities.has(entity.entityId)) issues.push(`Script entity "${entity.entityId}" is immutable history and cannot be deleted or replaced`);
2903
2991
  for (const relation of candidate.relations) {
2992
+ if (isScriptRevision(relation, candidateEntities)) {
2993
+ const count = (revisionParents.get(relation.endpoint0EntityId) ?? 0) + 1;
2994
+ revisionParents.set(relation.endpoint0EntityId, count);
2995
+ if (count > 1) issues.push(`Script "${relation.endpoint0EntityId}" may name only one revision source`);
2996
+ }
2904
2997
  const original = previous.get(relation.relationId);
2905
2998
  if (original == null) {
2906
2999
  if (relation.relationKind === "caption-alignment") issues.push(`new caption-alignment relation "${relation.relationId}" is forbidden`);
3000
+ if (isScriptRevision(relation, candidateEntities)) {
3001
+ const trace = readScriptRevisionTrace(relation.trace);
3002
+ const result = candidateEntities.get(relation.endpoint0EntityId);
3003
+ const source = candidateEntities.get(relation.endpoint1EntityId);
3004
+ if (trace == null || beforeEntities.has(relation.endpoint0EntityId) || !beforeEntities.has(relation.endpoint1EntityId) || trace.resultContentHash !== result.payload.contentHash || trace.sourceContentHash !== source.payload.contentHash || trace.resultContentHash === trace.sourceContentHash || revisionEditIds.has(trace.editId)) issues.push(`Script revision relation "${relation.relationId}" must be one canonical new-result to existing-source edit`);
3005
+ else revisionEditIds.add(trace.editId);
3006
+ }
2907
3007
  continue;
2908
3008
  }
2909
3009
  if (original.relationKind !== relation.relationKind || original.endpoint0EntityId !== relation.endpoint0EntityId || original.endpoint1EntityId !== relation.endpoint1EntityId) {
@@ -2912,9 +3012,13 @@ function validateEntityRelationTransition(before, candidate) {
2912
3012
  }
2913
3013
  if (!sameJson(original.metadata, relation.metadata)) issues.push(`relation "${relation.relationId}" changed semantic metadata without a new identity`);
2914
3014
  if (relation.relationKind === "caption-alignment" && !sameJson(original.trace, relation.trace)) issues.push(`legacy caption-alignment relation "${relation.relationId}" may only be carried unchanged or deleted`);
3015
+ if (isScriptRevision(relation, candidateEntities) && !sameJson(original.trace, relation.trace)) issues.push(`Script revision relation "${relation.relationId}" trace is immutable`);
2915
3016
  }
2916
3017
  if (issues.length > 0) throw new InvalidEntityRelationTransitionError(issues);
2917
3018
  }
3019
+ function isScriptRevision(relation, entities) {
3020
+ return relation.relationKind === "derived-from" && entities.get(relation.endpoint0EntityId)?.entityKind === "script" && entities.get(relation.endpoint1EntityId)?.entityKind === "script";
3021
+ }
2918
3022
  function sameJson(left, right) {
2919
3023
  if (left === right) return true;
2920
3024
  if (Array.isArray(left) && Array.isArray(right)) return left.length === right.length && left.every((value, index) => sameJson(value, right[index]));
@@ -3204,6 +3308,7 @@ function projectEntityTimeline(rows) {
3204
3308
  media_duration_ms: placement.duration,
3205
3309
  audio_storage_key: storageKey(asset),
3206
3310
  origin_speech_id: external.key,
3311
+ ...synthesized ? {} : { origin_asset_system: "memota" },
3207
3312
  audio_script: assembled?.text ?? (transcript === void 0 ? "" : assembleScriptText(transcript)),
3208
3313
  volume: volume.data,
3209
3314
  voice: object(voice) ? {
@@ -4005,11 +4110,13 @@ var LoroEntityDocument = class LoroEntityDocument {
4005
4110
  }
4006
4111
  /** The callback and every domain validation execute on a causally complete fork first. */
4007
4112
  transact(edit) {
4113
+ const before = readEntityDocument(this.doc, true);
4008
4114
  const probe = this.doc.fork();
4009
4115
  const draft = new LoroEntityDraft(probe);
4010
4116
  edit(draft);
4011
4117
  draft.captureSelectionBaselines();
4012
4118
  const state = readEntityDocument(probe);
4119
+ validateEntityRelationTransition(before.rows, state.rows);
4013
4120
  assertState(state);
4014
4121
  this.validate?.(state);
4015
4122
  materialize(probe);
@@ -4023,8 +4130,15 @@ var LoroEntityDocument = class LoroEntityDocument {
4023
4130
  from
4024
4131
  });
4025
4132
  }
4026
- /** Import preserves native operation identities; projection errors never discard remote operations. */
4133
+ /** Validate remote operations on a fork before importing their native identities. */
4027
4134
  import(update) {
4135
+ const before = readEntityDocument(this.doc, true);
4136
+ const probe = this.doc.fork();
4137
+ probe.import(update);
4138
+ const candidate = readEntityDocument(probe, true);
4139
+ validateEntityRelationTransition(before.rows, candidate.rows);
4140
+ assertState(candidate);
4141
+ this.validate?.(candidate);
4028
4142
  this.doc.import(update);
4029
4143
  const state = this.snapshot();
4030
4144
  assertState(state);
@@ -4140,6 +4254,7 @@ var LoroEntityDraft = class {
4140
4254
  });
4141
4255
  }
4142
4256
  delete(entityId) {
4257
+ if (workspace(this.doc, family(this.doc, entityId)).get("kind") === "script") throw new Error("Script entities are immutable history and cannot be deleted");
4143
4258
  this.record({
4144
4259
  type: "delete",
4145
4260
  root: family(this.doc, entityId)
@@ -4241,6 +4356,7 @@ var LoroEntityDraft = class {
4241
4356
  });
4242
4357
  }
4243
4358
  replaceOwned(entityId, payload) {
4359
+ this.assertGenericMutationAllowed(entityId);
4244
4360
  this.record({
4245
4361
  type: "write",
4246
4362
  root: family(this.doc, entityId),
@@ -4251,6 +4367,7 @@ var LoroEntityDraft = class {
4251
4367
  editText(entityId, path, start, deleteCount, text) {
4252
4368
  if (typeof path[0] !== "string") throw new Error("Text path must start with a field");
4253
4369
  const owner = resolveEntityFieldOwner(this.snapshot().rows, createEntityId(currentId(this.doc, entityId)), path[0]);
4370
+ this.assertGenericMutationAllowed(owner);
4254
4371
  this.record({
4255
4372
  type: "text",
4256
4373
  root: family(this.doc, owner),
@@ -4276,6 +4393,7 @@ var LoroEntityDraft = class {
4276
4393
  const owner = resolveEntityFieldOwner(state.rows, id, change.path[0]);
4277
4394
  const row = state.rows.entities.find((row) => row.entityId === owner);
4278
4395
  if (equalJson(row.payload, applyFieldChanges(row.payload, [change]))) continue;
4396
+ this.assertGenericMutationAllowed(owner);
4279
4397
  this.record({
4280
4398
  type: "field",
4281
4399
  root: family(this.doc, owner),
@@ -4285,6 +4403,9 @@ var LoroEntityDraft = class {
4285
4403
  });
4286
4404
  }
4287
4405
  }
4406
+ assertGenericMutationAllowed(entityId) {
4407
+ if (workspace(this.doc, family(this.doc, entityId)).get("kind") === "script") throw new Error("Script content is immutable; create a new Script revision");
4408
+ }
4288
4409
  changeRelation(relationId, changes) {
4289
4410
  assertFieldChanges(changes);
4290
4411
  const key = this.relationRoot(relationId);
@@ -736,7 +736,8 @@ type VideoClipPart = Omit<VideoClipPart$1, 'duration_ms'>;
736
736
  * from it by the projection.
737
737
  */
738
738
  type SpeechPart = Omit<SpeechPart$1, 'duration_ms'> & {
739
- media_duration_ms?: number | undefined;
739
+ media_duration_ms?: number | undefined; /** Compatibility discriminator for already-recorded Audio projected as speech. */
740
+ origin_asset_system?: 'memota' | undefined;
740
741
  };
741
742
  /**
742
743
  * Authoritative caption part. The IDL `CaptionPart` carries `duration_ms` (the
@@ -1286,7 +1287,7 @@ declare function buildInitialVideoDocument(): VideoDocument;
1286
1287
  //#endregion
1287
1288
  //#region ../medeo-dsl/src/sandbox.d.ts
1288
1289
  /** Kinds a script can author. */
1289
- type EntityKind = 'timeline' | 'track' | 'clip' | 'sequence-marker' | 'video' | 'audio' | 'voiceover-recording' | 'image' | 'voice' | 'audio-script' | 'phonetic-script' | 'caption';
1290
+ type EntityKind = 'timeline' | 'track' | 'clip' | 'sequence-marker' | 'video' | 'audio' | 'voiceover-recording' | 'image' | 'voice' | 'script' | 'audio-script' | 'phonetic-script' | 'caption';
1290
1291
  type RelationKind = 'contain' | 'marker' | 'clip-anchor' | 'voice-timbre' | 'caption-alignment' | 'derived-from';
1291
1292
  //#endregion
1292
1293
  //#region ../medeo-dsl/src/ids.d.ts
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as index_d_exports } from "./index-CMa6ZCtX.js";
2
- import { $ as derivePositionFromAbs, A as assertValidVideoDocument, At as VideoDraftContent, B as EntityRow, Bt as Track$1, C as isImplementedSemanticOpKind, Ct as TrackItem, D as PartIdFactory, Dt as VideoDocumentValidationIssue, E as ValidationError, Et as VideoDocument, F as ResolvedEntityTrack, Ft as CaptionPart$1, G as VoiceDescriptor, H as CaptionSegmentSelection, I as resolveEntityTimelineLayout, It as CaptionStyle, J as EntityId, K as JsonObject, L as projectEntityTimeline, Lt as PartAggregation, M as EntityTimelineTrackRole, Mt as effectiveVideoClipDurationMs, N as ResolvedEntityClipPlacement, Nt as speedOf, O as generatePartId, Ot as VideoDocumentValidationIssueCode, P as ResolvedEntityTimelineLayout, Pt as Attachment, Q as buildSpeechHostMap, R as EntityTimelineProjectionError, Rt as SpeedShift, S as SemanticOpKind, St as Track, T as SnapshotReadable, Tt as VideoClipPart, U as CaptionStyleFields, V as RelationRow, Vt as TrackItem$1, W as ScriptTextSegment, X as DerivedItemPosition, Y as buildInitialVideoDocument, Z as SpeechHostMap, _ as SemanticOpName, _t as DEFAULT_UNIT_TIME_MS, a as PlainMemoryAdapter, at as ensureLaneTrack, b as ImplementedSemanticOpKind, bt as SpeechPart, c as MirrorVideoDocumentAdapter, ct as solveVideoDocument, d as createMirrorVideoDocumentAdapter, dt as VideoDocumentDraft, et as fromVideoDocument, f as CommitOptions, ft as VideoDocumentMirrorSchema, g as SemanticOpInput, gt as CaptionPart, h as SemanticEditor, ht as CaptionDisplayCue, i as JournalEntry, it as SolvedVideoDocument, j as validateVideoDocument, jt as VideoDraftPartUnion, k as VideoDocumentValidationError, kt as VideoDraft, l as MirrorVideoDocumentOptions, lt as TrackDraft, m as SemanticDocumentAdapter, mt as BgmPart, n as videoDocumentSchema, nt as LANE_KINDS_IN_STACK_ORDER, o as PlainMemoryAdapterOptions, ot as findLaneTrack, p as OpActor, pt as videoDocumentMirrorSchema, q as JsonValue, r as readVideoDocumentFromDraft, rt as LaneKind, s as createPlainMemoryAdapter, st as laneTrackId, t as partUnionSchema, tt as toVideoDocument, u as createMirrorVideoDocument, ut as TrackItemDraft, v as TransactAudit, vt as PartKind, w as SchemaValidator, wt as TrackItemTimePosition, x as PlannedSemanticOpKind, xt as Timeline, y as IMPLEMENTED_SEMANTIC_OP_KINDS, yt as PartUnion, z as EntityRelationRows, zt as Timeline$1 } from "./index-BYv9FPSS.js";
2
+ import { $ as derivePositionFromAbs, A as assertValidVideoDocument, At as VideoDraftContent, B as EntityRow, Bt as Track$1, C as isImplementedSemanticOpKind, Ct as TrackItem, D as PartIdFactory, Dt as VideoDocumentValidationIssue, E as ValidationError, Et as VideoDocument, F as ResolvedEntityTrack, Ft as CaptionPart$1, G as VoiceDescriptor, H as CaptionSegmentSelection, I as resolveEntityTimelineLayout, It as CaptionStyle, J as EntityId, K as JsonObject, L as projectEntityTimeline, Lt as PartAggregation, M as EntityTimelineTrackRole, Mt as effectiveVideoClipDurationMs, N as ResolvedEntityClipPlacement, Nt as speedOf, O as generatePartId, Ot as VideoDocumentValidationIssueCode, P as ResolvedEntityTimelineLayout, Pt as Attachment, Q as buildSpeechHostMap, R as EntityTimelineProjectionError, Rt as SpeedShift, S as SemanticOpKind, St as Track, T as SnapshotReadable, Tt as VideoClipPart, U as CaptionStyleFields, V as RelationRow, Vt as TrackItem$1, W as ScriptTextSegment, X as DerivedItemPosition, Y as buildInitialVideoDocument, Z as SpeechHostMap, _ as SemanticOpName, _t as DEFAULT_UNIT_TIME_MS, a as PlainMemoryAdapter, at as ensureLaneTrack, b as ImplementedSemanticOpKind, bt as SpeechPart, c as MirrorVideoDocumentAdapter, ct as solveVideoDocument, d as createMirrorVideoDocumentAdapter, dt as VideoDocumentDraft, et as fromVideoDocument, f as CommitOptions, ft as VideoDocumentMirrorSchema, g as SemanticOpInput, gt as CaptionPart, h as SemanticEditor, ht as CaptionDisplayCue, i as JournalEntry, it as SolvedVideoDocument, j as validateVideoDocument, jt as VideoDraftPartUnion, k as VideoDocumentValidationError, kt as VideoDraft, l as MirrorVideoDocumentOptions, lt as TrackDraft, m as SemanticDocumentAdapter, mt as BgmPart, n as videoDocumentSchema, nt as LANE_KINDS_IN_STACK_ORDER, o as PlainMemoryAdapterOptions, ot as findLaneTrack, p as OpActor, pt as videoDocumentMirrorSchema, q as JsonValue, r as readVideoDocumentFromDraft, rt as LaneKind, s as createPlainMemoryAdapter, st as laneTrackId, t as partUnionSchema, tt as toVideoDocument, u as createMirrorVideoDocument, ut as TrackItemDraft, v as TransactAudit, vt as PartKind, w as SchemaValidator, wt as TrackItemTimePosition, x as PlannedSemanticOpKind, xt as Timeline, y as IMPLEMENTED_SEMANTIC_OP_KINDS, yt as PartUnion, z as EntityRelationRows, zt as Timeline$1 } from "./index-DaabF2vA.js";
3
3
  import { LoroDoc, PeerID } from "loro-crdt";
4
4
  import { DocState } from "@mengine/sync";
5
5
  import { BaseDocStorage, Connection, DocDiff, DocPushReceipt, DocSnapshotRecord, DocStorage, DocStorageOptions, DocUpdate, DocUpdateRecord } from "@mengine/storage";
@@ -880,7 +880,7 @@ declare class LoroEntityDocument {
880
880
  getVersion(id: string): EntityRow | undefined;
881
881
  /** The callback and every domain validation execute on a causally complete fork first. */
882
882
  transact(edit: (draft: LoroEntityDraft) => void): Uint8Array;
883
- /** Import preserves native operation identities; projection errors never discard remote operations. */
883
+ /** Validate remote operations on a fork before importing their native identities. */
884
884
  import(update: Uint8Array): void;
885
885
  /**
886
886
  * Bind a saved snapshot without rewriting text operations or resolving
@@ -924,6 +924,7 @@ declare class LoroEntityDraft {
924
924
  editText(entityId: string, path: readonly (string | number)[], start: number, deleteCount: number, text: string): void;
925
925
  /** Generic assembled-field edit compiled at the author's causal position. */
926
926
  change(entityId: string, changes: readonly FieldChange[]): void;
927
+ private assertGenericMutationAllowed;
927
928
  changeRelation(relationId: string, changes: readonly FieldChange[]): void;
928
929
  captureSelectionBaselines(): void;
929
930
  private record;
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { $ as fromVideoDocument, A as resolveEntityTimelineLayout, At as base64ToBytes, B as assembleScriptText, C as canonicalJson, Ct as safeDurationMs, D as resolveFieldPath, Dt as partUnionToDraft, E as assertFieldChanges, Et as speedOf, F as relationsTo, G as isJsonObject, H as selectAudioScriptSegment, I as ScriptCompositionError, J as hasSequence, K as createEntityId, L as assembleCaptionContent, M as validateEntityRelationTransition, N as decodeEntityRelationRows, O as projectEntityTimeline, Ot as recordEntries, P as relationsFrom, Q as derivePositionFromAbs, R as assembleEntityContent, S as scriptSourceAssetOf, St as partDurationMs, T as applyFieldChanges, Tt as effectiveVideoClipDurationMs, U as updateEntityFields, V as findComposedAudioScript, W as variantBaseEntityIds, X as buildInitialVideoDocument, Y as isMediaAssetVariantKind, Z as buildSpeechHostMap, _ as audioScriptAssetHash, _t as resolveAllSpeechOverlaps, a as createMirrorVideoDocument, at as videoDocumentSchema, b as phoneticScriptAssetHash, bt as TIMELINE_SKELETON_DURATION_MS, c as DocumentMutationGuard, ct as findLaneTrack, d as LoroEntityDraft, dt as solveVideoDocument, et as toVideoDocument, f as readEntityDocument, ft as cascadeAfterVideoClipChanges, g as audioScriptAssetFields, gt as recalculateTimelineDuration, h as audioScriptAssetContent, ht as reassignSpeechesToVideoClipsByTime, i as MirrorVideoDocumentAdapter, it as partUnionSchema, j as EntityTimelineProjectionError, jt as bytesToBase64, k as EntityProjectionGraph, kt as videoDocumentMirrorSchema, l as LORO_ENTITY_SCHEMA, lt as hasResolvedPlacement, m as entityOrderGroups, mt as fillMainTrackTimeGaps, n as createPlainMemoryAdapter, nt as assertValidVideoDocument, o as createMirrorVideoDocumentAdapter, ot as LANE_KINDS_IN_STACK_ORDER, p as readEntityRelationRecords, pt as arrangeMainTrackSeamlessly, q as createRelationId, r as generatePartId, rt as validateVideoDocument, s as readVideoDocumentFromDraft, st as ensureLaneTrack, t as PlainMemoryAdapter, tt as VideoDocumentValidationError, u as LoroEntityDocument, ut as laneTrackId, v as audioScriptAssetOf, vt as resolveSpeechOverlapByShiftingVideos, w as equalJson, wt as DEFAULT_UNIT_TIME_MS, x as phoneticScriptAssetOf, xt as isEmptyVideoClip, y as phoneticScriptAssetContent, yt as syncAggregatedClipsTimePosition, z as assemblePhoneticScriptContent } from "./document-DcpvSvQK.js";
1
+ import { $ as fromVideoDocument, A as resolveEntityTimelineLayout, At as base64ToBytes, B as assembleScriptText, C as canonicalJson, Ct as safeDurationMs, D as resolveFieldPath, Dt as partUnionToDraft, E as assertFieldChanges, Et as speedOf, F as relationsTo, G as isJsonObject, H as selectAudioScriptSegment, I as ScriptCompositionError, J as hasSequence, K as createEntityId, L as assembleCaptionContent, M as validateEntityRelationTransition, N as decodeEntityRelationRows, O as projectEntityTimeline, Ot as recordEntries, P as relationsFrom, Q as derivePositionFromAbs, R as assembleEntityContent, S as scriptSourceAssetOf, St as partDurationMs, T as applyFieldChanges, Tt as effectiveVideoClipDurationMs, U as updateEntityFields, V as findComposedAudioScript, W as variantBaseEntityIds, X as buildInitialVideoDocument, Y as isMediaAssetVariantKind, Z as buildSpeechHostMap, _ as audioScriptAssetHash, _t as resolveAllSpeechOverlaps, a as createMirrorVideoDocument, at as videoDocumentSchema, b as phoneticScriptAssetHash, bt as TIMELINE_SKELETON_DURATION_MS, c as DocumentMutationGuard, ct as findLaneTrack, d as LoroEntityDraft, dt as solveVideoDocument, et as toVideoDocument, f as readEntityDocument, ft as cascadeAfterVideoClipChanges, g as audioScriptAssetFields, gt as recalculateTimelineDuration, h as audioScriptAssetContent, ht as reassignSpeechesToVideoClipsByTime, i as MirrorVideoDocumentAdapter, it as partUnionSchema, j as EntityTimelineProjectionError, jt as bytesToBase64, k as EntityProjectionGraph, kt as videoDocumentMirrorSchema, l as LORO_ENTITY_SCHEMA, lt as hasResolvedPlacement, m as entityOrderGroups, mt as fillMainTrackTimeGaps, n as createPlainMemoryAdapter, nt as assertValidVideoDocument, o as createMirrorVideoDocumentAdapter, ot as LANE_KINDS_IN_STACK_ORDER, p as readEntityRelationRecords, pt as arrangeMainTrackSeamlessly, q as createRelationId, r as generatePartId, rt as validateVideoDocument, s as readVideoDocumentFromDraft, st as ensureLaneTrack, t as PlainMemoryAdapter, tt as VideoDocumentValidationError, u as LoroEntityDocument, ut as laneTrackId, v as audioScriptAssetOf, vt as resolveSpeechOverlapByShiftingVideos, w as equalJson, wt as DEFAULT_UNIT_TIME_MS, x as phoneticScriptAssetOf, xt as isEmptyVideoClip, y as phoneticScriptAssetContent, yt as syncAggregatedClipsTimePosition, z as assemblePhoneticScriptContent } from "./document-B3RLmTzA.js";
2
2
  import { addSpeechesInputSchema, addVideoClipsInputSchema, adjustBgmVolumeInputSchema, adjustSpeechVolumeInputSchema, adjustVideoClipDurationInputSchema, adjustVideoClipVolumeInputSchema, changeSpeechScriptInputSchema, changeSpeechVoiceInputSchema, deleteBgmInputSchema, deleteSpeechesInputSchema, deleteVideoClipsInputSchema, moveSpeechesInputSchema, moveVideoClipsByAnchorInputSchema, moveVideoClipsInputSchema, replaceVideoClipContentInputSchema, replaceVideoClipSequenceInputSchema, setBgmInputSchema, setCaptionStyleInputSchema, setCaptionVisibilityInputSchema, setVideoClipSpeedShiftInputSchema, t as schemas_exports } from "./schemas.js";
3
- import { LoroDoc, LoroMap, UndoManager, VersionVector } from "loro-crdt";
4
3
  import { sha256 } from "@noble/hashes/sha2.js";
5
4
  import { bytesToHex } from "@noble/hashes/utils.js";
5
+ import { LoroDoc, LoroMap, UndoManager, VersionVector } from "loro-crdt";
6
6
  import { ClientServerSynchronizer, DocManager } from "@mengine/sync";
7
7
  import { DisposableSet, EventBus, Task } from "@mengine/utils";
8
8
  import { BaseDocStorage, DummyConnection } from "@mengine/storage";
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { c as MirrorVideoDocumentAdapter, kt as VideoDraft } from "./index-BYv9FPSS.js";
1
+ import { c as MirrorVideoDocumentAdapter, kt as VideoDraft } from "./index-DaabF2vA.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 { At as base64ToBytes, jt as bytesToBase64, o as createMirrorVideoDocumentAdapter } from "./document-DcpvSvQK.js";
1
+ import { At as base64ToBytes, jt as bytesToBase64, o as createMirrorVideoDocumentAdapter } from "./document-B3RLmTzA.js";
2
2
  import { t as classifyUpdate } from "./loro-relay-doc-cJSY-uau.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": "2.0.1-alpha.28",
3
+ "version": "2.0.1-alpha.29",
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.3.3",
30
30
  "zod": "^4.4.3",
31
- "@mengine/sync": "2.0.1-alpha.28",
32
- "@mengine/utils": "2.0.1-alpha.28",
33
- "@mengine/storage": "2.0.1-alpha.28"
31
+ "@mengine/storage": "2.0.1-alpha.29",
32
+ "@mengine/utils": "2.0.1-alpha.29",
33
+ "@mengine/sync": "2.0.1-alpha.29"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^25.9.1",