@mengine/medeo-tool 1.4.1-alpha.5 → 2.0.1-alpha.10
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 +9 -0
- package/dist/{entity-contract-DlmUSouB.d.mts → entity-contract-Cf3AiSe7.d.mts} +45 -43
- package/dist/{entity-sandbox-TaUVT3on.mjs → entity-sandbox-BTR2cRl1.mjs} +255 -139
- package/dist/entity-sandbox-BTR2cRl1.mjs.map +1 -0
- package/dist/index.d.mts +27 -10
- package/dist/index.mjs +104 -116
- package/dist/index.mjs.map +1 -1
- package/dist/sandbox-api.d.mts +42 -44
- package/dist/worker-entry.d.mts +2 -1
- package/dist/worker-entry.mjs +101 -108
- package/dist/worker-entry.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/entity-sandbox-TaUVT3on.mjs.map +0 -1
package/dist/sandbox-api.d.mts
CHANGED
|
@@ -1,24 +1,5 @@
|
|
|
1
1
|
//#region src/sandbox/generated/entity-edit-sandbox-api.d.ts
|
|
2
2
|
/** @generated by gen:sandbox-dts. Entity-native editor contract; DO NOT EDIT. */
|
|
3
|
-
interface BoundedDerivedSequencePayload extends JsonObject {
|
|
4
|
-
extent: {
|
|
5
|
-
kind: 'bounded';
|
|
6
|
-
start: number;
|
|
7
|
-
end: number;
|
|
8
|
-
};
|
|
9
|
-
sampling: 'derived';
|
|
10
|
-
coordinateSpace: JsonValue;
|
|
11
|
-
}
|
|
12
|
-
interface BoundedNativeSequencePayload extends JsonObject {
|
|
13
|
-
/** Factual coordinates from recalled media metadata; never invent an end/duration. */
|
|
14
|
-
extent: {
|
|
15
|
-
kind: 'bounded';
|
|
16
|
-
start: number;
|
|
17
|
-
end: number;
|
|
18
|
-
};
|
|
19
|
-
sampling: 'native';
|
|
20
|
-
coordinateSpace: JsonValue;
|
|
21
|
-
}
|
|
22
3
|
/** Business editing surface. Reads are assembled snapshots; writes preserve native operation intent. */
|
|
23
4
|
interface BusinessEntityFacade {
|
|
24
5
|
list(): SandboxEntity[];
|
|
@@ -59,17 +40,20 @@ interface EntityAssetContent {
|
|
|
59
40
|
content: JsonValue;
|
|
60
41
|
}
|
|
61
42
|
interface EntityPayloadByKind {
|
|
62
|
-
|
|
43
|
+
/** Its Timeline decides how long it runs, so it stores no length. */
|
|
44
|
+
axvideo: JsonObject;
|
|
63
45
|
timeline: JsonObject;
|
|
64
46
|
track: JsonObject & {
|
|
65
47
|
hidden?: boolean;
|
|
66
48
|
role?: string;
|
|
67
49
|
};
|
|
68
50
|
clip: JsonObject;
|
|
69
|
-
video:
|
|
70
|
-
audio
|
|
71
|
-
|
|
72
|
-
|
|
51
|
+
video: OwnDurationPayload;
|
|
52
|
+
/** Every playable sound, including video original audio and synthesized voiceovers. */
|
|
53
|
+
audio: OwnDurationPayload;
|
|
54
|
+
/** A timbre identity, never playable content; the rendered take is an `audio` entity. */
|
|
55
|
+
voice: VoiceIdentityPayload;
|
|
56
|
+
image: NoDurationPayload;
|
|
73
57
|
'sequence-marker': JsonObject & {
|
|
74
58
|
sourceRange: {
|
|
75
59
|
start: number;
|
|
@@ -103,7 +87,7 @@ interface EntityPayloadByKind {
|
|
|
103
87
|
phonemeScript?: string;
|
|
104
88
|
prosody?: JsonObject;
|
|
105
89
|
};
|
|
106
|
-
caption:
|
|
90
|
+
caption: OwnDurationPayload & {
|
|
107
91
|
baseEntityIds: string[];
|
|
108
92
|
selection: CaptionTextSelection;
|
|
109
93
|
style?: JsonObject;
|
|
@@ -160,7 +144,7 @@ interface JsonObject {
|
|
|
160
144
|
type JsonPrimitive = string | number | boolean | null;
|
|
161
145
|
type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
|
|
162
146
|
type KnownEntityKind = 'axvideo' | 'timeline' | 'track' | 'clip' | 'video' | 'audio' | 'voice' | 'image' | 'sequence-marker' | 'viewport' | 'audio-script' | 'phonetic-script' | 'caption';
|
|
163
|
-
type KnownRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'generated' | 'caption-alignment' | 'clip-anchor' | 'phonetic-script-render' | 'audio-script-source' | 'audio-script-marker';
|
|
147
|
+
type KnownRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'generated' | 'caption-alignment' | 'clip-anchor' | 'phonetic-script-render' | 'voice-timbre' | 'audio-script-source' | 'audio-script-marker';
|
|
164
148
|
interface LinkRelationBase {
|
|
165
149
|
relation_id?: string;
|
|
166
150
|
endpoint_0_entity_id: string;
|
|
@@ -171,12 +155,18 @@ interface LinkRelationInput extends LinkRelationBase {
|
|
|
171
155
|
relation_kind: KnownRelationKind;
|
|
172
156
|
metadata?: JsonObject;
|
|
173
157
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
158
|
+
/** A still frame runs for as long as its use asks, so it states no length. */
|
|
159
|
+
interface NoDurationPayload extends JsonObject {
|
|
160
|
+
durationMs: null;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Content states how long it runs and nothing else: where it is taken from and
|
|
164
|
+
* where it lands both belong to its Sequence Marker.
|
|
165
|
+
*/
|
|
166
|
+
interface OwnDurationPayload extends JsonObject {
|
|
167
|
+
/** Factual whole milliseconds from recalled media metadata; never invented. */
|
|
168
|
+
durationMs: number;
|
|
169
|
+
}
|
|
180
170
|
interface RelationUpdateInput {
|
|
181
171
|
relation_id: string;
|
|
182
172
|
changes: FieldChange[];
|
|
@@ -199,18 +189,18 @@ type ScriptTextSegment = JsonObject & {
|
|
|
199
189
|
text: string;
|
|
200
190
|
language?: string;
|
|
201
191
|
};
|
|
202
|
-
/**
|
|
203
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Stored own fields; a variant may obtain required content fields from its
|
|
194
|
+
* declared bases.
|
|
195
|
+
*
|
|
196
|
+
* Caption and Audio may also be declared resource-only: the ASR transcript and
|
|
197
|
+
* the synthesized voiceover are attached by the host, which then fills in the
|
|
198
|
+
* remaining factual fields.
|
|
199
|
+
*/
|
|
200
|
+
type StoredEntityPayload<K extends KnownEntityKind> = EntityPayloadByKind[K] // Resource-only: the host fills the factual fields after reading the Asset.
|
|
201
|
+
| (K extends 'caption' | 'audio' ? Record<string, never> : never) | (JsonObject & Partial<EntityPayloadByKind[K]> & {
|
|
204
202
|
baseEntityIds: string[];
|
|
205
203
|
});
|
|
206
|
-
interface UnboundedConstantSequencePayload extends JsonObject {
|
|
207
|
-
extent: {
|
|
208
|
-
kind: 'unbounded';
|
|
209
|
-
start: number;
|
|
210
|
-
};
|
|
211
|
-
sampling: 'constant';
|
|
212
|
-
coordinateSpace: JsonValue;
|
|
213
|
-
}
|
|
214
204
|
interface UnlinkRelationInput {
|
|
215
205
|
relation_id: string;
|
|
216
206
|
}
|
|
@@ -219,9 +209,17 @@ interface UpdateEntityInput {
|
|
|
219
209
|
entity_id: string;
|
|
220
210
|
payload: JsonObject;
|
|
221
211
|
}
|
|
212
|
+
/** The voice-library timbre a synthesized Audio was rendered with. */
|
|
213
|
+
type VoiceIdentityPayload = JsonObject & {
|
|
214
|
+
voice: {
|
|
215
|
+
system: 'voice-library';
|
|
216
|
+
key: string;
|
|
217
|
+
name?: string;
|
|
218
|
+
};
|
|
219
|
+
};
|
|
222
220
|
declare const entities: BusinessEntityFacade;
|
|
223
221
|
declare const relations: BusinessRelationFacade;
|
|
224
|
-
/** Resolve this Entity's attached resource through the host; await before using an uninitialized Caption. */
|
|
222
|
+
/** Resolve this Entity's attached resource through the host; await before using an uninitialized Caption or Voice. */
|
|
225
223
|
declare function rgetAssetFromEntity(entityId: string): Promise<EntityAssetContent>;
|
|
226
224
|
declare function checkpoint(): EntitySandboxCheckpoint;
|
|
227
225
|
declare function rollbackTo(cp: EntitySandboxCheckpoint): void;
|
|
@@ -233,5 +231,5 @@ declare const console: {
|
|
|
233
231
|
error(...values: unknown[]): void;
|
|
234
232
|
};
|
|
235
233
|
//#endregion
|
|
236
|
-
export type {
|
|
234
|
+
export type { BusinessEntityFacade, BusinessRelationFacade, CaptionTextSelection, CreateEntityInput, DeleteEntityInput, EntityAssetContent, EntityPayloadByKind, EntitySandboxCheckpoint, EntityUpdateInput, FieldChange, FieldPath, JsonObject, JsonPrimitive, JsonValue, KnownEntityKind, KnownRelationKind, LinkRelationInput, NoDurationPayload, OwnDurationPayload, RelationUpdateInput, SandboxEntity, SandboxRelation, ScriptTextSegment, StoredEntityPayload, UnlinkRelationInput, UpdateEntityInput, VoiceIdentityPayload, checkpoint, console, entities, inputs, relations, rgetAssetFromEntity, rollbackTo };
|
|
237
235
|
//# sourceMappingURL=sandbox-api.d.mts.map
|
package/dist/worker-entry.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { l as EntityStoreSnapshot } from "./entity-contract-
|
|
1
|
+
import { l as EntityStoreSnapshot } from "./entity-contract-Cf3AiSe7.mjs";
|
|
2
2
|
import { VideoDocument } from "@mengine/medeo-client";
|
|
3
3
|
|
|
4
4
|
//#region src/sandbox/worker-entry.d.ts
|
|
@@ -10,6 +10,7 @@ import { VideoDocument } from "@mengine/medeo-client";
|
|
|
10
10
|
* host so hard timeout / OOM termination still preserves partial products.
|
|
11
11
|
*/
|
|
12
12
|
interface WorkerData {
|
|
13
|
+
docId: string;
|
|
13
14
|
document: VideoDocument;
|
|
14
15
|
script: string;
|
|
15
16
|
inputs?: Record<string, unknown>;
|
package/dist/worker-entry.mjs
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LoroEntityDocument, assertCanonicalEditorResources, assertMediaAssetWritePolicy, base64ToBytes, bytesToBase64, projectEntityTimeline } from "@mengine/medeo-client";
|
|
1
|
+
import { n as toDslRows, r as businessFacades, t as EntitySandbox } from "./entity-sandbox-BTR2cRl1.mjs";
|
|
2
|
+
import { LoroEntityDocument, assertCanonicalEditorResources, assertMediaAssetWritePolicy, audioScriptAssetContent, audioScriptAssetFields, audioScriptAssetOf, base64ToBytes, bytesToBase64, projectEntityTimeline } from "@mengine/medeo-client";
|
|
3
3
|
import { parentPort, workerData } from "node:worker_threads";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import vm from "node:vm";
|
|
6
6
|
//#region src/entity/entity-asset.ts
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Persisted voiceover Audio facts, resolved by the host rather than inferred
|
|
9
|
+
* from transcript timing.
|
|
10
|
+
*
|
|
11
|
+
* A returned voice-library descriptor is validated but never written here: the
|
|
12
|
+
* timbre is its own Voice entity linked by a `voice-timbre` Relation, so the
|
|
13
|
+
* rendered Audio row stays resource-only.
|
|
14
|
+
*/
|
|
15
|
+
function speechAssetFields(content) {
|
|
16
|
+
if (!content || typeof content !== "object" || Array.isArray(content) || typeof content.storageKey !== "string" || !content.storageKey.trim() || !Number.isSafeInteger(content.durationMs) || content.durationMs <= 0) throw new Error("Speech Asset requires factual storageKey and positive millisecond duration");
|
|
17
|
+
const voice = content.voice;
|
|
18
|
+
if (voice !== void 0 && (!voice || typeof voice !== "object" || Array.isArray(voice) || voice.system !== "voice-library" || typeof voice.key !== "string" || !voice.key.trim())) throw new Error("Speech Asset has an invalid voice-library identity");
|
|
19
|
+
return { durationMs: content.durationMs };
|
|
20
|
+
}
|
|
21
|
+
/** The storage path the speech resource reports, destined for its Asset. */
|
|
22
|
+
function speechAssetStorageKey(content) {
|
|
23
|
+
speechAssetFields(content);
|
|
24
|
+
return content.storageKey;
|
|
19
25
|
}
|
|
20
26
|
//#endregion
|
|
21
27
|
//#region src/sandbox/entity-script-session.ts
|
|
@@ -26,22 +32,22 @@ const TRUNCATE_MARK = "[truncated]";
|
|
|
26
32
|
const LOG_TRUNCATED = "[log truncated]";
|
|
27
33
|
/** Entity/relation script session; compilation retains the ordered operation journal. */
|
|
28
34
|
var EntityEditSandboxSession = class {
|
|
29
|
-
|
|
35
|
+
docId;
|
|
30
36
|
entitySandbox;
|
|
31
37
|
baseRows;
|
|
32
38
|
domainIdFactory;
|
|
33
39
|
logs = [];
|
|
34
40
|
onLog;
|
|
35
41
|
logBytes = 0;
|
|
36
|
-
|
|
42
|
+
directScriptWrites = /* @__PURE__ */ new Set();
|
|
37
43
|
logCapped = false;
|
|
38
44
|
entities;
|
|
39
45
|
relations;
|
|
40
46
|
console;
|
|
41
47
|
checkpoint;
|
|
42
48
|
rollbackTo;
|
|
43
|
-
constructor(
|
|
44
|
-
this.
|
|
49
|
+
constructor(options) {
|
|
50
|
+
this.docId = options?.docId ?? "";
|
|
45
51
|
this.baseRows = toDslRows(options?.entityState ?? {
|
|
46
52
|
revision: 0,
|
|
47
53
|
audioScriptEntityId: null,
|
|
@@ -58,20 +64,25 @@ var EntityEditSandboxSession = class {
|
|
|
58
64
|
onCommand: options?.onEntityCommand,
|
|
59
65
|
onTruncate: options?.onEntityTruncate
|
|
60
66
|
});
|
|
61
|
-
const business = businessFacades(this.entitySandbox.entities, this.entitySandbox.relations);
|
|
67
|
+
const business = businessFacades(this.entitySandbox.entities, this.entitySandbox.relations, (id) => this.directScriptWrites.add(id));
|
|
62
68
|
this.entities = business.entities;
|
|
63
69
|
this.relations = business.relations;
|
|
64
70
|
this.console = this.buildConsoleShim();
|
|
65
71
|
const checkpoints = /* @__PURE__ */ new Map();
|
|
66
72
|
this.checkpoint = () => {
|
|
67
73
|
const token = Object.freeze({});
|
|
68
|
-
checkpoints.set(token,
|
|
74
|
+
checkpoints.set(token, {
|
|
75
|
+
index: this.entitySandbox.commandCount,
|
|
76
|
+
scripts: new Set(this.directScriptWrites)
|
|
77
|
+
});
|
|
69
78
|
return token;
|
|
70
79
|
};
|
|
71
80
|
this.rollbackTo = (cp) => {
|
|
72
81
|
const index = checkpoints.get(cp);
|
|
73
82
|
if (index === void 0) throw new Error("Invalid or expired sandbox checkpoint");
|
|
74
|
-
this.entitySandbox.rollbackTo(index);
|
|
83
|
+
this.entitySandbox.rollbackTo(index.index);
|
|
84
|
+
this.directScriptWrites.clear();
|
|
85
|
+
for (const id of index.scripts) this.directScriptWrites.add(id);
|
|
75
86
|
let later = false;
|
|
76
87
|
for (const token of checkpoints.keys()) {
|
|
77
88
|
if (later) checkpoints.delete(token);
|
|
@@ -83,106 +94,70 @@ var EntityEditSandboxSession = class {
|
|
|
83
94
|
async rgetAssetFromEntity(entityId, load) {
|
|
84
95
|
const entity = this.entitySandbox.entities.get(entityId);
|
|
85
96
|
if (!entity || entity.entity_kind === "asset") throw new Error(`Business Entity not found: ${entityId}`);
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
88
|
-
const assetId =
|
|
97
|
+
const locator = this.entitySandbox.assetOf(entityId);
|
|
98
|
+
if (!locator || typeof locator.payload.key !== "string") throw new Error(`Entity ${entityId} has no attached Asset`);
|
|
99
|
+
const assetId = locator.payload.key;
|
|
89
100
|
const result = await load(entity);
|
|
90
|
-
if (result.assetId !==
|
|
101
|
+
if (result.assetId !== assetId) throw new Error("Host returned a different Entity Asset");
|
|
91
102
|
const current = this.entitySandbox.entities.get(entityId);
|
|
92
|
-
if (!current ||
|
|
93
|
-
if (entity.entity_kind === "
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}));
|
|
99
|
-
const bases = current.payload.baseEntityIds;
|
|
100
|
-
const scriptId = bases?.length ? this.entitySandbox.captionAudioScriptId(entityId) : this.entitySandbox.entities.create({
|
|
101
|
-
entity_kind: "audio-script",
|
|
102
|
-
payload: { segments: [] }
|
|
103
|
-
});
|
|
104
|
-
const existing = this.entitySandbox.entities.get(scriptId).payload.segments;
|
|
105
|
-
const additions = segments.filter((segment) => !existing.some((item) => item.segmentId === segment.segmentId));
|
|
106
|
-
this.entitySandbox.entities.update({
|
|
107
|
-
entity_id: scriptId,
|
|
108
|
-
payload: { segments: [...existing, ...additions] }
|
|
109
|
-
});
|
|
110
|
-
const ranges = timed.map((segment, index) => ({
|
|
111
|
-
segmentId: segments[index].segmentId,
|
|
112
|
-
startMs: segment.start_time_ms,
|
|
113
|
-
endMs: segment.end_time_ms
|
|
114
|
-
}));
|
|
115
|
-
const selection = current.payload.selection;
|
|
116
|
-
if (selection !== void 0 && (!isJsonObject(selection) || typeof selection.segmentId !== "string")) throw new Error("Caption selection must be one segment selection object");
|
|
117
|
-
const selected = selection === void 0 ? ranges : ranges.filter((range) => range.segmentId === selection.segmentId);
|
|
118
|
-
if (!selected.length) throw new Error("Caption selection does not name a segment of its resource");
|
|
119
|
-
for (const [index, range] of selected.entries()) {
|
|
120
|
-
const payload = {
|
|
121
|
-
baseEntityIds: bases?.length ? bases : [scriptId],
|
|
122
|
-
external,
|
|
123
|
-
...current.payload.style === void 0 ? {} : { style: current.payload.style },
|
|
124
|
-
selection: selection ?? { segmentId: range.segmentId },
|
|
125
|
-
extent: {
|
|
126
|
-
kind: "bounded",
|
|
127
|
-
start: range.startMs,
|
|
128
|
-
end: range.endMs
|
|
129
|
-
},
|
|
130
|
-
sampling: "native",
|
|
131
|
-
coordinateSpace: "milliseconds",
|
|
132
|
-
segmentRanges: [range]
|
|
133
|
-
};
|
|
134
|
-
const id = index === 0 ? entityId : this.entitySandbox.entities.create({
|
|
135
|
-
entity_kind: "caption",
|
|
136
|
-
payload
|
|
137
|
-
});
|
|
138
|
-
if (index === 0) this.entitySandbox.entities.update({
|
|
139
|
-
entity_id: id,
|
|
140
|
-
payload
|
|
141
|
-
});
|
|
142
|
-
this.resolvedCaptionAssets.add(`${id}:${assetId}`);
|
|
143
|
-
}
|
|
144
|
-
const markerId = this.entitySandbox.entities.create({
|
|
145
|
-
entity_kind: "sequence-marker",
|
|
146
|
-
payload: {
|
|
147
|
-
sourceRange: {
|
|
148
|
-
start: Math.min(...ranges.map((r) => r.startMs)),
|
|
149
|
-
end: Math.max(...ranges.map((r) => r.endMs))
|
|
150
|
-
},
|
|
151
|
-
duration: { mode: "from-source" },
|
|
152
|
-
segmentRanges: ranges
|
|
153
|
-
}
|
|
103
|
+
if (!current || this.entitySandbox.assetOf(entityId)?.entity_id !== locator.entity_id) throw new Error("Entity resource changed during its read");
|
|
104
|
+
if (entity.entity_kind === "audio" && locator.payload.system === "memota-speech") {
|
|
105
|
+
const fields = speechAssetFields(result.content);
|
|
106
|
+
if (Object.entries(fields).some(([key, value]) => JSON.stringify(current.payload[key]) !== JSON.stringify(value))) this.entitySandbox.entities.declareFields({
|
|
107
|
+
entity_id: entityId,
|
|
108
|
+
payload: fields
|
|
154
109
|
});
|
|
155
|
-
this.entitySandbox.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
endpoint_1_entity_id: markerId
|
|
110
|
+
this.entitySandbox.entities.declareFields({
|
|
111
|
+
entity_id: locator.entity_id,
|
|
112
|
+
payload: { storageKey: speechAssetStorageKey(result.content) }
|
|
159
113
|
});
|
|
160
114
|
}
|
|
161
|
-
this.resolvedCaptionAssets.add(`${entityId}:${assetId}`);
|
|
162
115
|
return result;
|
|
163
116
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const external = this.baseRows.entities.find((row) => row.entityId === entityId)?.payload.external;
|
|
168
|
-
return !!external && typeof external === "object" && !Array.isArray(external) && "key" in external && external.key === assetId;
|
|
117
|
+
/** A speech resource is hydrated once its Asset names where the bytes live. */
|
|
118
|
+
initializedSpeech(asset) {
|
|
119
|
+
return typeof asset.payload.storageKey === "string" && asset.payload.storageKey.trim() !== "";
|
|
169
120
|
}
|
|
170
|
-
/** Finish
|
|
121
|
+
/** Finish resource initialization before validation; no partial rows are published. */
|
|
171
122
|
async prepareEntityAssets(load) {
|
|
172
123
|
for (const entity of this.entitySandbox.entities.list()) {
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
124
|
+
const locator = this.entitySandbox.assetOf(entity.entity_id);
|
|
125
|
+
if (typeof locator?.payload.key !== "string") continue;
|
|
126
|
+
if (entity.entity_kind === "audio" && locator.payload.system === "memota-speech" && !this.initializedSpeech(locator)) await this.rgetAssetFromEntity(entity.entity_id, load);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Direct script writes save a resource before the entity graph can be committed. */
|
|
130
|
+
async persistAudioScriptAssets(write) {
|
|
131
|
+
for (const entity of this.entitySandbox.entities.list()) {
|
|
132
|
+
if (entity.entity_kind !== "audio-script") continue;
|
|
133
|
+
if (audioScriptAssetOf(this.entitySandbox.rows(), entity.entity_id) !== void 0 || !this.directScriptWrites.has(entity.entity_id)) continue;
|
|
134
|
+
const content = audioScriptAssetContent(entity.payload);
|
|
135
|
+
const saved = await write(entity, content);
|
|
136
|
+
if (JSON.stringify(saved.content) !== JSON.stringify(content)) throw new Error("AudioScript resource writer changed the submitted content");
|
|
137
|
+
const assetEntityId = this.entitySandbox.entities.ensureAsset({
|
|
138
|
+
system: "memota",
|
|
139
|
+
key: saved.assetId
|
|
140
|
+
});
|
|
141
|
+
this.entitySandbox.relations.link({
|
|
142
|
+
relation_kind: "from-asset",
|
|
143
|
+
endpoint_0_entity_id: entity.entity_id,
|
|
144
|
+
endpoint_1_entity_id: assetEntityId
|
|
145
|
+
});
|
|
146
|
+
this.entitySandbox.entities.declareFields({
|
|
147
|
+
entity_id: entity.entity_id,
|
|
148
|
+
payload: audioScriptAssetFields(entity.payload)
|
|
149
|
+
});
|
|
175
150
|
}
|
|
176
151
|
}
|
|
177
152
|
buildPlan(baseVersion) {
|
|
178
153
|
const entityPlan = this.entitySandbox.buildPlan();
|
|
179
154
|
assertCanonicalEditorResources(toDslRows(entityPlan.rows));
|
|
180
155
|
assertMediaAssetWritePolicy(this.baseRows, toDslRows(entityPlan.rows));
|
|
181
|
-
if (entityPlan.rows.loroSnapshot) projectEntityTimeline(toDslRows(entityPlan.rows)
|
|
156
|
+
if (entityPlan.rows.loroSnapshot) projectEntityTimeline(toDslRows(entityPlan.rows));
|
|
182
157
|
return {
|
|
183
158
|
...entityPlan.rows.loroSnapshot ? { loro_update: bytesToBase64(this.compileJournal(entityPlan)) } : {},
|
|
184
159
|
plan_kind: "entities",
|
|
185
|
-
doc_id: this.
|
|
160
|
+
doc_id: this.docId,
|
|
186
161
|
base_version: baseVersion,
|
|
187
162
|
ops: [],
|
|
188
163
|
entity_base_revision: entityPlan.base_revision,
|
|
@@ -197,7 +172,7 @@ var EntityEditSandboxSession = class {
|
|
|
197
172
|
compileJournal(plan) {
|
|
198
173
|
return LoroEntityDocument.fromSnapshot(base64ToBytes(plan.rows.loroSnapshot), (state) => {
|
|
199
174
|
assertCanonicalEditorResources(state.rows);
|
|
200
|
-
projectEntityTimeline(state.rows
|
|
175
|
+
projectEntityTimeline(state.rows);
|
|
201
176
|
}).transact((draft) => {
|
|
202
177
|
for (const command of plan.commands) switch (command.kind) {
|
|
203
178
|
case "create-entity": {
|
|
@@ -303,6 +278,21 @@ function loadEntityAsset(entity) {
|
|
|
303
278
|
});
|
|
304
279
|
});
|
|
305
280
|
}
|
|
281
|
+
function writeEntityAsset(entity, content) {
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
const id = ++requestId;
|
|
284
|
+
pending.set(id, {
|
|
285
|
+
resolve,
|
|
286
|
+
reject
|
|
287
|
+
});
|
|
288
|
+
port.postMessage({
|
|
289
|
+
t: "write-entity-asset",
|
|
290
|
+
requestId: id,
|
|
291
|
+
entity,
|
|
292
|
+
content
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
}
|
|
306
296
|
function post(message) {
|
|
307
297
|
port.postMessage(message);
|
|
308
298
|
}
|
|
@@ -343,8 +333,10 @@ function positionFromError(error, script, phase) {
|
|
|
343
333
|
};
|
|
344
334
|
}
|
|
345
335
|
async function main() {
|
|
346
|
-
const
|
|
347
|
-
|
|
336
|
+
const idFactory = data.idLabel != null ? countingFactory(data.idLabel) : void 0;
|
|
337
|
+
const session = new EntityEditSandboxSession({
|
|
338
|
+
docId: data.docId,
|
|
339
|
+
idFactory,
|
|
348
340
|
entityState: data.entityState,
|
|
349
341
|
domainIdFactory: domainIdFactory(data.idLabel),
|
|
350
342
|
onEntry: (entry) => post({
|
|
@@ -367,8 +359,7 @@ async function main() {
|
|
|
367
359
|
t: "entity-truncate",
|
|
368
360
|
index
|
|
369
361
|
})
|
|
370
|
-
};
|
|
371
|
-
const session = new EntityEditSandboxSession(data.document, options);
|
|
362
|
+
});
|
|
372
363
|
const wrapped = `(async (entities, relations, checkpoint, rollbackTo, inputs, console, rgetAssetFromEntity) => {${data.script}\n})`;
|
|
373
364
|
const ctx = vm.createContext(Object.create(null));
|
|
374
365
|
let run;
|
|
@@ -393,10 +384,12 @@ async function main() {
|
|
|
393
384
|
try {
|
|
394
385
|
const invoke = run;
|
|
395
386
|
post({ t: "ready" });
|
|
396
|
-
await invoke(session.entities, session.relations, session.checkpoint, session.rollbackTo, data.inputs ?? {}, session.console, (entityId) => {
|
|
387
|
+
await invoke(session.entities, session.relations, session.checkpoint, session.rollbackTo, data.inputs ?? {}, session.console, async (entityId) => {
|
|
388
|
+
await session.persistAudioScriptAssets(writeEntityAsset);
|
|
397
389
|
return session.rgetAssetFromEntity(entityId, loadEntityAsset);
|
|
398
390
|
});
|
|
399
391
|
await session.prepareEntityAssets(loadEntityAsset);
|
|
392
|
+
await session.persistAudioScriptAssets(writeEntityAsset);
|
|
400
393
|
} catch (error) {
|
|
401
394
|
post({
|
|
402
395
|
t: "fail",
|