@mengine/medeo-tool 2.0.1-alpha.11 → 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.
- package/dist/{entity-contract-Cf3AiSe7.d.mts → entity-contract-Dy0cozPt.d.mts} +15 -3
- package/dist/{entity-sandbox-BTR2cRl1.mjs → entity-sandbox-CulJBYrI.mjs} +62 -31
- package/dist/entity-sandbox-CulJBYrI.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +23 -6
- package/dist/index.mjs.map +1 -1
- package/dist/sandbox-api.d.mts +12 -0
- package/dist/worker-entry.d.mts +1 -1
- package/dist/worker-entry.mjs +33 -7
- package/dist/worker-entry.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/entity-sandbox-BTR2cRl1.mjs.map +0 -1
|
@@ -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. */
|
|
@@ -147,6 +147,18 @@ type CreateEntityInput = { [K in KnownEntityKind]: {
|
|
|
147
147
|
entity_id?: string;
|
|
148
148
|
entity_kind: K;
|
|
149
149
|
payload: StoredEntityPayload<K>;
|
|
150
|
+
/**
|
|
151
|
+
* The stored resource this entity is made from.
|
|
152
|
+
*
|
|
153
|
+
* Naming it here is the only way to say where an entity's bytes come from:
|
|
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 `Asset` — video,
|
|
156
|
+
* audio, image, caption, audio-script, phonetic-script — accepts one.
|
|
157
|
+
*/
|
|
158
|
+
asset?: {
|
|
159
|
+
system: 'memota' | 'memota-speech';
|
|
160
|
+
key: string;
|
|
161
|
+
};
|
|
150
162
|
} }[KnownEntityKind];
|
|
151
163
|
/** Patch supplied fields on the assembled entity; omitted fields remain unchanged. */
|
|
152
164
|
interface UpdateEntityInput {
|
|
@@ -240,4 +252,4 @@ interface BusinessRelationFacade {
|
|
|
240
252
|
}
|
|
241
253
|
//#endregion
|
|
242
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 };
|
|
243
|
-
//# sourceMappingURL=entity-contract-
|
|
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
|
-
*
|
|
19
|
-
*
|
|
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
|
-
"
|
|
37
|
+
"Asset"
|
|
37
38
|
]),
|
|
38
39
|
audio: Object.freeze([
|
|
39
40
|
"Sequence",
|
|
40
41
|
"Audible",
|
|
41
|
-
"
|
|
42
|
+
"Asset"
|
|
42
43
|
]),
|
|
43
44
|
image: Object.freeze([
|
|
44
45
|
"Sequence",
|
|
45
46
|
"Visual",
|
|
46
|
-
"
|
|
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", "
|
|
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
|
-
"
|
|
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 `
|
|
1025
|
-
*
|
|
1026
|
-
*
|
|
1027
|
-
*
|
|
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
|
|
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], "
|
|
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,7 +1493,17 @@ 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
|
|
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))
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
/** Only a kind that declares Asset can be bound to stored bytes. */
|
|
1500
|
+
function assetLocator(value, entityKind) {
|
|
1501
|
+
if (!declaresKind(entityKind, "Asset")) throw new Error(`Entity kind "${entityKind}" cannot be made from an Asset`);
|
|
1502
|
+
if (!isJsonObject(value) || value.system !== "memota" && value.system !== "memota-speech") throw new Error("asset.system must be \"memota\" or \"memota-speech\"");
|
|
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");
|
|
1504
|
+
return {
|
|
1505
|
+
system: value.system,
|
|
1506
|
+
key: value.key
|
|
1483
1507
|
};
|
|
1484
1508
|
}
|
|
1485
1509
|
/** Keep infrastructure rows available to assembly without exposing their management to scripts. */
|
|
@@ -1494,7 +1518,7 @@ function businessFacades(entities, relations, onDirectScriptWrite) {
|
|
|
1494
1518
|
for (const id of payload.baseEntityIds) if (typeof id === "string") assertBusiness(id);
|
|
1495
1519
|
}
|
|
1496
1520
|
};
|
|
1497
|
-
const visibleRelation = (relation) => relation.relation_kind
|
|
1521
|
+
const visibleRelation = (relation) => !assetBindingKinds.has(relation.relation_kind) && !isAsset(relation.endpoint_0_entity_id) && !isAsset(relation.endpoint_1_entity_id);
|
|
1498
1522
|
return {
|
|
1499
1523
|
entities: {
|
|
1500
1524
|
list: () => entities.list().filter((entity) => entity.entity_kind !== "asset").map(businessEntity),
|
|
@@ -1506,7 +1530,14 @@ function businessFacades(entities, relations, onDirectScriptWrite) {
|
|
|
1506
1530
|
if (input.entity_kind === "asset") throw new Error("Asset entities are managed by host assembly");
|
|
1507
1531
|
assertBusinessPayload(input.payload);
|
|
1508
1532
|
if (input.entity_id && entities.get(input.entity_id)) throw new Error(`Entity id ${input.entity_id} already exists`);
|
|
1509
|
-
const
|
|
1533
|
+
const asset = input.asset === void 0 ? void 0 : assetLocator(input.asset, input.entity_kind);
|
|
1534
|
+
const { asset: _named, ...row } = input;
|
|
1535
|
+
const id = entities.create(row);
|
|
1536
|
+
if (asset !== void 0) relations.link({
|
|
1537
|
+
relation_kind: "from-asset",
|
|
1538
|
+
endpoint_0_entity_id: id,
|
|
1539
|
+
endpoint_1_entity_id: entities.ensureAsset(asset)
|
|
1540
|
+
});
|
|
1510
1541
|
if (input.entity_kind === "audio-script") onDirectScriptWrite?.(id);
|
|
1511
1542
|
return id;
|
|
1512
1543
|
},
|
|
@@ -1541,7 +1572,7 @@ function businessFacades(entities, relations, onDirectScriptWrite) {
|
|
|
1541
1572
|
list: () => relations.list().filter(visibleRelation),
|
|
1542
1573
|
of: (id, kind) => relations.of(id, kind).filter(visibleRelation),
|
|
1543
1574
|
link: (input) => {
|
|
1544
|
-
if (input.relation_kind
|
|
1575
|
+
if (assetBindingKinds.has(input.relation_kind)) throw new Error("Asset bindings are managed by host assembly");
|
|
1545
1576
|
assertBusiness(input.endpoint_0_entity_id);
|
|
1546
1577
|
assertBusiness(input.endpoint_1_entity_id);
|
|
1547
1578
|
return relations.link(input);
|
|
@@ -1630,7 +1661,7 @@ var EntitySandbox = class {
|
|
|
1630
1661
|
decodeEntityRelationRows(rows, numericMarkerComparators);
|
|
1631
1662
|
if (this.state.revision !== 0 || rows.entities.length !== 0 || this.commands.length !== 0) {
|
|
1632
1663
|
assertCanonicalEditorResources(rows);
|
|
1633
|
-
readDocumentAudioScript({
|
|
1664
|
+
if (this.state.audioScriptEntityId !== null) readDocumentAudioScript({
|
|
1634
1665
|
rows,
|
|
1635
1666
|
audioScriptEntityId: this.state.audioScriptEntityId
|
|
1636
1667
|
});
|
|
@@ -1896,7 +1927,7 @@ var EntitySandbox = class {
|
|
|
1896
1927
|
}
|
|
1897
1928
|
for (const row of rows.relations) {
|
|
1898
1929
|
if (this.state.relations.some((relation) => relation.relation_id === row.relationId)) continue;
|
|
1899
|
-
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}`);
|
|
1900
1931
|
this.link({
|
|
1901
1932
|
relation_id: row.relationId,
|
|
1902
1933
|
relation_kind: row.relationKind,
|
|
@@ -2202,4 +2233,4 @@ const numericMarkerComparators = { compareMarkerPoints: (_marker, _range, left,
|
|
|
2202
2233
|
//#endregion
|
|
2203
2234
|
export { createEntityId as a, businessState as i, toDslRows as n, createRelationId as o, businessFacades as r, isMediaAssetVariantKind as s, EntitySandbox as t };
|
|
2204
2235
|
|
|
2205
|
-
//# sourceMappingURL=entity-sandbox-
|
|
2236
|
+
//# sourceMappingURL=entity-sandbox-CulJBYrI.mjs.map
|