@mengine/medeo-tool 2.0.1-alpha.12 → 2.0.1-alpha.13

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.
@@ -9,7 +9,7 @@ interface JsonObject {
9
9
  type KnownEntityKind = 'axvideo' | 'timeline' | 'track' | 'clip' | 'asset' | 'video' | 'audio' | 'voice' | 'image' | 'sequence-marker' | 'viewport' | 'audio-script' | 'phonetic-script' | 'caption';
10
10
  /** Asset identity, either an old physical-only row or a directly composed media variant. */
11
11
  type ResourceEntityKind = 'image' | 'video' | 'audio';
12
- type KnownRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'from-asset' | 'generated' | 'caption-alignment' | 'clip-anchor' | 'phonetic-script-render' | 'voice-timbre' | 'audio-script-source' | 'audio-script-marker';
12
+ type KnownRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'from-asset' | 'to-asset' | 'generated' | 'caption-alignment' | 'clip-anchor' | 'phonetic-script-render' | 'voice-timbre' | 'audio-script-source' | 'audio-script-marker';
13
13
  type AuthorableRelationKind = KnownRelationKind;
14
14
  /**
15
15
  * Content states how long it runs and nothing else: where it is taken from and
@@ -58,7 +58,7 @@ interface EntityPayloadByKind {
58
58
  role?: string;
59
59
  };
60
60
  clip: JsonObject;
61
- /** Where the bytes live. Reached from other entities by from-asset. */
61
+ /** Where the bytes live. Reached from other entities by from-asset or to-asset. */
62
62
  asset: AssetPayload;
63
63
  video: OwnDurationPayload;
64
64
  /** Every playable sound, including video original audio and synthesized voiceovers. */
@@ -152,8 +152,8 @@ type CreateEntityInput = { [K in KnownEntityKind]: {
152
152
  *
153
153
  * Naming it here is the only way to say where an entity's bytes come from:
154
154
  * the Asset row and the `from-asset` Relation that reaches it are assembled
155
- * by the host, never authored. Only a kind that declares `FromAsset` —
156
- * video, audio, image, caption, audio-script — accepts one.
155
+ * by the host, never authored. Only a kind that declares `Asset` — video,
156
+ * audio, image, caption, audio-script, phonetic-script — accepts one.
157
157
  */
158
158
  asset?: {
159
159
  system: 'memota' | 'memota-speech';
@@ -252,4 +252,4 @@ interface BusinessRelationFacade {
252
252
  }
253
253
  //#endregion
254
254
  export { UpdateEntityInput as C, UnlinkRelationInput as S, LinkRelationInput as _, DeleteEntityInput as a, SandboxEntity as b, EntitySandboxCheckpoint as c, JsonObject as d, JsonPrimitive as f, LinkGeneratedRelationInput as g, KnownRelationKind as h, CreateEntityInput as i, EntityStoreSnapshot as l, KnownEntityKind as m, BusinessEntityFacade as n, EntityCommand as o, JsonValue as p, BusinessRelationFacade as r, EntityPlanState as s, AuthorableRelationKind as t, EntityUpdateInput as u, RelationUpdateInput as v, SandboxRelation as x, ResourceEntityKind as y };
255
- //# sourceMappingURL=entity-contract-DNIIwpn1.d.mts.map
255
+ //# sourceMappingURL=entity-contract-Dy0cozPt.d.mts.map
@@ -15,8 +15,9 @@ import { applyFieldChanges, assertCanonicalEditorResources, assertFieldChanges,
15
15
  * `Voice` declares no `Sequence` — it is a timbre identity, not playable
16
16
  * content; a rendered voiceover is an `audio` entity.
17
17
  *
18
- * No declaration says anything about resources: whether an entity has one, and
19
- * which, is the `from-asset` Relation's answer alone.
18
+ * `Asset` says only that an entity of that kind may be bound to stored bytes,
19
+ * and asks nothing of its payload. Which bytes, and which way the content
20
+ * flowed, is the `from-asset` / `to-asset` Relation's answer alone.
20
21
  */
21
22
  const ENTITY_KINDS = Object.freeze({
22
23
  axvideo: Object.freeze([
@@ -33,27 +34,27 @@ const ENTITY_KINDS = Object.freeze({
33
34
  "Sequence",
34
35
  "Visual",
35
36
  "Audible",
36
- "FromAsset"
37
+ "Asset"
37
38
  ]),
38
39
  audio: Object.freeze([
39
40
  "Sequence",
40
41
  "Audible",
41
- "FromAsset"
42
+ "Asset"
42
43
  ]),
43
44
  image: Object.freeze([
44
45
  "Sequence",
45
46
  "Visual",
46
- "FromAsset"
47
+ "Asset"
47
48
  ]),
48
49
  voice: Object.freeze(["Container", "IPBound"]),
49
50
  "sequence-marker": Object.freeze([]),
50
51
  viewport: Object.freeze([]),
51
- "audio-script": Object.freeze(["SegmentText", "FromAsset"]),
52
- "phonetic-script": Object.freeze(["SegmentText"]),
52
+ "audio-script": Object.freeze(["SegmentText", "Asset"]),
53
+ "phonetic-script": Object.freeze(["SegmentText", "Asset"]),
53
54
  caption: Object.freeze([
54
55
  "SegmentText",
55
56
  "Sequence",
56
- "FromAsset"
57
+ "Asset"
57
58
  ])
58
59
  });
59
60
  /** What a known kind declares; extension kinds declare nothing through this table. */
@@ -347,15 +348,6 @@ function entityToRow(entity) {
347
348
  //#endregion
348
349
  //#region ../medeo-dsl/src/invariants.ts
349
350
  /** An entity is made from at most one Asset; two would leave its bytes ambiguous. */
350
- function validateAssetSource(entity, index) {
351
- if (entity.current().entityKind === "asset") return [];
352
- if ([...index.relationsOf(entity)].filter((relation) => relation.kind === "from-asset").length <= 1) return [];
353
- return [{
354
- code: "asset_source_cardinality",
355
- entityId: entity.entityId,
356
- message: `Entity "${entity.entityId}" is made from more than one Asset`
357
- }];
358
- }
359
351
  /** Validates one complete set of entities and its authoritative Relation rows. */
360
352
  function validateEntityRelationSet(entityRefs, index, options) {
361
353
  const { entities, issues } = collectRelatedEntities(entityRefs, index);
@@ -377,7 +369,6 @@ function validateEntityRelationSet(entityRefs, index, options) {
377
369
  const current = entity.current();
378
370
  const entityIssues = validateEntity(entity, entityIds);
379
371
  issues.push(...entityIssues);
380
- issues.push(...validateAssetSource(entity, index));
381
372
  if (entityIssues.some((issue) => issue.code === "invalid_entity_payload")) continue;
382
373
  if (current.entityKind === "sequence-marker") {
383
374
  const marker = entity;
@@ -1021,10 +1012,10 @@ const markerContentRelationSpec = Object.freeze({
1021
1012
  /**
1022
1013
  * `from-asset(entity, asset)` — the entity was made from that stored resource.
1023
1014
  *
1024
- * Eligibility is declared: the non-Asset endpoint must carry the `FromAsset`
1025
- * kind, which says an entity of that kind can be made from stored bytes and
1026
- * asks nothing of its payload. The bytes stay named in exactly one place, so
1027
- * no entity copies the locator into its own row and one Asset can back several
1015
+ * Eligibility is declared: the non-Asset endpoint must carry the `Asset` kind,
1016
+ * which says an entity of that kind can be bound to stored bytes and asks
1017
+ * nothing of its payload. The bytes stay named in exactly one place, so no
1018
+ * entity copies the locator into its own row and one Asset can back several
1028
1019
  * entities.
1029
1020
  */
1030
1021
  const fromAssetRelationSpec = Object.freeze({
@@ -1032,6 +1023,26 @@ const fromAssetRelationSpec = Object.freeze({
1032
1023
  validateEndpoints: (endpoints) => hasExactlyOneAsset(endpoints),
1033
1024
  validateMetadata: isAssetBinding
1034
1025
  });
1026
+ /**
1027
+ * `to-asset(entity, asset)` — the entity's content was written out to that
1028
+ * stored resource.
1029
+ *
1030
+ * The mirror of `from-asset`, and deliberately a separate relation kind rather
1031
+ * than a second declared kind: eligibility is the same either way, but which
1032
+ * way the content flowed is what says whether an entity may still be edited.
1033
+ * An entity reached by `from-asset` was produced from bytes it does not own,
1034
+ * so changing it would contradict them. An entity with a `to-asset` owns its
1035
+ * content and the resource is a snapshot of it, which is what goes stale and
1036
+ * gets written again. A transcript that is later edited carries both at once,
1037
+ * and only the distinct relation kinds keep them apart.
1038
+ *
1039
+ * Writing a snapshot is host assembly; nothing a script authors creates one.
1040
+ */
1041
+ const toAssetRelationSpec = Object.freeze({
1042
+ kind: "to-asset",
1043
+ validateEndpoints: (endpoints) => hasExactlyOneAsset(endpoints),
1044
+ validateMetadata: isAssetBinding
1045
+ });
1035
1046
  /** `generated(output, input)` means endpoint 0 was generated from endpoint 1. */
1036
1047
  const generatedRelationSpec = Object.freeze({
1037
1048
  kind: "generated",
@@ -1094,6 +1105,7 @@ const builtInRelationSpecs = Object.freeze([
1094
1105
  axVideoMarkerRelationSpec,
1095
1106
  markerTimelineRelationSpec,
1096
1107
  fromAssetRelationSpec,
1108
+ toAssetRelationSpec,
1097
1109
  generatedRelationSpec,
1098
1110
  captionAlignmentRelationSpec,
1099
1111
  clipAnchorRelationSpec,
@@ -1122,12 +1134,12 @@ function hasMarkerAndSequence(endpoints) {
1122
1134
  const second = endpoints[1].current();
1123
1135
  return first.entityKind === "sequence-marker" && hasSequence(second) || second.entityKind === "sequence-marker" && hasSequence(first);
1124
1136
  }
1125
- /** Exactly one end is the Asset, so the other end is unambiguously the maker. */
1137
+ /** Exactly one end is the Asset, so the other end is unambiguously the bound entity. */
1126
1138
  function hasExactlyOneAsset(endpoints) {
1127
1139
  const kinds = endpoints.map((endpoint) => endpoint.current().entityKind);
1128
1140
  const assets = kinds.map((kind) => kind === "asset");
1129
1141
  if (assets[0] === assets[1]) return false;
1130
- return declaresKind(assets[0] ? kinds[1] : kinds[0], "FromAsset");
1142
+ return declaresKind(assets[0] ? kinds[1] : kinds[0], "Asset");
1131
1143
  }
1132
1144
  function isGeneratedMedia(entity) {
1133
1145
  return entity.entityKind === "video" || entity.entityKind === "image" || entity.entityKind === "audio";
@@ -1467,6 +1479,8 @@ function errorMessage(error) {
1467
1479
  //#region src/sandbox/business-facades.ts
1468
1480
  /** Resource IDs are entity data; physical storage paths remain host-owned. */
1469
1481
  const infrastructureFields = new Set(["storageKey", "assetContentHash"]);
1482
+ /** Both directions of an Asset binding are host assembly, whoever asks. */
1483
+ const assetBindingKinds = new Set(["from-asset", "to-asset"]);
1470
1484
  function businessEntity(entity) {
1471
1485
  return {
1472
1486
  ...entity,
@@ -1479,12 +1493,12 @@ function businessState(state) {
1479
1493
  revision: state.revision,
1480
1494
  audioScriptEntityId: state.audioScriptEntityId,
1481
1495
  entities: state.entities.filter((row) => !assets.has(row.entity_id)).map(businessEntity),
1482
- relations: state.relations.filter((row) => row.relation_kind !== "from-asset" && !assets.has(row.endpoint_0_entity_id) && !assets.has(row.endpoint_1_entity_id))
1496
+ relations: state.relations.filter((row) => !assetBindingKinds.has(row.relation_kind) && !assets.has(row.endpoint_0_entity_id) && !assets.has(row.endpoint_1_entity_id))
1483
1497
  };
1484
1498
  }
1485
- /** Only a kind that declares FromAsset can be made from stored bytes. */
1499
+ /** Only a kind that declares Asset can be bound to stored bytes. */
1486
1500
  function assetLocator(value, entityKind) {
1487
- if (!declaresKind(entityKind, "FromAsset")) throw new Error(`Entity kind "${entityKind}" cannot be made from an Asset`);
1501
+ if (!declaresKind(entityKind, "Asset")) throw new Error(`Entity kind "${entityKind}" cannot be made from an Asset`);
1488
1502
  if (!isJsonObject(value) || value.system !== "memota" && value.system !== "memota-speech") throw new Error("asset.system must be \"memota\" or \"memota-speech\"");
1489
1503
  if (typeof value.key !== "string" || value.key.trim() !== value.key || value.key === "") throw new Error("asset.key must be a trimmed, non-empty resource id");
1490
1504
  return {
@@ -1504,7 +1518,7 @@ function businessFacades(entities, relations, onDirectScriptWrite) {
1504
1518
  for (const id of payload.baseEntityIds) if (typeof id === "string") assertBusiness(id);
1505
1519
  }
1506
1520
  };
1507
- const visibleRelation = (relation) => relation.relation_kind !== "from-asset" && !isAsset(relation.endpoint_0_entity_id) && !isAsset(relation.endpoint_1_entity_id);
1521
+ const visibleRelation = (relation) => !assetBindingKinds.has(relation.relation_kind) && !isAsset(relation.endpoint_0_entity_id) && !isAsset(relation.endpoint_1_entity_id);
1508
1522
  return {
1509
1523
  entities: {
1510
1524
  list: () => entities.list().filter((entity) => entity.entity_kind !== "asset").map(businessEntity),
@@ -1558,7 +1572,7 @@ function businessFacades(entities, relations, onDirectScriptWrite) {
1558
1572
  list: () => relations.list().filter(visibleRelation),
1559
1573
  of: (id, kind) => relations.of(id, kind).filter(visibleRelation),
1560
1574
  link: (input) => {
1561
- if (input.relation_kind === "from-asset") throw new Error("Asset bindings are managed by host assembly");
1575
+ if (assetBindingKinds.has(input.relation_kind)) throw new Error("Asset bindings are managed by host assembly");
1562
1576
  assertBusiness(input.endpoint_0_entity_id);
1563
1577
  assertBusiness(input.endpoint_1_entity_id);
1564
1578
  return relations.link(input);
@@ -1647,7 +1661,7 @@ var EntitySandbox = class {
1647
1661
  decodeEntityRelationRows(rows, numericMarkerComparators);
1648
1662
  if (this.state.revision !== 0 || rows.entities.length !== 0 || this.commands.length !== 0) {
1649
1663
  assertCanonicalEditorResources(rows);
1650
- readDocumentAudioScript({
1664
+ if (this.state.audioScriptEntityId !== null) readDocumentAudioScript({
1651
1665
  rows,
1652
1666
  audioScriptEntityId: this.state.audioScriptEntityId
1653
1667
  });
@@ -1913,7 +1927,7 @@ var EntitySandbox = class {
1913
1927
  }
1914
1928
  for (const row of rows.relations) {
1915
1929
  if (this.state.relations.some((relation) => relation.relation_id === row.relationId)) continue;
1916
- if (row.relationKind !== "timeline-track" && row.relationKind !== "voice-timbre" && row.relationKind !== "from-asset") throw new Error(`Unexpected resource Relation ${row.relationKind}`);
1930
+ if (row.relationKind !== "timeline-track" && row.relationKind !== "voice-timbre" && row.relationKind !== "from-asset" && row.relationKind !== "to-asset") throw new Error(`Unexpected resource Relation ${row.relationKind}`);
1917
1931
  this.link({
1918
1932
  relation_id: row.relationId,
1919
1933
  relation_kind: row.relationKind,
@@ -2219,4 +2233,4 @@ const numericMarkerComparators = { compareMarkerPoints: (_marker, _range, left,
2219
2233
  //#endregion
2220
2234
  export { createEntityId as a, businessState as i, toDslRows as n, createRelationId as o, businessFacades as r, isMediaAssetVariantKind as s, EntitySandbox as t };
2221
2235
 
2222
- //# sourceMappingURL=entity-sandbox-DpIkxcei.mjs.map
2236
+ //# sourceMappingURL=entity-sandbox-CulJBYrI.mjs.map