@mengine/medeo-tool 1.2.1-alpha.7 → 1.2.1-alpha.9
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 +71 -16
- package/dist/{entity-contract-B3txrzTt.d.mts → entity-contract-DycLxdQ5.d.mts} +49 -3
- package/dist/index.d.mts +192 -13
- package/dist/index.mjs +1030 -923
- package/dist/index.mjs.map +1 -1
- package/dist/sandbox-api.d.mts +397 -818
- package/dist/{script-session-BF44uKv_.mjs → script-session-CHyIUBkO.mjs} +366 -26
- package/dist/script-session-CHyIUBkO.mjs.map +1 -0
- package/dist/worker-entry.d.mts +5 -4
- package/dist/worker-entry.mjs +286 -5
- package/dist/worker-entry.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/script-session-BF44uKv_.mjs.map +0 -1
|
@@ -196,11 +196,15 @@ function validateEntityRelationSet(entityRefs, index, options) {
|
|
|
196
196
|
}));
|
|
197
197
|
issues.push(...validateMarkerSourceBounds(marker, index, (left, right) => options.compareMarkerPoints(marker, "source", left, right)));
|
|
198
198
|
}
|
|
199
|
-
if (current.entityKind === "clip")
|
|
199
|
+
if (current.entityKind === "clip") {
|
|
200
|
+
issues.push(...validateClipAdmission(entity, index));
|
|
201
|
+
issues.push(...validateClipPlacement(entity, index));
|
|
202
|
+
}
|
|
200
203
|
if (current.entityKind === "axvideo") issues.push(...validateAXVideoAdmission(entity, index));
|
|
201
204
|
if (current.entityKind === "caption") issues.push(...validateCaptionAsset(entity, index));
|
|
202
205
|
issues.push(...validateSequenceComposition(entity));
|
|
203
206
|
}
|
|
207
|
+
issues.push(...validateClipAnchorCycles(entities, index));
|
|
204
208
|
return issues;
|
|
205
209
|
}
|
|
206
210
|
function validateMarkerUse(marker, index) {
|
|
@@ -269,13 +273,84 @@ function validateAXVideoAdmission(axVideo, index) {
|
|
|
269
273
|
}];
|
|
270
274
|
}
|
|
271
275
|
function validateCaptionAsset(caption, index) {
|
|
272
|
-
|
|
276
|
+
const assets = ofKind([...index.relationsOf(caption)], "physical-asset").map((relation) => relation.other(caption)?.deref()?.current()).filter((entity) => entity?.entityKind === "asset");
|
|
277
|
+
if (assets.length > 0) {
|
|
278
|
+
const captionText = caption.current().text;
|
|
279
|
+
if (!assets.some((asset) => {
|
|
280
|
+
const inline = asset.inline;
|
|
281
|
+
return isRecord(inline) && inline.mediaType === "text/plain" && inline.text !== captionText;
|
|
282
|
+
})) return [];
|
|
283
|
+
return [{
|
|
284
|
+
code: "caption_inline_asset_mismatch",
|
|
285
|
+
entityId: caption.entityId,
|
|
286
|
+
message: "Caption text must match the text/plain inline Physical Asset"
|
|
287
|
+
}];
|
|
288
|
+
}
|
|
273
289
|
return [{
|
|
274
290
|
code: "caption_asset_required",
|
|
275
291
|
entityId: caption.entityId,
|
|
276
292
|
message: "Caption must have a Physical Asset Relation"
|
|
277
293
|
}];
|
|
278
294
|
}
|
|
295
|
+
function validateClipPlacement(clip, index) {
|
|
296
|
+
const marker = ofKind([...index.relationsOf(clip)], "clip-marker")[0]?.other(clip)?.deref();
|
|
297
|
+
if (marker?.current().entityKind !== "sequence-marker") return [];
|
|
298
|
+
const markerValue = marker.current();
|
|
299
|
+
const anchors = ofKind([...index.relationsOf(clip)], "clip-anchor").filter((relation) => relation.endpoints[0].deref() === clip);
|
|
300
|
+
const hasOrder = clip.current().order !== void 0;
|
|
301
|
+
const hasTarget = markerValue.targetRange !== void 0;
|
|
302
|
+
const hasAnchorOffset = markerValue.anchorOffset !== void 0;
|
|
303
|
+
const issues = [];
|
|
304
|
+
if (anchors.length > 1) issues.push({
|
|
305
|
+
code: "clip_anchor_cardinality",
|
|
306
|
+
entityId: clip.entityId,
|
|
307
|
+
message: "A Clip may follow at most one host Clip"
|
|
308
|
+
});
|
|
309
|
+
const hasAnchor = anchors.length === 1;
|
|
310
|
+
if (hasAnchor && (!hasAnchorOffset || hasOrder || hasTarget) || !hasAnchor && (hasAnchorOffset || Number(hasOrder) + Number(hasTarget) !== 1)) issues.push({
|
|
311
|
+
code: "clip_placement_invalid",
|
|
312
|
+
entityId: clip.entityId,
|
|
313
|
+
message: "Clip placement must be exactly one of Clip.order, Marker.targetRange, or clip-anchor with Marker.anchorOffset"
|
|
314
|
+
});
|
|
315
|
+
if (markerValue.durationPolicy === "timeline") {
|
|
316
|
+
const content = ofKind([...index.relationsOf(marker)], "marker-content")[0]?.other(marker)?.deref()?.current();
|
|
317
|
+
const track = ofKind([...index.relationsOf(clip)], "track-clip")[0]?.other(clip)?.deref()?.current();
|
|
318
|
+
const trackRole = track?.entityKind === "track" ? track.role : void 0;
|
|
319
|
+
if (content?.entityKind !== "audio" || trackRole !== "bgm" || !hasOrder) issues.push({
|
|
320
|
+
code: "timeline_duration_policy_invalid",
|
|
321
|
+
entityId: marker.entityId,
|
|
322
|
+
message: "Marker durationPolicy \"timeline\" is only valid for ordered Audio Clips on the bgm Track"
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return issues;
|
|
326
|
+
}
|
|
327
|
+
function validateClipAnchorCycles(entities, index) {
|
|
328
|
+
const hostByChild = /* @__PURE__ */ new Map();
|
|
329
|
+
for (const entity of entities) {
|
|
330
|
+
if (entity.current().entityKind !== "clip") continue;
|
|
331
|
+
for (const relation of ofKind([...index.relationsOf(entity)], "clip-anchor")) {
|
|
332
|
+
if (relation.endpoints[0].deref() !== entity) continue;
|
|
333
|
+
const host = relation.endpoints[1].deref();
|
|
334
|
+
if (host != null) hostByChild.set(entity.entityId, host.entityId);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const issues = [];
|
|
338
|
+
for (const child of hostByChild.keys()) {
|
|
339
|
+
const seen = /* @__PURE__ */ new Set();
|
|
340
|
+
let current = child;
|
|
341
|
+
while (current !== void 0 && !seen.has(current)) {
|
|
342
|
+
seen.add(current);
|
|
343
|
+
current = hostByChild.get(current);
|
|
344
|
+
}
|
|
345
|
+
if (current === void 0) continue;
|
|
346
|
+
issues.push({
|
|
347
|
+
code: "clip_anchor_cycle",
|
|
348
|
+
entityId: child,
|
|
349
|
+
message: "clip-anchor Relations must form an acyclic dependency graph"
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return issues;
|
|
353
|
+
}
|
|
279
354
|
function validateMarkerRanges(marker, compare) {
|
|
280
355
|
const current = marker.current();
|
|
281
356
|
const issues = [];
|
|
@@ -330,7 +405,7 @@ function validateEntity(entity, entityIds = /* @__PURE__ */ new Set()) {
|
|
|
330
405
|
entityId: current.entityId,
|
|
331
406
|
message: `Entity "${current.entityKind}" ${problem}`
|
|
332
407
|
});
|
|
333
|
-
const peerIdPaths = [...collectPeerEntityIdPaths(current, current.entityKind), ...collectPeerEntityValuePaths(current, current.entityId, entityIds)];
|
|
408
|
+
const peerIdPaths = [...collectPeerEntityIdPaths(current, current.entityKind), ...collectPeerEntityValuePaths(current, current.entityKind, current.entityId, entityIds)];
|
|
334
409
|
const uniquePeerIdPaths = [...new Set(peerIdPaths)].sort();
|
|
335
410
|
if (uniquePeerIdPaths.length > 0) issues.push({
|
|
336
411
|
code: "peer_entity_id_field",
|
|
@@ -347,12 +422,15 @@ function validateKnownEntityPayload(entity) {
|
|
|
347
422
|
case "track":
|
|
348
423
|
validateOptionalField(value, "hidden", "boolean", problems);
|
|
349
424
|
validateOptionalField(value, "role", "string", problems);
|
|
425
|
+
validateOptionalFiniteNumber(value, "order", problems);
|
|
350
426
|
break;
|
|
351
427
|
case "video":
|
|
352
428
|
case "audio":
|
|
353
429
|
case "voice":
|
|
354
430
|
case "caption":
|
|
355
431
|
validateSequencePayload(value, "bounded", "native", problems);
|
|
432
|
+
if (entity.entityKind === "voice") validateVoicePayload(value, problems);
|
|
433
|
+
if (entity.entityKind === "caption") validateCaptionPayload(value, problems);
|
|
356
434
|
break;
|
|
357
435
|
case "image":
|
|
358
436
|
validateSequencePayload(value, "unbounded", "constant", problems);
|
|
@@ -368,9 +446,15 @@ function validateKnownEntityPayload(entity) {
|
|
|
368
446
|
validateScriptPayload(value, problems);
|
|
369
447
|
break;
|
|
370
448
|
case "timeline":
|
|
449
|
+
case "viewport": break;
|
|
371
450
|
case "clip":
|
|
451
|
+
validateOptionalFiniteNumber(value, "order", problems);
|
|
452
|
+
validateOptionalFiniteNumber(value, "volume", problems);
|
|
453
|
+
if (typeof value.volume === "number" && (value.volume < -60 || value.volume > 20)) problems.push("volume must be decibels between -60 and 20");
|
|
454
|
+
break;
|
|
372
455
|
case "asset":
|
|
373
|
-
|
|
456
|
+
validateAssetPayload(value, problems);
|
|
457
|
+
break;
|
|
374
458
|
default: break;
|
|
375
459
|
}
|
|
376
460
|
return problems;
|
|
@@ -395,6 +479,63 @@ function validateMarkerPayload(value, problems) {
|
|
|
395
479
|
else if (duration.mode === "fixed") {
|
|
396
480
|
if (!Object.hasOwn(duration, "value") || duration.value === void 0) problems.push("duration.value is required when duration.mode is \"fixed\"");
|
|
397
481
|
} else if (duration.mode !== "from-source") problems.push("duration.mode must be \"from-source\" or \"fixed\"");
|
|
482
|
+
if (value.anchorOffset === void 0 && Object.hasOwn(value, "anchorOffset")) problems.push("anchorOffset cannot be undefined when present");
|
|
483
|
+
if (value.durationPolicy !== void 0 && value.durationPolicy !== "timeline") problems.push("durationPolicy must be \"timeline\" when present");
|
|
484
|
+
}
|
|
485
|
+
function validateAssetPayload(value, problems) {
|
|
486
|
+
const external = value.external;
|
|
487
|
+
if (external !== void 0) if (!isRecord(external)) problems.push("external must be an object when present");
|
|
488
|
+
else {
|
|
489
|
+
if (external.system !== "memota" && external.system !== "memota-speech") problems.push("external.system must be \"memota\" or \"memota-speech\"");
|
|
490
|
+
if (typeof external.key !== "string" || external.key.trim() === "") problems.push("external.key must be a non-empty string");
|
|
491
|
+
}
|
|
492
|
+
if (value.storageKey !== void 0 && (typeof value.storageKey !== "string" || value.storageKey.trim() === "")) problems.push("storageKey must be a non-empty string when present");
|
|
493
|
+
const inline = value.inline;
|
|
494
|
+
if (inline !== void 0) if (!isRecord(inline)) problems.push("inline must be an object when present");
|
|
495
|
+
else {
|
|
496
|
+
if (inline.mediaType !== "text/plain") problems.push("inline.mediaType must be \"text/plain\"");
|
|
497
|
+
if (typeof inline.text !== "string") problems.push("inline.text must be a string");
|
|
498
|
+
}
|
|
499
|
+
if (external !== void 0 && inline !== void 0) problems.push("Asset must not combine external and inline physical locations");
|
|
500
|
+
}
|
|
501
|
+
function validateVoicePayload(value, problems) {
|
|
502
|
+
const voice = value.voice;
|
|
503
|
+
if (voice === void 0) return;
|
|
504
|
+
if (!isRecord(voice)) {
|
|
505
|
+
problems.push("voice must be an object when present");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (voice.system !== "voice-library") problems.push("voice.system must be \"voice-library\"");
|
|
509
|
+
if (typeof voice.key !== "string" || voice.key.trim() === "") problems.push("voice.key must be a non-empty string");
|
|
510
|
+
if (voice.name !== void 0 && typeof voice.name !== "string") problems.push("voice.name must be a string when present");
|
|
511
|
+
}
|
|
512
|
+
function validateCaptionPayload(value, problems) {
|
|
513
|
+
if (value.text !== void 0 && typeof value.text !== "string") problems.push("text must be a string when present");
|
|
514
|
+
const style = value.style;
|
|
515
|
+
if (style === void 0) return;
|
|
516
|
+
if (!isRecord(style)) {
|
|
517
|
+
problems.push("style must be an object when present");
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const font = style.font;
|
|
521
|
+
if (font !== void 0) if (!isRecord(font)) problems.push("style.font must be an object when present");
|
|
522
|
+
else {
|
|
523
|
+
if (font.system !== "font-library") problems.push("style.font.system must be \"font-library\"");
|
|
524
|
+
if (typeof font.key !== "string" || font.key.trim() === "") problems.push("style.font.key must be a non-empty string");
|
|
525
|
+
}
|
|
526
|
+
for (const key of [
|
|
527
|
+
"fontSize",
|
|
528
|
+
"fontWeight",
|
|
529
|
+
"entranceAnimationDurationMs",
|
|
530
|
+
"strokeWidth",
|
|
531
|
+
"positionX",
|
|
532
|
+
"positionY"
|
|
533
|
+
]) validateOptionalFiniteNumber(style, key, problems, `style.${key}`);
|
|
534
|
+
for (const key of [
|
|
535
|
+
"fontColor",
|
|
536
|
+
"entranceAnimation",
|
|
537
|
+
"strokeColor"
|
|
538
|
+
]) if (style[key] !== void 0 && typeof style[key] !== "string") problems.push(`style.${key} must be a string`);
|
|
398
539
|
}
|
|
399
540
|
function validateRange(value, path, required, problems) {
|
|
400
541
|
if (!isRecord(value)) {
|
|
@@ -422,6 +563,9 @@ function validateScriptPayload(value, problems) {
|
|
|
422
563
|
function validateOptionalField(value, key, expectedType, problems) {
|
|
423
564
|
if (value[key] !== void 0 && typeof value[key] !== expectedType) problems.push(`${key} must be a ${expectedType} when present`);
|
|
424
565
|
}
|
|
566
|
+
function validateOptionalFiniteNumber(value, key, problems, label = key) {
|
|
567
|
+
if (value[key] !== void 0 && (typeof value[key] !== "number" || !Number.isFinite(value[key]))) problems.push(`${label} must be a finite number when present`);
|
|
568
|
+
}
|
|
425
569
|
function collectPeerEntityIdPaths(value, entityKind) {
|
|
426
570
|
const paths = [];
|
|
427
571
|
visitPeerEntityIdPaths(value, entityKind, "", /* @__PURE__ */ new Set(), paths);
|
|
@@ -439,25 +583,42 @@ function visitPeerEntityIdPaths(value, entityKind, parentPath, ancestors, paths)
|
|
|
439
583
|
}
|
|
440
584
|
ancestors.delete(value);
|
|
441
585
|
}
|
|
442
|
-
function collectPeerEntityValuePaths(value, ownEntityId, entityIds) {
|
|
586
|
+
function collectPeerEntityValuePaths(value, entityKind, ownEntityId, entityIds) {
|
|
443
587
|
const paths = [];
|
|
444
|
-
visitPeerEntityValues(value, ownEntityId, entityIds, "", /* @__PURE__ */ new Set(), paths);
|
|
588
|
+
visitPeerEntityValues(value, entityKind, ownEntityId, entityIds, "", /* @__PURE__ */ new Set(), paths);
|
|
445
589
|
return paths;
|
|
446
590
|
}
|
|
447
|
-
function visitPeerEntityValues(value, ownEntityId, entityIds, path, ancestors, paths) {
|
|
591
|
+
function visitPeerEntityValues(value, entityKind, ownEntityId, entityIds, path, ancestors, paths) {
|
|
448
592
|
if (typeof value === "string") {
|
|
449
|
-
if (value !== ownEntityId && entityIds.has(value)) paths.push(path);
|
|
593
|
+
if (value !== ownEntityId && entityIds.has(value) && !isOwnedLocalEntityValuePath(entityKind, path)) paths.push(path);
|
|
450
594
|
return;
|
|
451
595
|
}
|
|
452
596
|
if (typeof value !== "object" || value == null || ancestors.has(value)) return;
|
|
453
597
|
ancestors.add(value);
|
|
454
|
-
if (Array.isArray(value)) for (const [index, item] of value.entries()) visitPeerEntityValues(item, ownEntityId, entityIds, `${path}[${index}]`, ancestors, paths);
|
|
598
|
+
if (Array.isArray(value)) for (const [index, item] of value.entries()) visitPeerEntityValues(item, entityKind, ownEntityId, entityIds, `${path}[${index}]`, ancestors, paths);
|
|
455
599
|
else for (const [key, child] of Object.entries(value)) {
|
|
456
600
|
if (path.length === 0 && (key === "entityId" || key === "entityKind")) continue;
|
|
457
|
-
visitPeerEntityValues(child, ownEntityId, entityIds, path.length === 0 ? key : `${path}.${key}`, ancestors, paths);
|
|
601
|
+
visitPeerEntityValues(child, entityKind, ownEntityId, entityIds, path.length === 0 ? key : `${path}.${key}`, ancestors, paths);
|
|
458
602
|
}
|
|
459
603
|
ancestors.delete(value);
|
|
460
604
|
}
|
|
605
|
+
function isOwnedLocalEntityValuePath(entityKind, path) {
|
|
606
|
+
if (entityKind === "track" && path === "role") return true;
|
|
607
|
+
if ([
|
|
608
|
+
"video",
|
|
609
|
+
"audio",
|
|
610
|
+
"voice",
|
|
611
|
+
"image",
|
|
612
|
+
"caption",
|
|
613
|
+
"axvideo"
|
|
614
|
+
].includes(entityKind) && /^(?:sampling|coordinateSpace|coordinateSpace\.unit|extent\.kind)$/.test(path)) return true;
|
|
615
|
+
if (entityKind === "sequence-marker" && /^(?:durationPolicy|duration\.mode|timeRemapping\.(?:kind|mode))$/.test(path)) return true;
|
|
616
|
+
if (entityKind === "asset" && /^(?:external\.(?:system|key)|storageKey|inline\.(?:mediaType|text))$/.test(path)) return true;
|
|
617
|
+
if (entityKind === "voice" && /^voice\.(?:system|key|name)$/.test(path)) return true;
|
|
618
|
+
if (entityKind === "caption" && (path === "text" || path.startsWith("style."))) return true;
|
|
619
|
+
if ((entityKind === "audio-script" || entityKind === "phonetic-script") && /^segments\[\d+\]\.(?:segmentId|text|language)$/.test(path)) return true;
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
461
622
|
function isOwnedLocalIdPath(entityKind, path) {
|
|
462
623
|
if (path === "lifecycle.actorId") return true;
|
|
463
624
|
if ((entityKind === "audio-script" || entityKind === "phonetic-script") && /^segments\[\d+\]\.segmentId$/.test(path)) return true;
|
|
@@ -516,6 +677,7 @@ function ofKind(relations, kind) {
|
|
|
516
677
|
}
|
|
517
678
|
//#endregion
|
|
518
679
|
//#region ../medeo-dsl/src/json-values.ts
|
|
680
|
+
const nativeObjectConstructorSource = Function.prototype.toString.call(Object);
|
|
519
681
|
function isJsonObject(value) {
|
|
520
682
|
return isJsonValue(value, /* @__PURE__ */ new Set()) && !Array.isArray(value) && value !== null;
|
|
521
683
|
}
|
|
@@ -523,14 +685,25 @@ function isJsonValue(value, ancestors) {
|
|
|
523
685
|
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
524
686
|
if (typeof value === "number") return Number.isFinite(value);
|
|
525
687
|
if (typeof value !== "object") return false;
|
|
526
|
-
|
|
527
|
-
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return false;
|
|
688
|
+
if (!Array.isArray(value) && !isPlainObject(value)) return false;
|
|
528
689
|
if (ancestors.has(value)) return false;
|
|
529
690
|
ancestors.add(value);
|
|
530
691
|
const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, ancestors)) : Object.values(value).every((item) => isJsonValue(item, ancestors));
|
|
531
692
|
ancestors.delete(value);
|
|
532
693
|
return valid;
|
|
533
694
|
}
|
|
695
|
+
/** Recognize an ordinary object from any VM realm without admitting class instances. */
|
|
696
|
+
function isPlainObject(value) {
|
|
697
|
+
try {
|
|
698
|
+
const prototype = Object.getPrototypeOf(value);
|
|
699
|
+
if (prototype === null) return true;
|
|
700
|
+
if (Object.getPrototypeOf(prototype) !== null) return false;
|
|
701
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
702
|
+
return typeof constructor === "function" && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === nativeObjectConstructorSource;
|
|
703
|
+
} catch {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
534
707
|
//#endregion
|
|
535
708
|
//#region ../medeo-dsl/src/relation-specs.ts
|
|
536
709
|
const timelineTrackRelationSpec = emptySpec("timeline-track", "timeline", "track");
|
|
@@ -561,6 +734,21 @@ const captionAlignmentRelationSpec = Object.freeze({
|
|
|
561
734
|
validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["caption"]), new Set(["audio", "voice"])),
|
|
562
735
|
validateMetadata: isCaptionAlignmentMetadata
|
|
563
736
|
});
|
|
737
|
+
/** `clip-anchor(child, host)` means endpoint 0 follows endpoint 1. */
|
|
738
|
+
const clipAnchorRelationSpec = Object.freeze({
|
|
739
|
+
kind: "clip-anchor",
|
|
740
|
+
validateEndpoints: (endpoints) => endpoints[0].current().entityKind === "clip" && endpoints[1].current().entityKind === "clip",
|
|
741
|
+
validateMetadata: isEmptyMetadata
|
|
742
|
+
});
|
|
743
|
+
/** `audio-script-render(output, script)` means endpoint 0 was rendered from endpoint 1. */
|
|
744
|
+
const audioScriptRenderRelationSpec = Object.freeze({
|
|
745
|
+
kind: "audio-script-render",
|
|
746
|
+
validateEndpoints: (endpoints) => {
|
|
747
|
+
const outputKind = endpoints[0].current().entityKind;
|
|
748
|
+
return (outputKind === "audio" || outputKind === "voice") && endpoints[1].current().entityKind === "audio-script";
|
|
749
|
+
},
|
|
750
|
+
validateMetadata: isEmptyMetadata
|
|
751
|
+
});
|
|
564
752
|
/** Built-in kinds are reserved; callers may add specs only under new names. */
|
|
565
753
|
const builtInRelationSpecs = Object.freeze([
|
|
566
754
|
timelineTrackRelationSpec,
|
|
@@ -573,7 +761,9 @@ const builtInRelationSpecs = Object.freeze([
|
|
|
573
761
|
generatedRelationSpec,
|
|
574
762
|
phoneticScriptProvenanceRelationSpec,
|
|
575
763
|
captionProvenanceRelationSpec,
|
|
576
|
-
captionAlignmentRelationSpec
|
|
764
|
+
captionAlignmentRelationSpec,
|
|
765
|
+
clipAnchorRelationSpec,
|
|
766
|
+
audioScriptRenderRelationSpec
|
|
577
767
|
]);
|
|
578
768
|
function emptySpec(kind, a, b) {
|
|
579
769
|
return metadataSpec(kind, a, b, isEmptyMetadata);
|
|
@@ -670,6 +860,7 @@ var BiRelationIndex = class {
|
|
|
670
860
|
byRelationId = /* @__PURE__ */ new Map();
|
|
671
861
|
link(input) {
|
|
672
862
|
if (input.spec.kind === "generated") throw new Error("Author generated Relations with linkGenerated({ output, input })");
|
|
863
|
+
if (input.spec.kind === "clip-anchor" || input.spec.kind === "audio-script-render") throw new Error(`Author ordered ${input.spec.kind} Relations with the dedicated role-named method`);
|
|
673
864
|
return this.linkValidated(input);
|
|
674
865
|
}
|
|
675
866
|
/** Author `generated(output, input)` without exposing positional arguments. */
|
|
@@ -682,6 +873,26 @@ var BiRelationIndex = class {
|
|
|
682
873
|
trace: input.trace
|
|
683
874
|
});
|
|
684
875
|
}
|
|
876
|
+
/** Author `clip-anchor(child, host)` without exposing positional arguments. */
|
|
877
|
+
linkClipAnchor(input) {
|
|
878
|
+
return this.linkValidated({
|
|
879
|
+
relationId: input.relationId,
|
|
880
|
+
spec: clipAnchorRelationSpec,
|
|
881
|
+
endpoints: [input.child, input.host],
|
|
882
|
+
metadata: {},
|
|
883
|
+
trace: input.trace
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
/** Author `audio-script-render(output, script)` without exposing positional arguments. */
|
|
887
|
+
linkAudioScriptRender(input) {
|
|
888
|
+
return this.linkValidated({
|
|
889
|
+
relationId: input.relationId,
|
|
890
|
+
spec: audioScriptRenderRelationSpec,
|
|
891
|
+
endpoints: [input.output, input.script],
|
|
892
|
+
metadata: {},
|
|
893
|
+
trace: input.trace
|
|
894
|
+
});
|
|
895
|
+
}
|
|
685
896
|
/**
|
|
686
897
|
* Rehydrate a persisted row after resolving its spec and endpoint refs.
|
|
687
898
|
*
|
|
@@ -955,17 +1166,36 @@ var EntitySandbox = class {
|
|
|
955
1166
|
}
|
|
956
1167
|
buildPlan() {
|
|
957
1168
|
decodeEntityRelationRows(toDslRows(this.state), numericMarkerComparators);
|
|
1169
|
+
const currentEntityIds = new Set(this.state.entities.map((entity) => entity.entity_id));
|
|
1170
|
+
const currentRelationIds = new Set(this.state.relations.map((relation) => relation.relation_id));
|
|
958
1171
|
return {
|
|
959
1172
|
base_revision: this.original.revision,
|
|
960
1173
|
commands: this.commands.slice(),
|
|
961
|
-
rows: cloneSnapshot(this.state)
|
|
1174
|
+
rows: cloneSnapshot(this.state),
|
|
1175
|
+
deleted_entity_ids: this.original.entities.map((entity) => entity.entity_id).filter((entityId) => !currentEntityIds.has(entityId)).sort(),
|
|
1176
|
+
deleted_relation_ids: this.original.relations.map((relation) => relation.relation_id).filter((relationId) => !currentRelationIds.has(relationId)).sort()
|
|
962
1177
|
};
|
|
963
1178
|
}
|
|
964
1179
|
renderPreview() {
|
|
965
1180
|
const lines = [`Entity plan: base_revision=${this.original.revision} commands=${this.commands.length} entities=${this.state.entities.length} relations=${this.state.relations.length}`];
|
|
966
|
-
for (const command of this.commands)
|
|
967
|
-
|
|
968
|
-
|
|
1181
|
+
for (const command of this.commands) switch (command.kind) {
|
|
1182
|
+
case "create-entity":
|
|
1183
|
+
lines.push(`+ entity ${command.entity.entity_id} kind=${command.entity.entity_kind}`);
|
|
1184
|
+
break;
|
|
1185
|
+
case "update-entity":
|
|
1186
|
+
lines.push(`~ entity ${command.entity_id} payload`);
|
|
1187
|
+
break;
|
|
1188
|
+
case "delete-entity":
|
|
1189
|
+
lines.push(`- entity ${command.entity_id}`);
|
|
1190
|
+
break;
|
|
1191
|
+
case "unlink-relation":
|
|
1192
|
+
lines.push(`- relation ${command.relation_id}`);
|
|
1193
|
+
break;
|
|
1194
|
+
case "link-relation":
|
|
1195
|
+
if (command.relation.relation_kind === "generated") lines.push(`+ relation ${command.relation.relation_id} generated(output=${command.relation.endpoint_0_entity_id}, input=${command.relation.endpoint_1_entity_id})`);
|
|
1196
|
+
else lines.push(`+ relation ${command.relation.relation_id} kind=${command.relation.relation_kind} endpoints=${command.relation.endpoint_0_entity_id},${command.relation.endpoint_1_entity_id}`);
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
969
1199
|
return lines.join("\n");
|
|
970
1200
|
}
|
|
971
1201
|
buildEntityFacade() {
|
|
@@ -980,6 +1210,8 @@ var EntitySandbox = class {
|
|
|
980
1210
|
return clone(this.state.entities.filter((entity) => entity.entity_kind === "asset" && isImportedMemotaAsset(entity.payload, assetId)));
|
|
981
1211
|
},
|
|
982
1212
|
create: (input) => this.createEntity(input),
|
|
1213
|
+
update: (input) => this.updateEntity(input),
|
|
1214
|
+
delete: (input) => this.deleteEntity(input),
|
|
983
1215
|
importAsset: (input) => this.importAsset(input)
|
|
984
1216
|
};
|
|
985
1217
|
}
|
|
@@ -992,7 +1224,10 @@ var EntitySandbox = class {
|
|
|
992
1224
|
return clone(this.state.relations.filter((relation) => (relation.endpoint_0_entity_id === entityId || relation.endpoint_1_entity_id === entityId) && (relationKind === void 0 || relation.relation_kind === relationKind)));
|
|
993
1225
|
},
|
|
994
1226
|
link: (input) => this.link(input),
|
|
995
|
-
linkGenerated: (input) => this.linkGenerated(input)
|
|
1227
|
+
linkGenerated: (input) => this.linkGenerated(input),
|
|
1228
|
+
linkClipAnchor: (input) => this.linkClipAnchor(input),
|
|
1229
|
+
linkAudioScriptRender: (input) => this.linkAudioScriptRender(input),
|
|
1230
|
+
unlink: (input) => this.unlinkRelation(input)
|
|
996
1231
|
};
|
|
997
1232
|
}
|
|
998
1233
|
createEntity(input) {
|
|
@@ -1017,6 +1252,23 @@ var EntitySandbox = class {
|
|
|
1017
1252
|
});
|
|
1018
1253
|
return entityId;
|
|
1019
1254
|
}
|
|
1255
|
+
updateEntity(input) {
|
|
1256
|
+
assertTrimmed(input.entity_id, "entity_id");
|
|
1257
|
+
const payload = clone(input.payload);
|
|
1258
|
+
if (!isJsonObject(payload)) throw new Error("Entity payload must contain only JSON values");
|
|
1259
|
+
this.record({
|
|
1260
|
+
kind: "update-entity",
|
|
1261
|
+
entity_id: input.entity_id,
|
|
1262
|
+
payload
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
deleteEntity(input) {
|
|
1266
|
+
assertTrimmed(input.entity_id, "entity_id");
|
|
1267
|
+
this.record({
|
|
1268
|
+
kind: "delete-entity",
|
|
1269
|
+
entity_id: input.entity_id
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1020
1272
|
importAsset(input) {
|
|
1021
1273
|
assertTrimmed(input.asset_id, "asset_id");
|
|
1022
1274
|
const payload = input.payload === void 0 ? {} : clone(input.payload);
|
|
@@ -1073,6 +1325,55 @@ var EntitySandbox = class {
|
|
|
1073
1325
|
});
|
|
1074
1326
|
return relation.relation_id;
|
|
1075
1327
|
}
|
|
1328
|
+
linkClipAnchor(input) {
|
|
1329
|
+
const relation = this.relationFromInput({
|
|
1330
|
+
...input.relation_id !== void 0 ? { relation_id: input.relation_id } : {},
|
|
1331
|
+
endpoint_0_entity_id: input.child_clip_entity_id,
|
|
1332
|
+
endpoint_1_entity_id: input.host_clip_entity_id,
|
|
1333
|
+
metadata: {},
|
|
1334
|
+
...input.trace !== void 0 ? { trace: input.trace } : {}
|
|
1335
|
+
}, "clip-anchor");
|
|
1336
|
+
const [child, host] = this.refsFor(relation);
|
|
1337
|
+
new BiRelationIndex().linkClipAnchor({
|
|
1338
|
+
relationId: createRelationId(relation.relation_id),
|
|
1339
|
+
child,
|
|
1340
|
+
host,
|
|
1341
|
+
trace: relation.trace
|
|
1342
|
+
});
|
|
1343
|
+
this.record({
|
|
1344
|
+
kind: "link-relation",
|
|
1345
|
+
relation
|
|
1346
|
+
});
|
|
1347
|
+
return relation.relation_id;
|
|
1348
|
+
}
|
|
1349
|
+
linkAudioScriptRender(input) {
|
|
1350
|
+
const relation = this.relationFromInput({
|
|
1351
|
+
...input.relation_id !== void 0 ? { relation_id: input.relation_id } : {},
|
|
1352
|
+
endpoint_0_entity_id: input.output_entity_id,
|
|
1353
|
+
endpoint_1_entity_id: input.script_entity_id,
|
|
1354
|
+
metadata: {},
|
|
1355
|
+
...input.trace !== void 0 ? { trace: input.trace } : {}
|
|
1356
|
+
}, "audio-script-render");
|
|
1357
|
+
const [output, script] = this.refsFor(relation);
|
|
1358
|
+
new BiRelationIndex().linkAudioScriptRender({
|
|
1359
|
+
relationId: createRelationId(relation.relation_id),
|
|
1360
|
+
output,
|
|
1361
|
+
script,
|
|
1362
|
+
trace: relation.trace
|
|
1363
|
+
});
|
|
1364
|
+
this.record({
|
|
1365
|
+
kind: "link-relation",
|
|
1366
|
+
relation
|
|
1367
|
+
});
|
|
1368
|
+
return relation.relation_id;
|
|
1369
|
+
}
|
|
1370
|
+
unlinkRelation(input) {
|
|
1371
|
+
assertTrimmed(input.relation_id, "relation_id");
|
|
1372
|
+
this.record({
|
|
1373
|
+
kind: "unlink-relation",
|
|
1374
|
+
relation_id: input.relation_id
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1076
1377
|
relationFromInput(input, relationKind) {
|
|
1077
1378
|
const relationId = input.relation_id ?? this.idFactory("relation");
|
|
1078
1379
|
assertTrimmed(relationId, "relation_id");
|
|
@@ -1105,13 +1406,48 @@ var EntitySandbox = class {
|
|
|
1105
1406
|
this.onCommand?.(clone(command));
|
|
1106
1407
|
}
|
|
1107
1408
|
apply(command, enforceIdentity) {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1409
|
+
switch (command.kind) {
|
|
1410
|
+
case "create-entity": {
|
|
1411
|
+
if (enforceIdentity && this.state.entities.some((entity) => entity.entity_id === command.entity.entity_id)) throw new Error(`Entity id "${command.entity.entity_id}" already exists`);
|
|
1412
|
+
const original = this.original.entities.find((entity) => entity.entity_id === command.entity.entity_id);
|
|
1413
|
+
if (enforceIdentity && original != null && original.entity_kind !== command.entity.entity_kind) throw new Error(`Entity id "${command.entity.entity_id}" was originally kind "${original.entity_kind}" and cannot be recreated as "${command.entity.entity_kind}"`);
|
|
1414
|
+
this.state.entities.push(clone(command.entity));
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
case "update-entity": {
|
|
1418
|
+
const index = this.state.entities.findIndex((entity) => entity.entity_id === command.entity_id);
|
|
1419
|
+
if (index < 0) throw new Error(`Entity id "${command.entity_id}" does not exist`);
|
|
1420
|
+
const current = this.state.entities[index];
|
|
1421
|
+
if (current == null) throw new Error(`Entity id "${command.entity_id}" does not exist`);
|
|
1422
|
+
this.state.entities[index] = {
|
|
1423
|
+
...current,
|
|
1424
|
+
payload: clone(command.payload)
|
|
1425
|
+
};
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
case "delete-entity": {
|
|
1429
|
+
const index = this.state.entities.findIndex((entity) => entity.entity_id === command.entity_id);
|
|
1430
|
+
if (index < 0) throw new Error(`Entity id "${command.entity_id}" does not exist`);
|
|
1431
|
+
const incidentRelationIds = this.state.relations.filter((relation) => relation.endpoint_0_entity_id === command.entity_id || relation.endpoint_1_entity_id === command.entity_id).map((relation) => relation.relation_id).sort();
|
|
1432
|
+
if (incidentRelationIds.length > 0) throw new Error(`Entity id "${command.entity_id}" still has incident Relation id(s): ${incidentRelationIds.join(", ")}`);
|
|
1433
|
+
this.state.entities.splice(index, 1);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
case "link-relation":
|
|
1437
|
+
if (enforceIdentity && this.state.relations.some((relation) => relation.relation_id === command.relation.relation_id)) throw new Error(`Relation id "${command.relation.relation_id}" already exists`);
|
|
1438
|
+
if (enforceIdentity) {
|
|
1439
|
+
const original = this.original.relations.find((relation) => relation.relation_id === command.relation.relation_id);
|
|
1440
|
+
if (original != null && (original.relation_kind !== command.relation.relation_kind || original.endpoint_0_entity_id !== command.relation.endpoint_0_entity_id || original.endpoint_1_entity_id !== command.relation.endpoint_1_entity_id)) throw new Error(`Relation id "${command.relation.relation_id}" cannot change its kind or persisted endpoint positions`);
|
|
1441
|
+
}
|
|
1442
|
+
this.state.relations.push(clone(command.relation));
|
|
1443
|
+
return;
|
|
1444
|
+
case "unlink-relation": {
|
|
1445
|
+
const index = this.state.relations.findIndex((relation) => relation.relation_id === command.relation_id);
|
|
1446
|
+
if (index < 0) throw new Error(`Relation id "${command.relation_id}" does not exist`);
|
|
1447
|
+
this.state.relations.splice(index, 1);
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1112
1450
|
}
|
|
1113
|
-
if (enforceIdentity && this.state.relations.some((relation) => relation.relation_id === command.relation.relation_id)) throw new Error(`Relation id "${command.relation.relation_id}" already exists`);
|
|
1114
|
-
this.state.relations.push(clone(command.relation));
|
|
1115
1451
|
}
|
|
1116
1452
|
};
|
|
1117
1453
|
function isImportedMemotaAsset(payload, assetId) {
|
|
@@ -1230,6 +1566,10 @@ var EditSandboxSession = class {
|
|
|
1230
1566
|
entity_base_revision: entityPlan.base_revision,
|
|
1231
1567
|
entity_commands: entityPlan.commands,
|
|
1232
1568
|
...planKind === "entities" ? { entity_rows: entityPlan.rows } : {},
|
|
1569
|
+
...planKind === "entities" ? {
|
|
1570
|
+
deleted_entity_ids: entityPlan.deleted_entity_ids,
|
|
1571
|
+
deleted_relation_ids: entityPlan.deleted_relation_ids
|
|
1572
|
+
} : {},
|
|
1233
1573
|
preview: planKind === "entities" ? this.entitySandbox.renderPreview() : renderPreview(this.current.adapter.snapshot(), this.entries),
|
|
1234
1574
|
logs: this.logs.slice()
|
|
1235
1575
|
};
|
|
@@ -1496,6 +1836,6 @@ function replayJournalSync(adapter, journal) {
|
|
|
1496
1836
|
}
|
|
1497
1837
|
}
|
|
1498
1838
|
//#endregion
|
|
1499
|
-
export {
|
|
1839
|
+
export { collectAffectedPartIds as a, createRelationId as i, EntitySandbox as n, renderPreview as o, createEntityId as r, renderCompactProjection as s, EditSandboxSession as t };
|
|
1500
1840
|
|
|
1501
|
-
//# sourceMappingURL=script-session-
|
|
1841
|
+
//# sourceMappingURL=script-session-CHyIUBkO.mjs.map
|