@mengine/medeo-tool 1.4.1-alpha.5 → 2.0.1-alpha.11

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.
@@ -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
- axvideo: BoundedDerivedSequencePayload;
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: BoundedNativeSequencePayload & MediaAssetPayload;
70
- audio: BoundedNativeSequencePayload & MediaAssetPayload;
71
- voice: BoundedNativeSequencePayload & MediaAssetPayload;
72
- image: UnboundedConstantSequencePayload & MediaAssetPayload;
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: BoundedNativeSequencePayload & {
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
- type MediaAssetPayload = JsonObject & {
175
- external: {
176
- system: 'memota' | 'memota-speech';
177
- key: string;
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
- /** Stored own fields; a variant may obtain required content fields from its declared bases. */
203
- type StoredEntityPayload<K extends KnownEntityKind> = EntityPayloadByKind[K] | (K extends 'caption' ? JsonObject & Pick<MediaAssetPayload, 'external'> : never) | (JsonObject & Partial<EntityPayloadByKind[K]> & {
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 { BoundedDerivedSequencePayload, BoundedNativeSequencePayload, BusinessEntityFacade, BusinessRelationFacade, CaptionTextSelection, CreateEntityInput, DeleteEntityInput, EntityAssetContent, EntityPayloadByKind, EntitySandboxCheckpoint, EntityUpdateInput, FieldChange, FieldPath, JsonObject, JsonPrimitive, JsonValue, KnownEntityKind, KnownRelationKind, LinkRelationInput, MediaAssetPayload, RelationUpdateInput, SandboxEntity, SandboxRelation, ScriptTextSegment, StoredEntityPayload, UnboundedConstantSequencePayload, UnlinkRelationInput, UpdateEntityInput, checkpoint, console, entities, inputs, relations, rgetAssetFromEntity, rollbackTo };
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
@@ -1,4 +1,4 @@
1
- import { l as EntityStoreSnapshot } from "./entity-contract-DlmUSouB.mjs";
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>;
@@ -1,21 +1,27 @@
1
- import { a as isJsonObject, n as toDslRows, r as businessFacades, t as EntitySandbox } from "./entity-sandbox-TaUVT3on.mjs";
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
- /** MCAP's output_caption JSON contract; unknown formats never become invented captions. */
8
- function captionAssetSegments(content) {
9
- if (!content || typeof content !== "object" || Array.isArray(content) || !Array.isArray(content.segments)) throw new Error("Caption Asset must contain an output_caption object with segments");
10
- if (!content.segments.length) throw new Error("Caption Asset contains no speech segments");
11
- return content.segments.map((value) => {
12
- if (!value || typeof value !== "object" || Array.isArray(value) || typeof value.text !== "string" || !value.text.trim() || !Number.isSafeInteger(value.start_time_ms) || !Number.isSafeInteger(value.end_time_ms) || value.start_time_ms < 0 || value.end_time_ms <= value.start_time_ms) throw new Error("Caption Asset has invalid text or millisecond timing");
13
- return {
14
- text: value.text,
15
- start_time_ms: value.start_time_ms,
16
- end_time_ms: value.end_time_ms
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
- document;
35
+ docId;
30
36
  entitySandbox;
31
37
  baseRows;
32
38
  domainIdFactory;
33
39
  logs = [];
34
40
  onLog;
35
41
  logBytes = 0;
36
- resolvedCaptionAssets = /* @__PURE__ */ new Set();
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(document, options) {
44
- this.document = structuredClone(document);
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, this.entitySandbox.commandCount);
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 external = entity.payload.external;
87
- if (!external || typeof external !== "object" || Array.isArray(external) || typeof external.key !== "string") throw new Error(`Entity ${entityId} has no attached Asset`);
88
- const assetId = external.key;
89
- const result = await load(entity);
90
- if (result.assetId !== external.key) throw new Error("Host returned a different Entity Asset");
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;
100
+ const result = await load(entity, locator);
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 || JSON.stringify(current.payload.external) !== JSON.stringify(external)) throw new Error("Entity resource changed during its read");
93
- if (entity.entity_kind === "caption" && !this.initializedAsset(entityId, external.key)) {
94
- const timed = captionAssetSegments(result.content);
95
- const segments = timed.map((segment, index) => ({
96
- segmentId: `asset:${assetId}:${index}`,
97
- text: segment.text
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.relations.link({
156
- relation_kind: "audio-script-marker",
157
- endpoint_0_entity_id: scriptId,
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
- initializedAsset(entityId, assetId) {
165
- const current = this.entitySandbox.entities.get(entityId);
166
- if (this.resolvedCaptionAssets.has(`${entityId}:${assetId}`) && isJsonObject(current?.payload.selection) && Array.isArray(current?.payload.baseEntityIds)) return true;
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 unawaited Caption initialization before validation; no partial rows are published. */
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 external = entity.payload.external;
174
- if (entity.entity_kind === "caption" && external && typeof external === "object" && !Array.isArray(external) && typeof external.key === "string" && !this.initializedAsset(entity.entity_id, external.key)) await this.rgetAssetFromEntity(entity.entity_id, load);
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), this.document.meta);
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.document.meta.draft_id ?? "",
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, this.document.meta);
175
+ projectEntityTimeline(state.rows);
201
176
  }).transact((draft) => {
202
177
  for (const command of plan.commands) switch (command.kind) {
203
178
  case "create-entity": {
@@ -289,7 +264,7 @@ port.on("message", (message) => {
289
264
  if (message.error) waiter?.reject(new Error(message.error));
290
265
  else waiter?.resolve(message.result);
291
266
  });
292
- function loadEntityAsset(entity) {
267
+ function loadEntityAsset(entity, asset) {
293
268
  return new Promise((resolve, reject) => {
294
269
  const id = ++requestId;
295
270
  pending.set(id, {
@@ -299,7 +274,23 @@ function loadEntityAsset(entity) {
299
274
  port.postMessage({
300
275
  t: "entity-asset",
301
276
  requestId: id,
302
- entity
277
+ entity,
278
+ asset
279
+ });
280
+ });
281
+ }
282
+ function writeEntityAsset(entity, content) {
283
+ return new Promise((resolve, reject) => {
284
+ const id = ++requestId;
285
+ pending.set(id, {
286
+ resolve,
287
+ reject
288
+ });
289
+ port.postMessage({
290
+ t: "write-entity-asset",
291
+ requestId: id,
292
+ entity,
293
+ content
303
294
  });
304
295
  });
305
296
  }
@@ -343,8 +334,10 @@ function positionFromError(error, script, phase) {
343
334
  };
344
335
  }
345
336
  async function main() {
346
- const options = {
347
- idFactory: data.idLabel != null ? countingFactory(data.idLabel) : void 0,
337
+ const idFactory = data.idLabel != null ? countingFactory(data.idLabel) : void 0;
338
+ const session = new EntityEditSandboxSession({
339
+ docId: data.docId,
340
+ idFactory,
348
341
  entityState: data.entityState,
349
342
  domainIdFactory: domainIdFactory(data.idLabel),
350
343
  onEntry: (entry) => post({
@@ -367,8 +360,7 @@ async function main() {
367
360
  t: "entity-truncate",
368
361
  index
369
362
  })
370
- };
371
- const session = new EntityEditSandboxSession(data.document, options);
363
+ });
372
364
  const wrapped = `(async (entities, relations, checkpoint, rollbackTo, inputs, console, rgetAssetFromEntity) => {${data.script}\n})`;
373
365
  const ctx = vm.createContext(Object.create(null));
374
366
  let run;
@@ -393,10 +385,12 @@ async function main() {
393
385
  try {
394
386
  const invoke = run;
395
387
  post({ t: "ready" });
396
- await invoke(session.entities, session.relations, session.checkpoint, session.rollbackTo, data.inputs ?? {}, session.console, (entityId) => {
388
+ await invoke(session.entities, session.relations, session.checkpoint, session.rollbackTo, data.inputs ?? {}, session.console, async (entityId) => {
389
+ await session.persistAudioScriptAssets(writeEntityAsset);
397
390
  return session.rgetAssetFromEntity(entityId, loadEntityAsset);
398
391
  });
399
392
  await session.prepareEntityAssets(loadEntityAsset);
393
+ await session.persistAudioScriptAssets(writeEntityAsset);
400
394
  } catch (error) {
401
395
  post({
402
396
  t: "fail",