@mengine/medeo-client 2.1.1-dsl.1 → 2.1.1-dsl.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 CHANGED
@@ -23,6 +23,20 @@ session.destroy();
23
23
 
24
24
  For explicit synchronization, use `ManualSyncDoc.open({client, peerId})`, then `pull()`, `editor`, `push()` and `dispose()`. Both lifecycles use the same binary snapshot/update and version-coverage contracts. Neither silently upgrades old documents or overwrites an incompatible cache.
25
25
 
26
+ ## Local persistence
27
+
28
+ Browser hosts can inject `IndexedDBDocStorage` through `localStorage`. An optional `initialSnapshot` merges a freshly fetched server snapshot into that storage before DocManager loads it; it never replaces locally saved, unconfirmed edits. The host can require a successful online fetch before constructing the session.
29
+
30
+ `getLocalSaveState()` / `subscribeLocalSaveState()` report `{ status: 'saving' | 'saved' | 'failed', error }` for this session's local commits. This is separate from `getSyncState()` and server confirmation. The storage adapter determines durability: IndexedDB resolves only after its transaction commits; MemoryDocStorage remains in memory. Failed saves retain their causal order and retry without blocking other documents.
31
+
32
+ `waitForLocalSave({ timeoutMs? })` waits until the local save queue drains, including in-flight writes and edits made while waiting (default deadline: 15 seconds). Continuous editing may keep it pending. Timing out only stops the wait; persistence continues. This does not wait for server acknowledgement.
33
+
34
+ For normal teardown, use `await session.close()`. It immediately rejects new edits and stops synchronization, then keeps local persistence alive until pending commits are saved before releasing connections. Storage failures remain retryable while closing. `destroy()` is immediate cancellation for exceptional teardown, including failed startup; it can discard unpersisted in-memory work. Neither API can guarantee a save after the browser process is forcibly terminated. Only completed local saves survive reopening.
35
+
36
+ Edits received from another local-storage peer also remain unconfirmed until covered by an authoritative server version. Receiving a cross-tab notification is not a server acknowledgement.
37
+
38
+ Reopening uses the stored Loro history and the server's version vector to derive missing updates, without replaying semantic operations or their external side effects. Undo history remains limited to the current session.
39
+
26
40
  ## Editing and conversion
27
41
 
28
42
  - Track: create, reorder, hide and delete.
