@mengine/medeo-client 1.0.1-alpha.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{document-DgffKwRw.js → document-C98vSu7J.js} +186 -149
- package/dist/{index-DRUGsbm2.d.ts → index-BUKA3L7o.d.ts} +766 -112
- package/dist/index.d.ts +543 -76
- package/dist/index.js +1310 -145
- package/dist/loro-relay-doc-ssdYpuef.js +58 -0
- package/dist/relay.d.ts +37 -0
- package/dist/relay.js +2 -0
- package/dist/testing.d.ts +16 -1
- package/dist/testing.js +58 -6
- package/package.json +5 -7
- package/dist/index-6e5cbdM3.d.ts +0 -475
- package/dist/schemas.d.ts +0 -2
- package/dist/schemas.js +0 -378
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Mirror, schema } from "loro-mirror";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { LoroDoc } from "loro-crdt";
|
|
4
|
-
import { produce } from "immer";
|
|
5
4
|
//#region src/client/base64.ts
|
|
6
5
|
function bytesToBase64(bytes) {
|
|
7
6
|
let binary = "";
|
|
@@ -536,7 +535,12 @@ function cascadeAfterVideoClipChanges(doc, makeEmptyPart) {
|
|
|
536
535
|
function solveVideoDocument(document) {
|
|
537
536
|
const doc = videoDocumentToTimelineDoc(document);
|
|
538
537
|
let counter = 0;
|
|
539
|
-
|
|
538
|
+
const derivedFillerPartIds = /* @__PURE__ */ new Set();
|
|
539
|
+
cascadeAfterVideoClipChanges(doc, () => {
|
|
540
|
+
const partId = `empty_${counter++}`;
|
|
541
|
+
derivedFillerPartIds.add(partId);
|
|
542
|
+
return { partId };
|
|
543
|
+
});
|
|
540
544
|
const absByPartId = /* @__PURE__ */ new Map();
|
|
541
545
|
for (const item of doc.main_track) absByPartId.set(item.part_id, item.abs_time_position);
|
|
542
546
|
for (const item of doc.speech_track) absByPartId.set(item.part_id, item.abs_time_position);
|
|
@@ -552,7 +556,8 @@ function solveVideoDocument(document) {
|
|
|
552
556
|
}))
|
|
553
557
|
})),
|
|
554
558
|
durationMs: doc.timeline.duration_ms,
|
|
555
|
-
partLibrary: doc.part_library
|
|
559
|
+
partLibrary: doc.part_library,
|
|
560
|
+
derivedFillerPartIds
|
|
556
561
|
};
|
|
557
562
|
}
|
|
558
563
|
function videoDocumentToTimelineDoc(document) {
|
|
@@ -643,6 +648,42 @@ function seedAggregations(mainItems, speechItems) {
|
|
|
643
648
|
return order.map((host) => byHost.get(host));
|
|
644
649
|
}
|
|
645
650
|
/**
|
|
651
|
+
* The four lanes in top-to-bottom stack order — the order a `tracks` list holds
|
|
652
|
+
* them in (see {@link laneRank}).
|
|
653
|
+
*
|
|
654
|
+
* Exported so a document can be seeded with all four lanes up front. That seed is
|
|
655
|
+
* not cosmetic: {@link ensureLaneTrack} is find-then-mint over a `LoroMovableList`,
|
|
656
|
+
* so two concurrent writers that each mint the same absent lane both keep their
|
|
657
|
+
* row, and the merged document holds two tracks for one lane. For the main lane
|
|
658
|
+
* that is fatal — `videoDocumentSchema` allows at most one `video_clip` track, so
|
|
659
|
+
* the merged document stops being projectable at all, symmetrically on both
|
|
660
|
+
* replicas. Pre-seeding every lane makes `ensureLaneTrack` always take its find
|
|
661
|
+
* branch, which removes the race by construction rather than by detection.
|
|
662
|
+
*
|
|
663
|
+
* What closes the race is that a track with the lane's `parts_kind` EXISTS — the
|
|
664
|
+
* lookup is by kind, not by id. So a seed is only safe if it covers every lane:
|
|
665
|
+
* a partial seed leaves the uncovered lanes exactly as exposed as before.
|
|
666
|
+
*/
|
|
667
|
+
const LANE_KINDS_IN_STACK_ORDER = [
|
|
668
|
+
"caption",
|
|
669
|
+
"video_clip",
|
|
670
|
+
"speech",
|
|
671
|
+
"bgm"
|
|
672
|
+
];
|
|
673
|
+
/**
|
|
674
|
+
* The conventional track id for a lane (`main_track`, `<kind>_track`). Shared by
|
|
675
|
+
* the up-front seed and {@link ensureLaneTrack}'s lazy mint so a document's lane
|
|
676
|
+
* ids do not depend on which of the two created the track.
|
|
677
|
+
*
|
|
678
|
+
* Ids are cosmetic to the merge itself — lane lookup goes by `parts_kind`, so
|
|
679
|
+
* drifting them apart would not reopen the concurrent-mint race. They matter to
|
|
680
|
+
* readers that address a lane by id (the FE editor's panes, fixtures), which is
|
|
681
|
+
* why there is one convention rather than two.
|
|
682
|
+
*/
|
|
683
|
+
function laneTrackId(kind) {
|
|
684
|
+
return kind === "video_clip" ? "main_track" : `${kind}_track`;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
646
687
|
* Lane-stacking rank for the single `tracks` list (reference/17 §4): caption
|
|
647
688
|
* (above) sits before the video_clip main track, which sits before speech / bgm
|
|
648
689
|
* (below). The `tracks` array is kept in this top-to-bottom order so a freshly
|
|
@@ -673,14 +714,18 @@ function insertTrackByLaneOrder(tracks, track) {
|
|
|
673
714
|
* Locate a lane's track row in the single `tracks` list by kind, minting an empty
|
|
674
715
|
* row in lane-stacking order if absent (reference/17 §4: lane = `parts_kind`).
|
|
675
716
|
* Ops use this to write authoritative items onto the right lane. The track id
|
|
676
|
-
*
|
|
717
|
+
* comes from {@link laneTrackId}, shared with the up-front seed.
|
|
718
|
+
*
|
|
719
|
+
* The mint branch is a concurrency hazard, not a convenience: see
|
|
720
|
+
* {@link LANE_KINDS_IN_STACK_ORDER}. A document seeded with all four lanes never
|
|
721
|
+
* reaches it.
|
|
677
722
|
*/
|
|
678
723
|
function ensureLaneTrack(draft, kind) {
|
|
679
724
|
draft.tracks ??= [];
|
|
680
725
|
let track = draft.tracks.find((t) => t?.parts_kind === kind);
|
|
681
726
|
if (track == null) {
|
|
682
727
|
track = {
|
|
683
|
-
id: kind
|
|
728
|
+
id: laneTrackId(kind),
|
|
684
729
|
parts_kind: kind,
|
|
685
730
|
is_hidden: void 0,
|
|
686
731
|
items: []
|
|
@@ -876,11 +921,59 @@ function validateVideoDocument(document) {
|
|
|
876
921
|
validatePartValues(partId, part, issues);
|
|
877
922
|
}
|
|
878
923
|
for (const [idx, track] of (doc.tracks ?? []).entries()) validateTrack(track, `tracks/${idx}`, partLibrary, issues, track.parts_kind === "video_clip");
|
|
924
|
+
validateLaneTrackUniqueness(doc, issues);
|
|
879
925
|
validateSpeechCaptionReferences(partLibrary, issues);
|
|
880
926
|
validatePositionReferences(doc, partLibrary, issues);
|
|
881
927
|
return issues;
|
|
882
928
|
}
|
|
883
929
|
/**
|
|
930
|
+
* Report a duplicated lane: two tracks sharing the same `id`, or two secondary
|
|
931
|
+
* tracks of the same `parts_kind`.
|
|
932
|
+
*
|
|
933
|
+
* Found by the Phase 6 M0/S2 concurrency probe. `ensureLaneTrack` locates a lane
|
|
934
|
+
* by `parts_kind` and mints `<kind>_track` when absent, so two replicas that both
|
|
935
|
+
* start without (say) a caption track each create one; `tracks` is a
|
|
936
|
+
* `LoroMovableList`, so the merge keeps BOTH. The lane is then split across two
|
|
937
|
+
* same-id tracks, and the projection's `find`-by-kind returns only one of them —
|
|
938
|
+
* so an item can be authoritative yet invisible in the pane a reader looks at.
|
|
939
|
+
*
|
|
940
|
+
* Flagged **recoverable**, not `error`, for two reasons. It is a reachable
|
|
941
|
+
* outcome of ordinary concurrent editing, and hard-rejecting would make an
|
|
942
|
+
* unavoidable merge result un-projectable — the same argument §11.1 makes for
|
|
943
|
+
* orphans. And the projection already tolerates it: `solveVideoDocument`
|
|
944
|
+
* concatenates every track of a kind, so the cascade sees all items regardless.
|
|
945
|
+
* The defect is a *reader-visible* split, so the right response is to surface it
|
|
946
|
+
* (loudly, in a channel someone reads), not to block the document.
|
|
947
|
+
*
|
|
948
|
+
* The main track is exempt from the kind check: `parts_kind === 'video_clip'` may
|
|
949
|
+
* legitimately appear on several tracks (the projection treats only the first as
|
|
950
|
+
* the main pane), so only its `id` collision is reported.
|
|
951
|
+
*/
|
|
952
|
+
function validateLaneTrackUniqueness(doc, issues) {
|
|
953
|
+
const tracks = doc.tracks ?? [];
|
|
954
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
955
|
+
const seenSecondaryKinds = /* @__PURE__ */ new Set();
|
|
956
|
+
for (const [idx, track] of tracks.entries()) {
|
|
957
|
+
const id = track?.id;
|
|
958
|
+
if (id != null && id !== "") if (seenIds.has(id)) issues.push({
|
|
959
|
+
code: "duplicate_lane_track",
|
|
960
|
+
path: `/tracks/${idx}/id`,
|
|
961
|
+
message: `Track "tracks/${idx}" reuses track id "${id}"`,
|
|
962
|
+
severity: "recoverable"
|
|
963
|
+
});
|
|
964
|
+
else seenIds.add(id);
|
|
965
|
+
const kind = track?.parts_kind;
|
|
966
|
+
if (kind == null || kind === "video_clip") continue;
|
|
967
|
+
if (seenSecondaryKinds.has(kind)) issues.push({
|
|
968
|
+
code: "duplicate_lane_track",
|
|
969
|
+
path: `/tracks/${idx}/parts_kind`,
|
|
970
|
+
message: `Lane "${kind}" is split across more than one track (at "tracks/${idx}")`,
|
|
971
|
+
severity: "recoverable"
|
|
972
|
+
});
|
|
973
|
+
else seenSecondaryKinds.add(kind);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
884
977
|
* An `anchored` item whose `anchorPartId` no longer exists in the library is the
|
|
885
978
|
* orphan condition (RFC 02 §4/§11.1) — e.g. a speech whose host video, or a
|
|
886
979
|
* caption whose host speech, was concurrently deleted while this item was being
|
|
@@ -1102,7 +1195,7 @@ function toAuthoritativePartLibrary(partLibrary) {
|
|
|
1102
1195
|
const { duration_ms, rest } = splitDurationMs(part.caption);
|
|
1103
1196
|
out[partId] = { caption: {
|
|
1104
1197
|
...rest,
|
|
1105
|
-
initial_duration_ms: duration_ms
|
|
1198
|
+
initial_duration_ms: rest.initial_duration_ms ?? duration_ms
|
|
1106
1199
|
} };
|
|
1107
1200
|
} else if (part.bgm != null) out[partId] = { bgm: omitDurationMs(part.bgm) };
|
|
1108
1201
|
}
|
|
@@ -1209,7 +1302,7 @@ function fromVideoDocument(document) {
|
|
|
1209
1302
|
above_main_tracks: aboveTracks.map((t) => draftTrack(t, view.absByPartId)),
|
|
1210
1303
|
below_main_tracks: belowTracks.map((t) => draftTrack(t, view.absByPartId)),
|
|
1211
1304
|
part_aggregations: view.aggregations,
|
|
1212
|
-
part_library: toReadViewPartLibrary(view.partLibrary, view.durationMs),
|
|
1305
|
+
part_library: toReadViewPartLibrary(view.partLibrary, view.durationMs, view.derivedFillerPartIds),
|
|
1213
1306
|
version: document.meta.version
|
|
1214
1307
|
};
|
|
1215
1308
|
}
|
|
@@ -1225,31 +1318,38 @@ function fromVideoDocument(document) {
|
|
|
1225
1318
|
*
|
|
1226
1319
|
* Parts are deep-cloned; the engine extensions (`media_duration_ms`,
|
|
1227
1320
|
* `initial_duration_ms`) are kept alongside the injected `duration_ms`.
|
|
1321
|
+
*
|
|
1322
|
+
* `derivedFillerPartIds` are skipped: the solve minted them to lay the track out
|
|
1323
|
+
* and they are not part of the document (see `fromVideoDocument`). An empty clip a
|
|
1324
|
+
* writer placed deliberately is NOT in that set and is projected normally.
|
|
1228
1325
|
*/
|
|
1229
|
-
function toReadViewPartLibrary(partLibrary, durationMs) {
|
|
1326
|
+
function toReadViewPartLibrary(partLibrary, durationMs, derivedFillerPartIds) {
|
|
1230
1327
|
const out = {};
|
|
1231
|
-
for (const [partId, part] of Object.entries(partLibrary))
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1328
|
+
for (const [partId, part] of Object.entries(partLibrary)) {
|
|
1329
|
+
if (derivedFillerPartIds.has(partId)) continue;
|
|
1330
|
+
if (part.video_clip != null) {
|
|
1331
|
+
const clip = clone(part.video_clip);
|
|
1332
|
+
out[partId] = { video_clip: {
|
|
1333
|
+
...clip,
|
|
1334
|
+
duration_ms: effectiveVideoClipDurationMs(clip)
|
|
1335
|
+
} };
|
|
1336
|
+
} else if (part.speech != null) {
|
|
1337
|
+
const speech = clone(part.speech);
|
|
1338
|
+
out[partId] = { speech: {
|
|
1339
|
+
...speech,
|
|
1340
|
+
duration_ms: speech.media_duration_ms
|
|
1341
|
+
} };
|
|
1342
|
+
} else if (part.caption != null) {
|
|
1343
|
+
const caption = clone(part.caption);
|
|
1344
|
+
out[partId] = { caption: {
|
|
1345
|
+
...caption,
|
|
1346
|
+
duration_ms: caption.initial_duration_ms
|
|
1347
|
+
} };
|
|
1348
|
+
} else if (part.bgm != null) out[partId] = { bgm: {
|
|
1349
|
+
...clone(part.bgm),
|
|
1350
|
+
duration_ms: durationMs
|
|
1248
1351
|
} };
|
|
1249
|
-
}
|
|
1250
|
-
...clone(part.bgm),
|
|
1251
|
-
duration_ms: durationMs
|
|
1252
|
-
} };
|
|
1352
|
+
}
|
|
1253
1353
|
return out;
|
|
1254
1354
|
}
|
|
1255
1355
|
/** Deep-clone a part payload, dropping the derived read-view `duration_ms`. */
|
|
@@ -1307,6 +1407,57 @@ function clone(value) {
|
|
|
1307
1407
|
return value;
|
|
1308
1408
|
}
|
|
1309
1409
|
//#endregion
|
|
1410
|
+
//#region src/document/initial-document.ts
|
|
1411
|
+
/**
|
|
1412
|
+
* Build the `VideoDocument` a newly created project starts from: the caller's
|
|
1413
|
+
* project facts, no parts, and one empty track per lane.
|
|
1414
|
+
*
|
|
1415
|
+
* The empty lane tracks are the reason this function exists rather than callers
|
|
1416
|
+
* assembling a document inline. Lane tracks are otherwise minted lazily by the
|
|
1417
|
+
* first op that needs one (`ensureLaneTrack`), which is find-then-mint over a
|
|
1418
|
+
* CRDT list — so if the Agent's first generation and a user edit race on an
|
|
1419
|
+
* empty document, each mints its own copy of the same lane and the merge keeps
|
|
1420
|
+
* both. Two video_clip tracks violate the at-most-one-main-track rule, and the
|
|
1421
|
+
* merged document becomes unprojectable for *every* reader (editor, render,
|
|
1422
|
+
* share) with no winning replica to fall back on. Seeding all four lanes up
|
|
1423
|
+
* front means `ensureLaneTrack` only ever finds, never mints, so the race cannot
|
|
1424
|
+
* happen on a document created here.
|
|
1425
|
+
*
|
|
1426
|
+
* All four, not just the main lane: the secondary lanes fail less loudly (a
|
|
1427
|
+
* duplicate pane rather than an unreadable document), but they fail by the same
|
|
1428
|
+
* mechanism, and covering only the fatal one would leave three live races.
|
|
1429
|
+
*
|
|
1430
|
+
* `timeline` is left absent: `unit_time_ms` is a display concern the editor
|
|
1431
|
+
* supplies, and total duration is derived on read (RFC 02 §6), so a new document
|
|
1432
|
+
* has no timeline fact to state.
|
|
1433
|
+
*/
|
|
1434
|
+
function buildInitialVideoDocument(facts) {
|
|
1435
|
+
return {
|
|
1436
|
+
meta: {
|
|
1437
|
+
schema_version: VIDEO_DOCUMENT_SCHEMA_VERSION,
|
|
1438
|
+
draft_id: facts.draftId,
|
|
1439
|
+
project_id: facts.projectId,
|
|
1440
|
+
owner_id: facts.ownerId,
|
|
1441
|
+
thumbnail_storage_key: void 0,
|
|
1442
|
+
chat_session_id: facts.chatSessionId,
|
|
1443
|
+
video_creation_settings: facts.videoCreationSettings,
|
|
1444
|
+
version: void 0
|
|
1445
|
+
},
|
|
1446
|
+
timeline: void 0,
|
|
1447
|
+
tracks: emptyLaneTracks(),
|
|
1448
|
+
part_library: {}
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
/** One empty track per lane, in lane-stacking order, with the conventional ids. */
|
|
1452
|
+
function emptyLaneTracks() {
|
|
1453
|
+
return LANE_KINDS_IN_STACK_ORDER.map((kind) => ({
|
|
1454
|
+
id: laneTrackId(kind),
|
|
1455
|
+
parts_kind: kind,
|
|
1456
|
+
is_hidden: void 0,
|
|
1457
|
+
items: []
|
|
1458
|
+
}));
|
|
1459
|
+
}
|
|
1460
|
+
//#endregion
|
|
1310
1461
|
//#region src/document/mirror-read.ts
|
|
1311
1462
|
/**
|
|
1312
1463
|
* Project the mirror state (`VideoDocumentDraft`) into the authoritative
|
|
@@ -1422,7 +1573,8 @@ var MirrorVideoDocumentAdapter = class {
|
|
|
1422
1573
|
message: JSON.stringify({
|
|
1423
1574
|
semantic_op: audit.kind,
|
|
1424
1575
|
payload: audit.payload,
|
|
1425
|
-
intent: audit.intent ?? null
|
|
1576
|
+
intent: audit.intent ?? null,
|
|
1577
|
+
actor: audit.actor ?? null
|
|
1426
1578
|
})
|
|
1427
1579
|
});
|
|
1428
1580
|
}
|
|
@@ -1436,7 +1588,7 @@ function createMirrorVideoDocument(document, options = {}) {
|
|
|
1436
1588
|
doc,
|
|
1437
1589
|
schema: videoDocumentMirrorSchema
|
|
1438
1590
|
}).setState((draft) => {
|
|
1439
|
-
|
|
1591
|
+
seedDraft(draft, document);
|
|
1440
1592
|
}, { origin: options.origin ?? "mengine.bootstrap" });
|
|
1441
1593
|
return doc;
|
|
1442
1594
|
}
|
|
@@ -1444,8 +1596,8 @@ function createMirrorVideoDocument(document, options = {}) {
|
|
|
1444
1596
|
function createMirrorVideoDocumentAdapter(document, options) {
|
|
1445
1597
|
return new MirrorVideoDocumentAdapter(createMirrorVideoDocument(document, options));
|
|
1446
1598
|
}
|
|
1447
|
-
/** Write a whole `VideoDocument` into
|
|
1448
|
-
function
|
|
1599
|
+
/** Write a whole `VideoDocument` into the mirror draft (used to seed a fresh doc). */
|
|
1600
|
+
function seedDraft(draft, document) {
|
|
1449
1601
|
draft.meta = {
|
|
1450
1602
|
schema_version: document.meta.schema_version,
|
|
1451
1603
|
draft_id: document.meta.draft_id ?? void 0,
|
|
@@ -1475,119 +1627,4 @@ function toTrackRow(track) {
|
|
|
1475
1627
|
};
|
|
1476
1628
|
}
|
|
1477
1629
|
//#endregion
|
|
1478
|
-
|
|
1479
|
-
/**
|
|
1480
|
-
* Part-id generation, aligned with the online ecosystem.
|
|
1481
|
-
*
|
|
1482
|
-
* The authoritative online producers — agent-harness (`@harness/shared`
|
|
1483
|
-
* `genObjId`) and director.v2 (`common/obj_id.py` `gen_obj_id`) — both mint part
|
|
1484
|
-
* ids as `` `${prefix}_${ulid()}` ``, and real captured drafts use exactly that
|
|
1485
|
-
* shape (`clip_…` / `spe_…` / `cap_…` / `bgm_…`, each a 26-char ULID). The engine
|
|
1486
|
-
* previously emitted `vc_<base36 timestamp><6 random>`, a different prefix AND a
|
|
1487
|
-
* different encoding — the sole cross-repo id divergence. This module removes it
|
|
1488
|
-
* by emitting the same `<prefix>_<ULID>` bytes.
|
|
1489
|
-
*
|
|
1490
|
-
* The ULID is generated inline (Crockford Base32, 48-bit time + 80-bit random)
|
|
1491
|
-
* rather than pulling the `ulid` npm package: the randomness class matches the
|
|
1492
|
-
* old generator (both `Math.random`-based) and it keeps `@mengine/medeo-client`
|
|
1493
|
-
* dependency-free for a purely mechanical id string. Part ids only need to be
|
|
1494
|
-
* unique and lexicographically time-sortable, which this satisfies.
|
|
1495
|
-
*/
|
|
1496
|
-
/** Crockford Base32 alphabet (no I, L, O, U), per the ULID spec. */
|
|
1497
|
-
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
1498
|
-
const TIME_LEN = 10;
|
|
1499
|
-
const RANDOM_LEN = 16;
|
|
1500
|
-
function encodeTime(now) {
|
|
1501
|
-
let out = "";
|
|
1502
|
-
let ms = now;
|
|
1503
|
-
for (let i = TIME_LEN - 1; i >= 0; i--) {
|
|
1504
|
-
const mod = ms % 32;
|
|
1505
|
-
out = CROCKFORD[mod] + out;
|
|
1506
|
-
ms = (ms - mod) / 32;
|
|
1507
|
-
}
|
|
1508
|
-
return out;
|
|
1509
|
-
}
|
|
1510
|
-
function encodeRandom() {
|
|
1511
|
-
let out = "";
|
|
1512
|
-
for (let i = 0; i < RANDOM_LEN; i++) out += CROCKFORD[Math.floor(Math.random() * 32)];
|
|
1513
|
-
return out;
|
|
1514
|
-
}
|
|
1515
|
-
/** A 26-char Crockford Base32 ULID (10-char time + 16-char random). */
|
|
1516
|
-
function ulid() {
|
|
1517
|
-
return encodeTime(Date.now()) + encodeRandom();
|
|
1518
|
-
}
|
|
1519
|
-
function generatePartId(prefix) {
|
|
1520
|
-
return `${prefix}_${ulid()}`;
|
|
1521
|
-
}
|
|
1522
|
-
//#endregion
|
|
1523
|
-
//#region src/document/plain-memory-adapter.ts
|
|
1524
|
-
/**
|
|
1525
|
-
* Pure in-memory `SemanticDocumentAdapter` — no Loro/WASM. Holds a
|
|
1526
|
-
* `VideoDocumentDraft` object and applies each `transact` via immer `produce`,
|
|
1527
|
-
* journaling every audit that actually mutated state (plus any ids minted
|
|
1528
|
-
* during that transact).
|
|
1529
|
-
*
|
|
1530
|
-
* Known benign difference vs `MirrorVideoDocumentAdapter`: the editor's
|
|
1531
|
-
* `setPart` assigns a fresh part object on every call, so a same-value rewrite
|
|
1532
|
-
* produces a new immer state and IS journaled here, while the mirror's deep
|
|
1533
|
-
* diff emits no commit. Terminal `snapshot()` stays equal; replay is idempotent.
|
|
1534
|
-
*/
|
|
1535
|
-
var PlainMemoryAdapter = class {
|
|
1536
|
-
state;
|
|
1537
|
-
_journal = [];
|
|
1538
|
-
baseIdFactory;
|
|
1539
|
-
/** Non-null only while a `transact` edit callback is running. */
|
|
1540
|
-
pendingIds = null;
|
|
1541
|
-
/**
|
|
1542
|
-
* Recording wrapper around the underlying factory. Callers (sandbox editor)
|
|
1543
|
-
* use this so every minted id is appended to the current transact's list.
|
|
1544
|
-
*/
|
|
1545
|
-
idFactory;
|
|
1546
|
-
constructor(document, options) {
|
|
1547
|
-
assertValidVideoDocument(document);
|
|
1548
|
-
const draft = {};
|
|
1549
|
-
writeVideoDocumentToDraft(draft, document);
|
|
1550
|
-
this.state = draft;
|
|
1551
|
-
this.baseIdFactory = options?.idFactory ?? generatePartId;
|
|
1552
|
-
this.idFactory = (prefix) => {
|
|
1553
|
-
const id = this.baseIdFactory(prefix);
|
|
1554
|
-
if (this.pendingIds != null) this.pendingIds.push(id);
|
|
1555
|
-
return id;
|
|
1556
|
-
};
|
|
1557
|
-
}
|
|
1558
|
-
get journal() {
|
|
1559
|
-
return this._journal;
|
|
1560
|
-
}
|
|
1561
|
-
hasContent() {
|
|
1562
|
-
return (this.state.meta?.schema_version ?? "") !== "";
|
|
1563
|
-
}
|
|
1564
|
-
snapshot() {
|
|
1565
|
-
return readVideoDocumentFromDraft(this.state);
|
|
1566
|
-
}
|
|
1567
|
-
/**
|
|
1568
|
-
* Apply one op via immer. A throw in `edit` discards the draft (state and
|
|
1569
|
-
* journal unchanged). When `produce` returns the same reference, there was
|
|
1570
|
-
* no structural change — skip journal, matching mirror "no change, no commit".
|
|
1571
|
-
*/
|
|
1572
|
-
transact(edit, audit) {
|
|
1573
|
-
this.pendingIds = [];
|
|
1574
|
-
try {
|
|
1575
|
-
const next = produce(this.state, edit);
|
|
1576
|
-
if (next !== this.state) {
|
|
1577
|
-
this.state = next;
|
|
1578
|
-
this._journal.push({
|
|
1579
|
-
...audit,
|
|
1580
|
-
generated_ids: this.pendingIds
|
|
1581
|
-
});
|
|
1582
|
-
}
|
|
1583
|
-
} finally {
|
|
1584
|
-
this.pendingIds = null;
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
};
|
|
1588
|
-
/** Build a `PlainMemoryAdapter` seeded with `document`. */
|
|
1589
|
-
function createPlainMemoryAdapter(document, options) {
|
|
1590
|
-
return new PlainMemoryAdapter(document, options);
|
|
1591
|
-
}
|
|
1592
|
-
//#endregion
|
|
1593
|
-
export { isEmptyVideoClip as A, bytesToBase64 as B, fillMainTrackTimeGaps as C, resolveSpeechOverlapByShiftingVideos as D, resolveAllSpeechOverlaps as E, speedOf as F, partUnionToDraft as I, recordEntries as L, safeDurationMs as M, VIDEO_DOCUMENT_SCHEMA_VERSION as N, syncAggregatedClipsTimePosition as O, effectiveVideoClipDurationMs as P, videoDocumentMirrorSchema as R, arrangeMainTrackSeamlessly as S, recalculateTimelineDuration as T, videoDocumentSchema as _, createMirrorVideoDocument as a, solveVideoDocument as b, readVideoDocumentFromDraft as c, fromVideoDocument as d, toVideoDocument as f, partUnionSchema as g, validateVideoDocument as h, MirrorVideoDocumentAdapter as i, partDurationMs as j, TIMELINE_SKELETON_DURATION_MS as k, buildSpeechHostMap as l, assertValidVideoDocument as m, createPlainMemoryAdapter as n, createMirrorVideoDocumentAdapter as o, VideoDocumentValidationError as p, generatePartId as r, writeVideoDocumentToDraft as s, PlainMemoryAdapter as t, derivePositionFromAbs as u, ensureLaneTrack as v, reassignSpeechesToVideoClipsByTime as w, cascadeAfterVideoClipChanges as x, findLaneTrack as y, base64ToBytes as z };
|
|
1630
|
+
export { partDurationMs as A, reassignSpeechesToVideoClipsByTime as C, syncAggregatedClipsTimePosition as D, resolveSpeechOverlapByShiftingVideos as E, recordEntries as F, videoDocumentMirrorSchema as I, base64ToBytes as L, VIDEO_DOCUMENT_SCHEMA_VERSION as M, effectiveVideoClipDurationMs as N, TIMELINE_SKELETON_DURATION_MS as O, partUnionToDraft as P, bytesToBase64 as R, fillMainTrackTimeGaps as S, resolveAllSpeechOverlaps as T, findLaneTrack as _, buildInitialVideoDocument as a, cascadeAfterVideoClipChanges as b, fromVideoDocument as c, assertValidVideoDocument as d, validateVideoDocument as f, ensureLaneTrack as g, LANE_KINDS_IN_STACK_ORDER as h, readVideoDocumentFromDraft as i, safeDurationMs as j, isEmptyVideoClip as k, toVideoDocument as l, videoDocumentSchema as m, createMirrorVideoDocument as n, buildSpeechHostMap as o, partUnionSchema as p, createMirrorVideoDocumentAdapter as r, derivePositionFromAbs as s, MirrorVideoDocumentAdapter as t, VideoDocumentValidationError as u, laneTrackId as v, recalculateTimelineDuration as w, arrangeMainTrackSeamlessly as x, solveVideoDocument as y };
|