@@ -30,13 +44,14 @@ For explicit synchronization, use `ManualSyncDoc.open({client, peerId})`, then `
30
44
  - Placement: sequential, absolute or anchored. Reordering and replacement require an explicit associated-content policy.
31
45
  - Voiceover: `applyVoiceover` consumes materialized recording/script/caption results in one commit. Upload, ASR and TTS belong to the host.
32
46
  - BGM: `setBgm` requires full resource facts and explicitly fills the Timeline; trim/speed are not enabled.
33
- - Caption: independent native Text or AudioScript Cursor reference; style and source switching.
47
+ - AudioScript: segmented text; segments are inserted, moved and deleted by id, each with its own native Text.
48
+ - Caption: independent native Text or a selection of one script segment (whole, or a Cursor run inside it); style and source switching.
34
49
 
35
50
  `resolveDslLayout`, `toVideoDraft`, `fromVideoDraft`, `MedeoDsl` and the model types are exported from the root and `/dsl`. Projection returns diagnostics; unrepresentable legacy output throws `DslProjectionError`. `MengineHttpClient.fetchDraft()` / `fetchDraftAt(seq)` read the server's content, diagnostics and matching version.
36
51
 
37
52
  ## The DSL document
38
53
 
39
- The live document lives here: `src/dsl/document` holds the `MedeoDsl` handle, transactions, draft guards and native reads; `src/dsl/projection` holds layout solving, VideoDraft conversion and the generated IDL types. Definitions, the Loro schema and change constraints come from the private `@mengine/medeo-dsl` and are re-exported by name, so the published API does not grow when the DSL does. Transactions ask the DSL's constraint functions; only what needs a live Loro document — caption ranges in the current body, Tree identities, Mirror's K03 limit — is checked here. See [RFC 04](../../docs/projects/medeo-dsl/rfc/04-entity-relation-to-loro-mapping.md).
54
+ The live document lives here: `src/dsl/document` holds the `MedeoDsl` handle, transactions, draft guards and native reads; `src/dsl/projection` holds layout solving, VideoDraft conversion and the generated IDL types. Definitions, the Loro schema and change constraints come from the private `@mengine/medeo-dsl` and are re-exported by name, so the published API does not grow when the DSL does. Transactions ask the DSL's constraint functions; only what needs a live Loro document — selections of a segment's current text, Tree identities, Mirror's K03 limit — is checked here. See [RFC 04](../../docs/projects/medeo-dsl/rfc/04-entity-relation-to-loro-mapping.md).
40
55
 
41
56
  ### Using the editor
42
57
 
@@ -69,13 +84,13 @@ dsl.dispose();
69
84
 
70
85
  ### Interface and invariants
71
86
 
72
- - `getSnapshot()` returns a frozen, read-only `MedeoDslSnapshot`: entity and relation tables, effective containment, rich-text runs, resolved Caption text and diagnostics. It does not compute layout or VideoDraft. Reads reuse the same snapshot until a DSL content event invalidates it; no-op Actions and repeated imports retain it. Reading never commits changes.
87
+ - `getSnapshot()` returns a frozen, read-only `MedeoDslSnapshot`: entity tables, the voice-timbre and derived-from relation tables, effective containment, rich-text runs, resolved Caption text and diagnostics. A relation an entity holds at most one of is a field of its row: a Clip's `source` and `anchor`, a Caption's `selection`, a PhoneticScript's `phoneme`. An unreadable optional field is dropped from the row with a diagnostic; the row stays. A Caption's own text wins over its selection when a merge leaves both. It does not compute layout or VideoDraft. Reads reuse the same snapshot until a DSL content event invalidates it; no-op Actions and repeated imports retain it. Reading never commits changes.
73
88
  - `subscribe(listener)` includes text-only and marks-only changes. The returned function unsubscribes; dispose is idempotent. As with other synchronous document observers, listeners should not throw or initiate nested writes.
74
89
  - `applyMutations(mutations, audit)` is the preferred interface for typed entity, relation, Tree, Text and Caption-selection commands. It opens one `transact`; `tx.applyMutations` inside an existing callback uses the same draft and commit.
75
- - `transact(edit, audit)` exposes a guarded `tx.draft` for ordinary fields and Tree, `tx.editText(scriptId, edits, expectedBodyId?)`, `tx.editCaptionText(captionId, edits, expectedBodyId?)`, and `tx.setCaptionContent({captionId, audioScriptId, start, end})`. The synchronous callback and every draft reference expire on return. Async callbacks, nested writes, identity changes, direct overwrites of existing bodies, and same-transaction entity deletion/recreation are rejected. Catching a guard/command error inside the callback does not allow a partial commit.
76
- - `editText(scriptId, expectedBodyId, edits, audit)` delegates to the same transaction. Text supports insert/delete/mark/unmark/editing Delta; offsets are UTF-16 and cannot split surrogate pairs. `tx.draft` exposes the text preview after preceding edits, while existing body strings are never sent through Mirror's full-text diff. A supplied body identity must match the current Text; Actions editing an existing body retain that stale-view check.
77
- - Caption selections are interpreted at their position in the command sequence. Their Cursors are created after earlier native edits, and follow later edits. New selections are deferred commands, not synthetic Cursor values in `tx.draft`; do not read that relation table to retrieve a pending selection. Explicit Cursor relation writes and deletes also replay in sequence with Text edits.
78
- - `textRange(scriptId, start, end)` returns native Cursors for a nonempty continuous selection. Caption content stores their native bytes through the central codec. The permanent first-boundary limitation remains deferred.
90
+ - `transact(edit, audit)` exposes a guarded `tx.draft` for ordinary fields and Tree, `tx.editText(scriptId, segmentId, edits, expectedBodyId?)`, `tx.editCaptionText(captionId, edits, expectedBodyId?)`, `tx.insertSegment` / `tx.moveSegment` / `tx.deleteSegment`, `tx.setCaptionContent({captionId, audioScriptId, segmentId, start?, end?})` and `tx.setPhoneme({phoneticScriptId, audioScriptId, segmentId, start?, end?, phonemeScript?})`. The synchronous callback and every draft reference expire on return. Async callbacks, nested writes, identity changes, direct overwrites of existing bodies, and same-transaction entity deletion/recreation are rejected. Catching a guard/command error inside the callback does not allow a partial commit.
91
+ - `editText(scriptId, segmentId, expectedBodyId, edits, audit)` delegates to the same transaction. Text supports insert/delete/mark/unmark/editing Delta; offsets are UTF-16 and cannot split surrogate pairs. `tx.draft` exposes the text preview after preceding edits, while existing body strings are never sent through Mirror's full-text diff. A supplied body identity must match the current Text; Actions editing an existing body retain that stale-view check.
92
+ - Caption selections are interpreted at their position in the command sequence. Their Cursors are created after earlier native edits, and follow later edits. New selections are deferred commands, not synthetic Cursor values in `tx.draft`; do not read a Caption's `selection` from the draft to retrieve a pending one. Explicit Cursor values written to `selection`, and its removal, also replay in sequence with Text edits.
93
+ - `textRange(scriptId, segmentId, start, end)` returns native Cursors for a nonempty run inside one segment. A Caption's `selection` stores their native bytes through the central codec. The permanent first-boundary limitation remains deferred.
79
94
 
80
95
  Every business write requires a structured `DslAudit`: `{semantic_op, payload, intent?, actor?}`. The transaction exit serializes the existing server envelope exactly once; initialization alone uses an internal unaudited path. No-op transactions add no history. External pending native operations must be committed separately first. Known preparation failures write nothing; unexpected native/runtime failures do not receive database-style rollback.
81
96
 
@@ -87,11 +102,11 @@ const audit = {
87
102
  };
88
103
 
89
104
  dsl.transact((tx) => {
90
- tx.draft['audio-script-entities']['script-1'] = { body: 'hello' };
105
+ tx.draft['audio-script-entities']['script-1'] = { segments: [{ id: 'segment-1', text: 'hello' }] };
91
106
  tx.draft['caption-entities']['caption-1'] = {};
92
- tx.editText('script-1', [{ type: 'insert', at: 5, text: ' world' }]);
107
+ tx.editText('script-1', 'segment-1', [{ type: 'insert', at: 5, text: ' world' }]);
93
108
  // Reads the prepared preview: "hello world". The native Text does not yet exist.
94
- tx.setCaptionContent({ captionId: 'caption-1', audioScriptId: 'script-1', start: 0, end: 5 });
109
+ tx.setCaptionContent({ captionId: 'caption-1', audioScriptId: 'script-1', segmentId: 'segment-1', start: 0, end: 5 });
95
110
  }, audit);
96
111
  ```
97
112
 
@@ -139,7 +154,7 @@ try {
139
154
 
140
155
  Caption text is matched forward in `speech.caption_ids` order. A failed exact match preserves the text in the Caption's own optional **LoroText**, without advancing the match cursor or inventing another AudioScript. Inline text is also available through `DslEditor.createCaption({text, style?})`, `setCaptionText`, and `editCaptionText`. Switching to a reference clears the inline field in the same transaction; direct `transact` callers must clear the other source explicitly. Concurrent coexistence reads the reference, even when it later becomes invalid (no stale-text fallback).
141
156
 
142
- `richText` is keyed by AudioScript or inline Caption entity ID; `captionText` resolves either content source. Optional Caption Texts have ordinary identities and are created in the native hook, preserving mergeable style/font while avoiding duplicate text on mergeable-Text deletion Undo. See [RFC 02](../../docs/projects/medeo-dsl/rfc/02-videodraft-projection.md) for layout, import normalization and compatibility limits.
157
+ `richText` is keyed by script segment ID or inline Caption entity ID; `captionText` resolves either content source and `phoneticText` what each PhoneticScript selects. Optional Caption Texts have ordinary identities and are created in the native hook, preserving mergeable style/font while avoiding duplicate text on mergeable-Text deletion Undo. See [RFC 02](../../docs/projects/medeo-dsl/rfc/02-videodraft-projection.md) for layout, import normalization and compatibility limits.
143
158
 
144
159
  ## Legacy consumers
145
160