@mengine/medeo-client 2.0.1 → 2.1.1-dsl.0

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/index.d.ts CHANGED
@@ -1,1122 +1,5 @@
1
- import { $ as VideoDraft, A as assertValidVideoDocument, B as CaptionPart, C as index_d_exports, D as VideoDocumentMirrorSchema, E as VideoDocumentDraft, F as buildSpeechHostMap, G as Timeline, H as PartKind, I as derivePositionFromAbs, J as TrackItemTimePosition, K as Track, L as fromVideoDocument, M as buildInitialVideoDocument, N as DerivedItemPosition, O as videoDocumentMirrorSchema, P as SpeechHostMap, Q as VideoDocumentValidationIssueCode, R as toVideoDocument, S as ValidationError, T as TrackItemDraft, U as PartUnion, V as DEFAULT_UNIT_TIME_MS, W as SpeechPart, X as VideoDocument, Y as VideoClipPart, Z as VideoDocumentValidationIssue, _ as PlannedSemanticOpKind, a as MirrorVideoDocumentOptions, at as PartAggregation, b as SchemaValidator, c as CommitOptions, ct as Track$1, d as SemanticEditor, et as VideoDraftContent, f as SemanticOpInput, g as ImplementedSemanticOpKind, h as IMPLEMENTED_SEMANTIC_OP_KINDS, i as MirrorVideoDocumentAdapter, it as CaptionStyle, j as validateVideoDocument, k as VideoDocumentValidationError, l as OpActor, lt as TrackItem$1, m as TransactAudit, n as videoDocumentSchema, nt as Attachment, o as createMirrorVideoDocument, ot as SpeedShift, p as SemanticOpName, q as TrackItem, r as readVideoDocumentFromDraft, rt as CaptionPart$1, s as createMirrorVideoDocumentAdapter, st as Timeline$1, t as partUnionSchema, tt as VideoDraftPartUnion, u as SemanticDocumentAdapter, v as SemanticOpKind, w as TrackDraft, x as SnapshotReadable, y as isImplementedSemanticOpKind, z as BgmPart } from "./index-CnZ9l3rb.js";
2
- import { LoroDoc, PeerID } from "loro-crdt";
3
- import { DocState } from "@mengine/sync";
4
- import { BaseDocStorage, Connection, DocDiff, DocPushReceipt, DocSnapshotRecord, DocStorage, DocStorageOptions, DocUpdate, DocUpdateRecord } from "@mengine/storage";
5
-
6
- //#region src/client/base64.d.ts
7
- declare function bytesToBase64(bytes: Uint8Array): string;
8
- declare function base64ToBytes(base64: string): Uint8Array;
9
- //#endregion
10
- //#region src/client/wire.d.ts
11
- interface MengineDocumentVersion {
12
- update_seq: number;
13
- /** base64 `VersionVector.encode` of the server oplog — the sync anchor. */
14
- server_vv: string;
15
- /** base64 `encodeFrontiers` of the server oplog heads. */
16
- frontiers: string;
17
- }
18
- interface MengineSnapshotResponse {
19
- snapshot: string;
20
- version: MengineDocumentVersion;
21
- }
22
- /** Business + causal metadata for one update (audit / rollback anchor). */
23
- interface MengineUpdateMeta {
24
- semantic_op: string | null;
25
- payload: unknown;
26
- intent: string | null;
27
- /**
28
- * Who authored the op, as recorded in the Loro commit message (see `OpActor`).
29
- * Null for writes with no acting user — bootstrap, repair — and for Changes
30
- * written before attribution existed. Self-reported by the authoring client;
31
- * the server does not verify it against the request identity.
32
- */
33
- actor: {
34
- user_id: string;
35
- role: string;
36
- } | null;
37
- message: string | null;
38
- parse_error: boolean;
39
- peer: string;
40
- counter: number;
41
- lamport: number;
42
- timestamp: number;
43
- frontiers: string;
44
- }
45
- /**
46
- * Response to `GET .../sync?from=<vv b64>`: the ops the caller is missing as one
47
- * merged Loro update blob (`export({mode:'update', from})`) plus the server's
48
- * current oplog VV. Loro resolves the causal partial order inside the blob, so
49
- * there is no per-update envelope; `import` is idempotent, so an already-current
50
- * caller gets a framing-only blob that applies as a no-op. Mirrors the server's
51
- * `SyncWireResponse`.
52
- */
53
- interface MengineSyncResponse {
54
- /** base64 merged Loro update blob (empty string when the caller is current). */
55
- update: string;
56
- server_vv: string;
57
- }
58
- interface MengineAuditEntry extends MengineUpdateMeta {
59
- update_seq: number;
60
- }
61
- interface MengineAuditResponse {
62
- entries: MengineAuditEntry[];
63
- }
64
- interface MenginePushUpdateResponse {
65
- kind: 'ack' | 'duplicate';
66
- update_seq: number | null;
67
- version: MengineDocumentVersion;
68
- }
69
- interface MengineRejectedResponse {
70
- kind: 'rejected';
71
- code: string;
72
- message: string;
73
- server_version: MengineDocumentVersion;
74
- }
75
- interface MengineSseUpdateEvent {
76
- update_seq: number;
77
- updates: string[];
78
- meta: MengineUpdateMeta;
79
- version: MengineDocumentVersion;
80
- }
81
- type MenginePushResponse = MenginePushUpdateResponse | MengineRejectedResponse;
82
- //#endregion
83
- //#region src/client/http-client.d.ts
84
- interface MengineHttpClientOptions {
85
- docId: string;
86
- httpOrigin: string;
87
- /**
88
- * Bearer token for the `authorization` header. Accepts a static string or a
89
- * getter evaluated per request — prefer the getter in the browser editor so
90
- * each request carries the current login JWT (same credential the REST link
91
- * sends), rather than a value snapshotted before login hydrates or gone stale
92
- * after a refresh. A literal `user_dev` is the local dev/relay-test stub.
93
- */
94
- authToken?: string | (() => string | undefined);
95
- /**
96
- * The end-user id sent as the `medeo-user-id` header. Accepts either a static
97
- * string or a getter evaluated per request. Prefer the getter when the login
98
- * state hydrates asynchronously (e.g. the browser editor): the client is
99
- * constructed eagerly but each request reads the latest id, so an id that is
100
- * not yet ready at construction time is picked up once it settles — no need to
101
- * defer client/session creation until auth is ready.
102
- */
103
- userId?: string | (() => string | undefined);
104
- fetchImpl?: typeof fetch;
105
- }
106
- declare class MengineHttpRequestError extends Error {
107
- readonly status: number;
108
- readonly payload: unknown;
109
- constructor(status: number, payload: unknown);
110
- }
111
- /**
112
- * A push the server refused on its merits (`kind: 'rejected'`), as opposed to a
113
- * transport failure. Carries the server's machine `code` so callers can branch on
114
- * *why* rather than on an HTTP status:
115
- *
116
- * - `missing_dependency` (server answers 409) — retryable: the update depends on
117
- * ops the server log lacks, so catching up and re-exporting resolves it.
118
- * - `corrupt_update` (server answers 422) — never valid, retrying cannot help.
119
- *
120
- * Distinct from {@link MengineHttpRequestError} (transport/status-level failure)
121
- * and from a raw `fetch` rejection (network down): the three are separate classes
122
- * so a caller can tell "the server said no" from "the server never answered".
123
- */
124
- declare class MenginePushRejectedError extends Error {
125
- readonly code: string;
126
- readonly serverMessage: string;
127
- readonly serverVersion: MengineDocumentVersion | undefined;
128
- readonly status: number | undefined;
129
- constructor(code: string, serverMessage: string, serverVersion: MengineDocumentVersion | undefined, status: number | undefined);
130
- }
131
- declare class MengineHttpClient {
132
- private readonly options;
133
- private readonly fetchImpl;
134
- constructor(options: MengineHttpClientOptions);
135
- fetchSnapshot(): Promise<MengineSnapshotResponse>;
136
- /**
137
- * Loro VV-diff pull: send the caller's oplog `VersionVector.encode` as `from`
138
- * (omit for a full pull) and receive exactly the updates it is missing plus
139
- * the server's current VV. Replaces the old integer `after_update_id` cursor.
140
- */
141
- sync(fromVV?: Uint8Array): Promise<MengineSyncResponse>;
142
- /** Audit trail: extracted metadata per accepted update, in log order. */
143
- audit(): Promise<MengineAuditResponse>;
144
- /**
145
- * Append one Loro update to the server log.
146
- *
147
- * Resolves for both accepted outcomes and hands the verdict back verbatim —
148
- * `ack` (appended, `update_seq` allocated) and `duplicate` (already known, no
149
- * row appended). A `duplicate` is NOT an error, but it is also not an ack: it
150
- * means the bytes contributed nothing, so a caller waiting for its own write to
151
- * land must be able to tell them apart. Hence the verdict is returned rather
152
- * than collapsed into `void`.
153
- *
154
- * Rejections raise {@link MenginePushRejectedError} carrying the server's
155
- * machine `code`. The server answers them with 409/422, so the failure arrives
156
- * as a non-ok response; this method re-reads the parsed body to recover `code` /
157
- * `server_version` instead of leaving the caller a bare status. Transport-level
158
- * failures stay {@link MengineHttpRequestError}, and a dead network keeps
159
- * surfacing as the underlying `fetch` rejection.
160
- */
161
- pushUpdate(update: Uint8Array): Promise<MenginePushUpdateResponse>;
162
- eventsUrl(): string;
163
- headers(): Headers;
164
- fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
165
- private requestJson;
166
- private endpoint;
167
- }
168
- //#endregion
169
- //#region src/client/sse.d.ts
170
- interface MengineEventStreamOptions {
171
- client: MengineHttpClient;
172
- signal?: AbortSignal;
173
- /** Fired once the stream response is established (HTTP ok, body readable). */
174
- onOpen?(): void;
175
- onUpdate(event: MengineSseUpdateEvent): void;
176
- /** A committed update omitted from live transport; fetch the missing operations by VV. */
177
- onResync?(): void;
178
- }
179
- declare function readMengineEventStream(options: MengineEventStreamOptions): Promise<void>;
180
- //#endregion
181
- //#region src/editor/id-gen.d.ts
182
- /**
183
- * Part-id generation, aligned with the online ecosystem.
184
- *
185
- * The authoritative online producers — agent-harness (`@harness/shared`
186
- * `genObjId`) and director.v2 (`common/obj_id.py` `gen_obj_id`) — both mint part
187
- * ids as `` `${prefix}_${ulid()}` ``, and real captured drafts use exactly that
188
- * shape (`clip_…` / `spe_…` / `cap_…` / `bgm_…`, each a 26-char ULID). The engine
189
- * previously emitted `vc_<base36 timestamp><6 random>`, a different prefix AND a
190
- * different encoding — the sole cross-repo id divergence. This module removes it
191
- * by emitting the same `<prefix>_<ULID>` bytes.
192
- *
193
- * The ULID is generated inline (Crockford Base32, 48-bit time + 80-bit random)
194
- * rather than pulling the `ulid` npm package: the randomness class matches the
195
- * old generator (both `Math.random`-based) and it keeps `@mengine/medeo-client`
196
- * dependency-free for a purely mechanical id string. Part ids only need to be
197
- * unique and lexicographically time-sortable, which this satisfies.
198
- */
199
- /**
200
- * Online part-id semantic prefixes. `clip` (video clip) is the only value the
201
- * engine currently mints (see `addVideoClips`); the rest are declared so the
202
- * type documents the shared vocabulary and guards against reintroducing the old
203
- * `vc`/`sp`/`cp`/`bg` names. Speech/caption/bgm ids arrive pre-minted in op
204
- * payloads, so the engine never generates them itself.
205
- */
206
- type PartIdPrefix = 'clip' | 'spe' | 'cap' | 'bgm' | 'ti';
207
- declare function generatePartId(prefix: PartIdPrefix): string;
208
- //#endregion
209
- //#region src/editor/snapshot-utils.d.ts
210
- /** Main-track item identity read from a raw snapshot (only `part_id` is needed). */
211
- interface MainTrackItemRef {
212
- part_id: string | undefined;
213
- }
214
- declare function isMap(value: unknown): value is Map<unknown, unknown>;
215
- declare function snapshotToPlain(value: unknown): unknown;
216
- declare function getAt(snapshot: unknown, ...keys: string[]): unknown;
217
- declare function readMainTrackItems(snapshot: unknown): MainTrackItemRef[];
218
- /**
219
- * The effective timeline duration of a part read from a raw snapshot. No part
220
- * stores a derived `duration_ms` in authoritative state (reference/17 §5): a
221
- * video clip's length is its trim window `play_out - play_in` over the speed
222
- * multiplier; a speech's is its intrinsic `media_duration_ms`; a caption's is its
223
- * `initial_duration_ms`. Falls back to `fallback` when no source value is
224
- * resolvable (e.g. an unknown/raw part).
225
- */
226
- declare function readPartDurationMs(snapshot: unknown, partId: string, fallback?: number): number;
227
- declare function readPart(snapshot: unknown, partId: string): (Record<string, unknown> & {
228
- part_kind?: PartKind;
229
- }) | null;
230
- //#endregion
231
- //#region src/manual-sync/types.d.ts
232
- /**
233
- * Opaque marker for "the document state I last looked at".
234
- *
235
- * Deliberately not a number. Under mengine there is no monotonic document
236
- * version to compare: `meta.version` is stamped once at legacy ingest and then
237
- * frozen, and the server's `update_seq` counts every writer's pushes, so neither
238
- * can answer "has this document changed since I last read it". The answer comes
239
- * from comparing version vectors.
240
- *
241
- * Opaque means: no arithmetic, no ordering, no comparison other than handing it
242
- * back to {@link ManualSyncDoc.hasChangedSince}. Two version vectors are only
243
- * partially ordered — concurrent ones are neither greater nor equal, and Loro's
244
- * own `compare` returns `undefined` for them — so `mark1 > mark2` or sorting a
245
- * list of marks has no meaning to get wrong.
246
- *
247
- * Persisting one IS supported, via {@link encodeDocVersionMark} /
248
- * {@link decodeDocVersionMark}. Storing an opaque blob and reading it back does
249
- * not order or compute anything, and callers that compare across turns (rather
250
- * than within one process) need it: the legacy integer version this replaces was
251
- * itself persisted between agent turns.
252
- */
253
- interface DocVersionMark {
254
- readonly __brand: 'mengine-doc-version-mark';
255
- /** Encoded oplog `VersionVector` at the moment the mark was taken. */
256
- readonly encoded: Uint8Array;
257
- }
258
- /** Why a `pull()` did not complete. Mirrors the push failure taxonomy. */
259
- type PullFailureReason = 'failed';
260
- /**
261
- * Result of a `pull()`.
262
- *
263
- * `ok: false` is a first-class outcome, not an exception: per the M2/A ruling a
264
- * failed pull must not hard-fail the tool call, matching legacy's tolerance for
265
- * read failures (`getDraftVersion` swallows to null, `DraftVersionDetector`
266
- * catches and skips). The caller decides whether to proceed on a possibly-stale
267
- * document, so the degradation has to be visible in the return value.
268
- */
269
- type PullResult = {
270
- ok: true; /** True when the pull actually advanced the local document. */
271
- changed: boolean;
272
- } | {
273
- ok: false;
274
- reason: PullFailureReason;
275
- error: Error;
276
- };
277
- /**
278
- * What the server did with a `push()`.
279
- *
280
- * The first four mirror the server's verdict taxonomy (see `PushOutcomeKind`);
281
- * `nothing_to_push` is a local short-circuit — no request was made because the
282
- * document holds no ops the server lacks.
283
- */
284
- type PushResultKind = 'ack' | 'duplicate' | 'rejected' | 'failed' | 'nothing_to_push';
285
- /**
286
- * Result of a `push()`.
287
- *
288
- * `collaborated` is the load-bearing field. The legacy Drizzle path fails loudly
289
- * on concurrent writes (`Optimistic lock failed`); mengine merges silently by
290
- * design (M0/S2 accepted CRDT merge semantics). Migrating without surfacing this
291
- * would replace a path that reports conflicts with one that hides them, so every
292
- * successful push says whether the server held ops the local document did not.
293
- *
294
- * Its meaning is "the server has ops you don't", NOT "there was a conflict" — the
295
- * same user's other browser tab counts. It is therefore suitable for driving a
296
- * re-pull and for informing the caller, but not for raising an alarm on its own.
297
- */
298
- interface PushResult {
299
- kind: PushResultKind;
300
- /** Server document sequence observed after a successful `ack` or `duplicate`. */
301
- mengineUpdateSeq?: number | undefined;
302
- /** @deprecated Ack-only sequence retained until consumers adopt `mengineUpdateSeq`. */
303
- updateSeq?: number | undefined;
304
- /** Whether the server held ops the local document lacked. See above. */
305
- collaborated: boolean;
306
- /** Server machine code for `rejected` (`missing_dependency` / `corrupt_update`). */
307
- code?: string | undefined;
308
- /** Underlying error for `rejected` / `failed`. */
309
- error?: Error | undefined;
310
- }
311
- //#endregion
312
- //#region src/manual-sync/doc-version-mark.d.ts
313
- /**
314
- * Encode a {@link DocVersionMark} for storage or transport.
315
- *
316
- * The mark stays opaque across the round trip — the string is not a version
317
- * number and must not be compared, ordered, or parsed. Its only use is
318
- * {@link decodeDocVersionMark} followed by `hasChangedSince`.
319
- *
320
- * Callers that persist this should know the encoded length grows with the number
321
- * of peers that have ever written to the document (one counter each), and the FE
322
- * mints a fresh peer per page load. Still small in practice (a few hundred bytes
323
- * for dozens of peers), but it grows with document age rather than size; version
324
- * vector compaction is deferred to a later phase.
325
- */
326
- declare function encodeDocVersionMark(mark: DocVersionMark): string;
327
- /**
328
- * Rebuild a mark from {@link encodeDocVersionMark}'s output.
329
- *
330
- * Returns `undefined` for input this did not produce (a legacy integer version,
331
- * a truncated value, an empty string). That is the honest answer — "I cannot
332
- * establish what you last saw" — and callers should treat it as "no baseline"
333
- * rather than as "unchanged". Decoding does not validate the bytes as a version
334
- * vector; `hasChangedSince` reports "changed" for an undecodable mark, which is
335
- * the conservative direction.
336
- */
337
- declare function decodeDocVersionMark(encoded: string): DocVersionMark | undefined;
338
- //#endregion
339
- //#region src/manual-sync/manual-sync-doc.d.ts
340
- interface ManualSyncDocOptions {
341
- client: MengineHttpClient;
342
- /**
343
- * Peer id for this writer. Every writer needs a distinct one: two writers
344
- * sharing a peer and branching from the same base emit identical
345
- * `(peer, counter)` pairs, the server classifies the second as `duplicate` and
346
- * drops it, and the write is silently lost. Callers mint a random one (see the
347
- * harness's `mintHarnessPeerId`).
348
- */
349
- peerId?: PeerID;
350
- }
351
- /**
352
- * Agent-facing document with explicit `pull()` / `push()` over one Loro
353
- * document, with no background sync (ADR 0015 D1–D4).
354
- *
355
- * It deliberately does NOT reuse `MengineDocSession`'s stack. That stack —
356
- * SSE + local `DocStorage` + `ClientServerSynchronizer` + `DocManager` — is
357
- * correct for a browser editor and actively wrong here:
358
- *
359
- * - Its retry backoff lands *outside* the tool-call lifetime, so a write can
360
- * settle seconds after the tool already told the LLM what happened.
361
- * - It has no durable local queue in the harness, so pending pushes die with the
362
- * process — that is lost data, reported as success.
363
- * - Agent semantics require the document to change only at points the agent can
364
- * name. If it converged on its own between tool calls, "what state was this
365
- * decision based on" would be unanswerable, and a remote change could land
366
- * mid-edit.
367
- *
368
- * What replaces the whole background job queue is one variable: the **watermark**,
369
- * the version the server has confirmed. `push()` exports `{mode:'update', from:
370
- * watermark}` and only advances it on a confirmed verdict, so a failed push is
371
- * retried implicitly — the next push carries both the failed ops and any new
372
- * ones, in one blob. No queue, no timer, no retry bookkeeping.
373
- *
374
- * Two non-obvious properties of that watermark, both verified against a real
375
- * server in the ADR 0015 spike:
376
- *
377
- * - It can legitimately *lead* the local document (it carries other peers'
378
- * counters). Exporting `from` a leading watermark does not error; the blob
379
- * correctly contains only the local peer's new ops. So a collaborative merge
380
- * does not force a pull before pushing.
381
- * - Therefore every emptiness/coverage test must be one-directional containment,
382
- * never equality. See {@link covers}.
383
- *
384
- * Lifecycle: one `LoroDoc` per document, shared across agent loops with a
385
- * refcount held by the caller's session registry. Rebuilding the doc per tool
386
- * call would mint a new peer each time and permanently inflate the document's
387
- * version vector for every future reader.
388
- *
389
- * Not thread-safe by design and it does not need to be: harness tool calls run
390
- * serially (`execToolCalls` is a `for` + `await`).
391
- */
392
- declare class ManualSyncDoc {
393
- private readonly client;
394
- private readonly doc;
395
- private readonly adapter;
396
- readonly editor: SemanticEditor;
397
- /**
398
- * The version the server is known to hold. Starts empty (nothing confirmed)
399
- * and only ever moves forward on a verdict that proves the server took our ops.
400
- */
401
- private watermark;
402
- private constructor();
403
- /**
404
- * Open an existing server document.
405
- *
406
- * Fetches the snapshot up front rather than starting empty and converging: the
407
- * agent's first act is to read the document, so there is no useful state before
408
- * the snapshot lands. This also fails fast and loudly on a document that does
409
- * not exist or whose first snapshot is incomplete or invalid.
410
- */
411
- static open(options: ManualSyncDocOptions): Promise<ManualSyncDoc>;
412
- /** Current document read model (authoritative shape). */
413
- snapshot(): VideoDocument;
414
- /**
415
- * The Loro peer this document writes as.
416
- *
417
- * Exposed because the peer is an externally-meaningful fact, not an internal
418
- * detail: it is the identity every op this document emits is attributed to, and
419
- * callers mint it under rules of their own (the harness reserves a range so a
420
- * peer id alone says "Agent wrote this"). Being able to read it back means those
421
- * rules can be verified against the live document rather than against whatever
422
- * was passed to the constructor.
423
- */
424
- editorPeerId(): PeerID;
425
- /** Current content projected into the compatible track and part read shape. */
426
- content(): VideoDraftContent;
427
- /**
428
- * Mark the document state the caller has just observed, for a later
429
- * {@link hasChangedSince}.
430
- *
431
- * This pair replaces the legacy integer-version comparison that
432
- * `DraftVersionDetector` used to tell the LLM "the draft was modified
433
- * externally, reload before editing". Comparing Loro version vectors covers
434
- * all collaborative edits without a business revision field in the document.
435
- *
436
- * Same capability, not a stronger one: like the legacy detector, this only
437
- * reports what changed between two moments the caller chose to sample.
438
- */
439
- versionMark(): DocVersionMark;
440
- /**
441
- * Has the document moved since `mark` was taken?
442
- *
443
- * Reports any advance, whoever caused it — including this document's own edits.
444
- * The caller decides what is interesting: a detector sampling once per turn is
445
- * asking "did anything happen", and its own edits legitimately count.
446
- */
447
- hasChangedSince(mark: DocVersionMark): boolean;
448
- /**
449
- * Fetch and merge everything the server has that this document lacks.
450
- *
451
- * Must run *before* the editor on each tool call. `SemanticEditor` validates
452
- * against the local document, so editing a stale one validates against a world
453
- * that no longer exists: the ADR 0015 spike confirmed that without pull-first
454
- * an edit to a clip another writer had already deleted passes validation and is
455
- * accepted by the server. Pulling afterwards cannot undo that.
456
- *
457
- * A failure is returned, not thrown — a transient network blip must not make
458
- * the tool unusable (M2/A ruling; legacy tolerates read failures the same way).
459
- * The caller proceeds on a possibly-stale document knowingly.
460
- */
461
- pull(): Promise<PullResult>;
462
- /**
463
- * Push every local op the server has not confirmed, and report the verdict.
464
- *
465
- * The return value is the durability answer a tool needs before claiming
466
- * success: only `ack` / `duplicate` mean the server holds the ops. This is why
467
- * this class exists rather than an ack-waiter — with a direct call, "did it
468
- * land" is simply the result.
469
- *
470
- * `duplicate` counts as durable: the bytes added nothing *because* the server
471
- * already had them.
472
- */
473
- push(): Promise<PushResult>;
474
- /** Decode a wire `server_vv`, tolerating absence/corruption (never throws). */
475
- private serverVVFrom;
476
- }
477
- //#endregion
478
- //#region src/storage/medeo-http-doc-storage.d.ts
479
- interface MedeoHttpDocStorageOptions {
480
- docId: string;
481
- client: MengineHttpClient;
482
- sseReconnectDelayMs?: number;
483
- readonlyMode?: boolean;
484
- }
485
- /**
486
- * What the server did with one pushed update, as observed by this storage.
487
- *
488
- * `ack` / `duplicate` mirror the server verdict. `rejected` is a refusal on the
489
- * merits (carrying the machine `code`: `missing_dependency` is retryable after
490
- * catch-up, `corrupt_update` never is). `failed` is everything else — transport
491
- * error, non-2xx status, dead network — i.e. the server's answer is unknown.
492
- *
493
- * The four are kept distinct because the recovery differs per case, and because
494
- * the whole point of M1 is that a caller can tell "written" from "not written".
495
- */
496
- type PushOutcomeKind = 'ack' | 'duplicate' | 'rejected' | 'failed';
497
- interface PushOutcome {
498
- kind: PushOutcomeKind;
499
- /** Server-allocated sequence number; only present for `ack`. */
500
- updateSeq?: number | undefined;
501
- /**
502
- * The server oplog version vector *after* handling this push (`ack` and
503
- * `duplicate` both carry it; encoded `VersionVector`).
504
- *
505
- * This is what an "is my write durable" waiter keys on, and it is deliberately
506
- * the ONLY correlation handle here. An earlier revision also carried the pushed
507
- * bytes so a waiter could match its own update; that was removed because byte
508
- * identity is not a reliable match — the same ops can reach the server either
509
- * as the individual blob the push job carried or as a merged
510
- * `export({from: serverVV})` blob produced by the synchronizer's sync path.
511
- * Version coverage is reliable, and it makes `duplicate` satisfy a waiter
512
- * correctly: the bytes appended nothing precisely because the server already
513
- * held them. Offering both invited the wrong one.
514
- */
515
- serverVV?: Uint8Array | undefined;
516
- /** Server machine code for `rejected` (e.g. `missing_dependency`). */
517
- code?: string | undefined;
518
- /** The underlying error for `rejected` / `failed`. */
519
- error?: Error | undefined;
520
- }
521
- /**
522
- * Adapts the mengine-server HTTP/SSE protocol to the engine `DocStorage`
523
- * contract so `ClientServerSynchronizer` can treat it as a remote peer.
524
- *
525
- * Deliberately thin (mirrors the socket `DocStorage` in the playground): it
526
- * forwards live SSE updates and exposes a version-vector diff, and keeps NO
527
- * sync state of its own.
528
- *
529
- * - `getDocDiff(docId, knownVersion)` pulls the server-computed VV-diff via
530
- * `GET /sync?from=<vv>` — the synchronizer passes the real `doc.version()`, so
531
- * the response carries exactly the ops the doc is missing. `getDoc` (full
532
- * `/snapshot`) stays for cold start, when the caller holds no version yet.
533
- * - `pushDocUpdate` forwards a Loro update; the server appends it.
534
- * - `subscribeDocUpdate` registers a callback for live SSE updates. It does no
535
- * catch-up and keeps no cursor: after an SSE drop the connection reports a
536
- * status change, and the synchronizer re-runs its cycle to catch up via
537
- * `getDocDiff(doc.version())`. `LoroDoc.import` is idempotent (OpId/VV), so
538
- * re-forwarded or echoed updates are harmless.
539
- *
540
- * It is bound to a single `docId` because `MengineHttpClient` is per-document.
541
- */
542
- declare class MedeoHttpDocStorage implements DocStorage {
543
- private readonly options;
544
- readonly connection: Connection;
545
- private readonly client;
546
- private readonly docId;
547
- private readonly events;
548
- constructor(options: MedeoHttpDocStorageOptions);
549
- get isReadonly(): boolean;
550
- getDoc(docId: string): Promise<DocSnapshotRecord | null>;
551
- getDocDiff(docId: string, knownVersion?: Uint8Array): Promise<DocDiff | null>;
552
- /**
553
- * Forward one update to the server, returning what the server says it now holds.
554
- *
555
- * The returned `server_vv` is the server's own statement about itself, computed
556
- * inside the write transaction. A caller tracking "what the remote has" can
557
- * adopt it directly, which is strictly better than inferring that bound from
558
- * the pushed blob: it also covers ops other peers wrote, so those stop being
559
- * re-sent on every later push. `ack` and `duplicate` both carry it.
560
- *
561
- * The verdict itself stays on {@link subscribePushOutcome}, which reports
562
- * failures too — a return value cannot. Previously the verdict was read and
563
- * dropped, so a `duplicate` (bytes contributed nothing) was indistinguishable
564
- * from a successful write.
565
- *
566
- * The error is still rethrown after being published: the synchronizer treats a
567
- * throw as "retry this cycle", and swallowing it here would strand the update.
568
- * Publishing is therefore additive observability, not error handling.
569
- */
570
- pushDocUpdate(update: DocUpdate, _origin: unknown): Promise<DocPushReceipt>;
571
- /**
572
- * Observe the server's verdict for every pushed update, including failures.
573
- *
574
- * This is the loud channel the `void`-returning `DocStorage.pushDocUpdate`
575
- * cannot express. Consumers that need "did my write land" (the agent's
576
- * tool-level ack wait) subscribe here.
577
- */
578
- subscribePushOutcome(callback: (outcome: PushOutcome) => void): () => void;
579
- /** Observe live hints that require VV catch-up without replacing the SSE connection. */
580
- subscribeResync(callback: () => void): () => void;
581
- /** Authoritative HTTP reads and accepted pushes, never inferred from SSE bytes. */
582
- subscribeServerVersion(callback: (version: Uint8Array) => void): () => void;
583
- private reportServerVersion;
584
- deleteDoc(_docId: string): Promise<void>;
585
- subscribeDocUpdate(callback: (update: DocUpdate, origin: unknown) => void): () => void;
586
- private assertDocId;
587
- private emitUpdate;
588
- }
589
- //#endregion
590
- //#region src/session/document-undo-manager.d.ts
591
- /** Availability for this document's current editing session, not server revisions. */
592
- interface MengineUndoState {
593
- readonly canUndo: boolean;
594
- readonly canRedo: boolean;
595
- }
596
- //#endregion
597
- //#region src/session/types.d.ts
598
- /**
599
- * Local storage contract the runtime depends on. Aliased to the engine
600
- * `DocStorage` so browsers can inject `IndexedDBDocStorage` and Node can inject
601
- * `MemoryDocStorage` without medeo-client taking a hard dependency on either
602
- * concrete implementation.
603
- */
604
- type DocStorageLike = DocStorage;
605
- //#endregion
606
- //#region src/session/mengine-doc-session.d.ts
607
- interface MengineDocSessionUpdateEvent {
608
- source: 'remote' | 'local';
609
- snapshot: VideoDocument;
610
- }
611
- /**
612
- * Raised by {@link MengineDocSession.waitForServerAck} when the local edits it
613
- * was asked to confirm were not acknowledged as durable.
614
- *
615
- * Named `...AckFailed`, not `...AckTimeout`: two of the three `reason` values are
616
- * not timeouts, and the earlier name made callers reach for a retry-after-delay
617
- * that is wrong for `rejected`.
618
- *
619
- * The distinction the caller needs is "was it written": if this throws, treat the
620
- * write as NOT durable. `reason` says which failure it was, and `code` carries the
621
- * server's machine code when the push was rejected on its merits:
622
- *
623
- * - `timeout` — no verdict within the deadline. Ambiguous by nature: the push may
624
- * still land later. The caller should surface it as unconfirmed, not as "failed".
625
- * - `rejected` — the server refused. `missing_dependency` is retryable after
626
- * catch-up; `corrupt_update` never is.
627
- * - `failed` — transport/network failure; the server's answer is unknown.
628
- */
629
- declare class MengineAckFailedError extends Error {
630
- readonly reason: 'timeout' | 'rejected' | 'failed';
631
- readonly code: string | undefined;
632
- readonly cause: Error | undefined;
633
- constructor(reason: 'timeout' | 'rejected' | 'failed', code: string | undefined, cause: Error | undefined, message: string);
634
- }
635
- interface WaitForServerAckOptions {
636
- /** Deadline in ms. Rejects with reason `timeout` when it elapses. */
637
- timeoutMs?: number;
638
- }
639
- /** Document-level confirmation is independent of local operation completion. */
640
- interface MengineDocSyncState {
641
- /** Remote-only imports do not create a new local confirmation obligation. */
642
- readonly confirmation: 'unknown' | 'pending' | 'confirmed';
643
- /** Transport/activity phase. Idle does not imply confirmation; failure details
644
- * remain available through push outcomes and confirmation wait errors. */
645
- readonly phase: 'connecting' | 'idle' | 'syncing' | 'offline' | 'failed';
646
- }
647
- interface MengineDocSessionOptions {
648
- docId: string;
649
- client: MengineHttpClient;
650
- peerId?: PeerID;
651
- /**
652
- * Local storage peer. Defaults to in-memory so the session works in Node.
653
- * Browsers should pass an `IndexedDBDocStorage` for refresh/cross-tab support.
654
- */
655
- localStorage?: DocStorageLike;
656
- sseReconnectDelayMs?: number;
657
- }
658
- /**
659
- * A live editing session for one Medeo document — the single entry point clients
660
- * (FE draft driver, Agent, embeds) use to open, edit, and observe a document.
661
- *
662
- * SemanticEditor writes through MirrorVideoDocumentAdapter directly, while
663
- * DocumentUndoManager observes the same LoroDoc independently. Both mutation
664
- * paths use the session's DocumentMutationGuard, closed before teardown.
665
- *
666
- * `DocManager` owns the `LoroDoc`: local edits committed on the adapter are
667
- * picked up via `subscribeLocalUpdates`, saved to local storage, then pushed to
668
- * the server by the synchronizer. Remote SSE updates flow server → synchronizer
669
- * → local storage → manager → `LoroDoc`. A single `LoroDoc.subscribe` turns any
670
- * resulting change into a snapshot event, so callers never track update ids by
671
- * hand.
672
- *
673
- * Replaces the per-consumer hand-written pull/SSE loops that previously lived in
674
- * the standalone client session and `MengineDraftDriver` with one verified path.
675
- */
676
- declare class MengineDocSession {
677
- private readonly options;
678
- private readonly docId;
679
- private readonly local;
680
- private readonly server;
681
- private readonly synchronizer;
682
- private readonly manager;
683
- private readonly events;
684
- private readonly disposables;
685
- private readonly mutationGuard;
686
- private adapterValue;
687
- private editorValue;
688
- private undoManagerValue;
689
- private started;
690
- private startTask;
691
- private destroyed;
692
- private lifetime;
693
- private hasConnected;
694
- private initialVersionValue;
695
- private localVersionValue;
696
- private pushErrorValue;
697
- /**
698
- * Latest server oplog version seen on an authoritative HTTP response. Lets `waitForServerAck`
699
- * return without waiting when the server is already known to cover the local
700
- * doc (e.g. nothing was edited since the last confirmed push).
701
- */
702
- private serverVVValue;
703
- constructor(options: MengineDocSessionOptions);
704
- /**
705
- * Observe every push verdict, including `duplicate` and failures.
706
- *
707
- * Exposed so a host can log/meter the four outcomes (rfc/05 §3 asks for
708
- * explicit ack/rejected semantics). Most callers want the higher-level
709
- * {@link waitForServerAck} instead.
710
- */
711
- subscribePushOutcome(callback: (outcome: PushOutcome) => void): () => void;
712
- /**
713
- * Resolve once the server has durably accepted the local commits as of
714
- * *now* — the capability an Agent tool needs to answer "did my edit land?"
715
- * before reporting success.
716
- *
717
- * Call it right after the editor ops whose durability matters. It snapshots the
718
- * latest local commit's version (or the loaded baseline before any edit) and
719
- * resolves when an authoritative server version covers it. Remote-only
720
- * imports do not extend this target, matching the displayed confirmation state.
721
- *
722
- * Version coverage, not byte identity, is the acceptance test — for two reasons:
723
- * the ops may reach the server inside a merged blob rather than as the exact
724
- * bytes a push job carried, and a `duplicate` verdict then correctly satisfies
725
- * the wait (the bytes appended nothing *because* the server already had them).
726
- * A `rejected` / `failed` outcome rejects immediately with that reason rather
727
- * than burning the whole timeout, since neither will resolve by waiting.
728
- *
729
- * Returns early when the server is already known to be current, so a caller
730
- * with nothing outstanding does not block.
731
- *
732
- * @throws {MengineAckFailedError} on timeout, rejection, or transport failure.
733
- */
734
- waitForServerAck(options?: WaitForServerAckOptions): Promise<void>;
735
- /**
736
- * The editor for local edits. Each op method validates, writes, and commits
737
- * itself as one SemanticOp (single commit carrying its audit message), so
738
- * callers just call `session.editor.someOp(...)` — there is no separate commit
739
- * step. The committed change drives DocManager's local-update push.
740
- */
741
- get editor(): SemanticEditor;
742
- /** Current document snapshot (read model). */
743
- snapshot(): VideoDocument;
744
- /** Current-session undo/redo availability. Loading and closed sessions report false. */
745
- getUndoState(): MengineUndoState;
746
- /**
747
- * Supplies current availability immediately, then coalesces stack changes in a
748
- * microtask. Session teardown publishes empty availability synchronously.
749
- */
750
- subscribeUndoState(cb: (state: MengineUndoState) => void): () => void;
751
- /**
752
- * Undo a local edit immediately; server confirmation remains asynchronous.
753
- * On a shared LWW field this writes its prior value, potentially overwriting a
754
- * later remote edit to that same field. Returns false if no update is produced.
755
- */
756
- undo(options?: CommitOptions): boolean;
757
- /**
758
- * Restore the state visible before undo without rerunning external side effects.
759
- * Returns false if no update is produced; durable confirmation is separate.
760
- */
761
- redo(options?: CommitOptions): boolean;
762
- /**
763
- * Combine existing semantic operations (e.g. trim a clip and adjust its volume)
764
- * or repeated commits during one drag gesture into a single undo step. A gesture
765
- * that only previews locally and commits once on release needs no group.
766
- * Each commit is immediately visible locally and enters sync independently;
767
- * a later failure does not roll it back.
768
- * Always pair with endUndoGroup in a finally block. Conflicting remote imports
769
- * can end/split the group early, and unrelated local edits would join it.
770
- * Complete asynchronous asset preparation before opening the group.
771
- */
772
- beginUndoGroup(): void;
773
- /** End a gesture, including one already split by a conflicting remote import. */
774
- endUndoGroup(): void;
775
- /**
776
- * Start the sync stack and connect the document.
777
- *
778
- * Returns after DocManager loads a local snapshot, or after the first remote
779
- * sync when no local snapshot exists. A valid empty document is ready too.
780
- * Cached documents can open before SSE connects; subsequent remote changes
781
- * surface through `subscribe`.
782
- */
783
- start(): Promise<VideoDocument>;
784
- private open;
785
- getSyncState(): MengineDocSyncState;
786
- private confirmationTarget;
787
- /** Immediately supplies current state; unsubscribe when the host changes docs. */
788
- subscribeSyncState(cb: (state: MengineDocSyncState) => void): () => void;
789
- private emitSyncState;
790
- subscribe(cb: (event: MengineDocSessionUpdateEvent) => void): () => void;
791
- onStateChange(cb: (state: DocState) => void): () => void;
792
- getState(): DocState;
793
- destroy(): void;
794
- /** Local absence needs a remote sync; empty business content does not. */
795
- private waitForInitialLoad;
796
- }
797
- //#endregion
798
- //#region src/storage/memory-doc-storage.d.ts
799
- /**
800
- * Runtime-neutral local `DocStorage` backed by in-process memory.
801
- *
802
- * `IndexedDBDocStorage` is the browser-side local storage, but it requires
803
- * `indexedDB`/`idb`, which is absent in Node (FE/agent tests, unit tests, SSR).
804
- * The mengine runtime injects this implementation as the local peer in those
805
- * environments so the same `DocManager` + `ClientServerSynchronizer` wiring
806
- * works without a browser. It mirrors the merge-on-read and update-sequence
807
- * behavior of `IndexedDBDocStorage` so sync semantics are identical.
808
- */
809
- declare class MemoryDocStorage extends BaseDocStorage {
810
- readonly connection: Connection;
811
- private readonly entries;
812
- constructor(options?: DocStorageOptions);
813
- pushDocUpdate(update: DocUpdate, origin: unknown): Promise<DocPushReceipt>;
814
- deleteDoc(docId: string): Promise<void>;
815
- protected getDocSnapshot(docId: string): Promise<DocSnapshotRecord | null>;
816
- protected setDocSnapshot(snapshot: DocSnapshotRecord): Promise<boolean>;
817
- protected getDocUpdates(docId: string): Promise<DocUpdateRecord[]>;
818
- protected markUpdatesMerged(docId: string, updates: DocUpdateRecord[]): Promise<number>;
819
- private entry;
820
- private now;
821
- }
822
- //#endregion
823
- //#region src/timeline-core/types.d.ts
824
- /** Total document duration when there is no real content (matches FE bgm fallback). */
825
- declare const TIMELINE_SKELETON_DURATION_MS = 20000;
826
- /**
827
- * Plain-JSON working shape the timeline-core cascade functions operate on.
828
- *
829
- * It is a flat, lane-as-array view of the document — close to agent-harness's
830
- * `NormalizedDraftRow` and to the legacy `VideoDraft`, deliberately *not* the
831
- * mirror's keyed-track-map storage shape. The editor converts a
832
- * `VideoDocumentDraft` into this view, runs the cascade, and writes the result
833
- * back (see `timeline-core/index.ts`). Keeping the algorithms on a structured
834
- * shape makes them readable and 1:1 comparable with the canonical reference.
835
- *
836
- * All times are integer milliseconds (ADR 0009 §4): callers normalize on the
837
- * way in and the cascade preserves that.
838
- */
839
- interface TimelineDoc {
840
- main_track: TimelineItem[];
841
- /** speech lane (below main). */
842
- speech_track: TimelineItem[];
843
- /** caption lane (above main). */
844
- caption_track: TimelineItem[];
845
- /** bgm lane (below main). */
846
- bgm_track: TimelineItem[];
847
- part_library: Record<string, PartUnion>;
848
- /** body_part_id -> { attachment part_id -> relative_time_position }. */
849
- aggregations: Aggregation[];
850
- timeline: {
851
- duration_ms: number;
852
- unit_time_ms: number;
853
- };
854
- }
855
- /**
856
- * Working item the cascade solves over. `part_id` is the placement identity
857
- * (unique per lane). `time_position` is the authoritative input read from the
858
- * draft; `abs_time_position` is the cascade's solve variable (derived output),
859
- * seeded from `time_position` (or `fallback_abs_ms`) on read.
860
- *
861
- * `fallback_abs_ms` is the authoritative orphan-recovery snapshot carried beside
862
- * `time_position` (RFC 02 §11.1). On read it seeds the solve variable for
863
- * `anchored` items; on write it is refreshed to the freshly solved absolute
864
- * position.
865
- */
866
- interface TimelineItem {
867
- part_id: string;
868
- time_position: TrackItemTimePosition;
869
- abs_time_position: number;
870
- fallback_abs_ms?: number | undefined;
871
- }
872
- interface Aggregation {
873
- body_part_id: string;
874
- attachments: Array<{
875
- part_id: string;
876
- relative_time_position: number;
877
- }>;
878
- }
879
- /** Empty placeholder clip marker: a video_clip part with no backing media. */
880
- declare function isEmptyVideoClip(part: PartUnion | undefined): boolean;
881
- /** Clamp a duration to a non-negative integer (NaN/Infinity/negative → 0). */
882
- declare function safeDurationMs(value: number | undefined): number;
883
- declare function partDurationMs(doc: TimelineDoc, partId: string): number;
884
- //#endregion
885
- //#region src/timeline-core/cascade.d.ts
886
- /**
887
- * Canonical timeline cascade primitives (ADR 0009).
888
- *
889
- * Single source of truth for "how an edit's connected regions move": main-track
890
- * seamless layout, aggregation position sync + reassignment, total-duration
891
- * recompute, speech-overlap resolution, gap filling. Reconciled per ADR 0009 §4:
892
- *
893
- * - product behavior follows the FE current implementation;
894
- * - all times are integer ms — positions/durations are rounded, never floated;
895
- * - function decomposition follows agent-harness (the Python-derived structure).
896
- *
897
- * Every function mutates the `TimelineDoc` in place (the editor runs them inside
898
- * one immer `transact`, so in-place edits diff correctly).
899
- */
900
- /**
901
- * Lay the main track out head-to-tail from 0, rewriting each item's
902
- * `abs_time_position`. Items whose part is missing from the library are dropped.
903
- */
904
- declare function arrangeMainTrackSeamlessly(doc: TimelineDoc): void;
905
- /**
906
- * Sync attached parts' absolute positions from their host:
907
- * speech.abs = host_video.abs + relative_time_position
908
- * caption.abs = speech.abs + caption.start_ms
909
- */
910
- declare function syncAggregatedClipsTimePosition(doc: TimelineDoc): void;
911
- /**
912
- * Reassign each speech to the video clip whose time range contains its start,
913
- * rebuilding `aggregations`. A speech before the first clip or after the last
914
- * falls back to the first / last clip respectively (FE: see §4 note — FE falls
915
- * back to last only; we keep the harness two-sided fallback because a speech
916
- * dragged before clip 0 belonging to the last clip is clearly wrong, and the FE
917
- * single-sided rule is an acknowledged rough edge). `relative_time_position` is
918
- * clamped to a non-negative integer.
919
- */
920
- declare function reassignSpeechesToVideoClipsByTime(doc: TimelineDoc): void;
921
- /**
922
- * Recompute `timeline.duration_ms` as the max end (abs + duration) across main,
923
- * speech, and caption lanes (BGM does not extend the timeline).
924
- *
925
- * BGM has no authoritative duration (RFC 02 / `reference/16` §0b): its effective
926
- * length is always the timeline total, so it is not written back here — the
927
- * projection derives it from `timeline.duration_ms` on read (`partDurationMs`
928
- * returns the timeline total for a bgm part). The empty-document 20s skeleton is
929
- * applied at that read step, not stored.
930
- */
931
- declare function recalculateTimelineDuration(doc: TimelineDoc): void;
932
- /**
933
- * Resolve one speech overlap by shifting the overlapping speech's host video
934
- * (and every clip after it) right. Returns true when one overlap was resolved;
935
- * callers loop until it returns false. The compared range is the speech merged
936
- * with its captions: start = min(speech.start, captions.start) (FE behavior),
937
- * end = max(speech.end, captions.end).
938
- */
939
- declare function resolveSpeechOverlapByShiftingVideos(doc: TimelineDoc): boolean;
940
- /**
941
- * Resolve speech overlaps for one cascade pass. Mirrors the authoritative FE
942
- * `ensureNoOverlappingClips`, which is documented to resolve AT MOST ONE overlap
943
- * per cascade and is invoked exactly once at every FE call site — the supported
944
- * ops each produce at most one new overlap. It is NOT a fixpoint loop: shifting a
945
- * host right also moves every speech anchored to it, so two speeches sharing a
946
- * host can never be separated by shifting. Looping to a "fixed point" there does
947
- * not converge — it accumulates the same overlap every iteration and pushes the
948
- * clip arbitrarily far right (e.g. a sped-up clip landing at ~287k ms instead of
949
- * its seamless slot). A single pass matches FE product behavior and terminates.
950
- */
951
- declare function resolveAllSpeechOverlaps(doc: TimelineDoc): void;
952
- /**
953
- * Make the main track gapless by adjusting/merging empty placeholder clips or
954
- * inserting new ones between real clips. Mirrors the harness four-case rule, but
955
- * the merge of two adjacent empty clips keeps the earlier clip (harness Case 4).
956
- * Requires `makeEmptyPart` to mint a placeholder part (the editor supplies an
957
- * id generator).
958
- */
959
- declare function fillMainTrackTimeGaps(doc: TimelineDoc, makeEmptyPart: (durationMs: number) => {
960
- partId: string;
961
- }): void;
962
- //#endregion
963
- //#region src/timeline-core/entrypoints.d.ts
964
- /**
965
- * The composed cascade the read-side projection runs (RFC 02 §7): the ordered
966
- * step sequence `solveVideoDocument` applies to derive every absolute position,
967
- * aggregation, gap filler, and the total duration from the position-only facts.
968
- *
969
- * `makeEmptyPart` mints placeholder clips for gap filling; the projection
970
- * supplies a bounded id generator.
971
- */
972
- type MakeEmptyPart = (durationMs: number) => {
973
- partId: string;
974
- };
975
- /**
976
- * The full solve pipeline: arrange → sync → reassign → resolve-overlap →
977
- * fill-gaps → recalc. The single cascade the read-side projection runs; ops
978
- * never call it (they write only facts — RFC 02 §7/§10).
979
- */
980
- declare function cascadeAfterVideoClipChanges(doc: TimelineDoc, makeEmptyPart: MakeEmptyPart): void;
981
- //#endregion
982
- //#region src/timeline-core/bridge.d.ts
983
- /**
984
- * Bridge between the authoritative `VideoDocument` (position-only facts) and the
985
- * flat `TimelineDoc` the cascade primitives solve over.
986
- *
987
- * The data flow is single-directional (RFC 02 §7/§10): ops write only facts
988
- * (`time_position`, parts) to `VideoDocument`; absolute time, `part_aggregations`,
989
- * total duration, and gap fillers are NOT stored — the projection derives them
990
- * on read by running the cascade. There is no write-back of solved positions.
991
- *
992
- * - `solveVideoDocument` seeds a `TimelineDoc` from each item's `time_position`,
993
- * runs the full cascade, and returns the derived read-view (abs / aggregations
994
- * / duration). It is the single solve shared by the legacy `VideoDraft`
995
- * projection.
996
- * - `ensureLaneTrack` / `findLaneTrack` locate (or mint) a secondary lane's
997
- * track row in the draft, so ops can write authoritative facts onto the right
998
- * named container (lane = container).
999
- */
1000
- /** The projection-derived read-view of a `VideoDocument` (RFC 02 §7). */
1001
- interface SolvedVideoDocument {
1002
- absByPartId: Map<string, number>;
1003
- aggregations: PartAggregation[];
1004
- durationMs: number;
1005
- partLibrary: Record<string, PartUnion>;
1006
- /**
1007
- * Part ids of the gap fillers this solve minted.
1008
- *
1009
- * They exist only inside the solve: gap filling needs them so the clips after a
1010
- * gap land at the right absolute time, but they are not authoritative state and
1011
- * the read view does not carry them (see `fromVideoDocument`).
1012
- *
1013
- * Reported as ids rather than left for the caller to detect, because the caller
1014
- * *cannot* detect them. The obvious predicate — `origin_media_id === ''` — also
1015
- * matches an empty clip a writer placed on purpose (`batch_replace_video_clip_sequence`
1016
- * documents "Omit to create an empty clip placeholder"), and those are
1017
- * authoritative parts that must survive the projection. The mint callback is the
1018
- * only place that knows the difference.
1019
- *
1020
- * Note this deliberately excludes the fillers that gap filling *extended* rather
1021
- * than minted (`fillMainTrackTimeGaps` cases 1 and 2): those are authoritative
1022
- * empty clips already in `part_library`, and only their length is derived.
1023
- */
1024
- derivedFillerPartIds: Set<string>;
1025
- }
1026
- /**
1027
- * Solve a `VideoDocument` (authoritative, position-only) into its derived
1028
- * read-view: absolute time per item, `part_aggregations`, and total duration.
1029
- * This is the read side of the single-directional flow — never written back.
1030
- */
1031
- declare function solveVideoDocument(document: VideoDocument): SolvedVideoDocument;
1032
- /** The named container a secondary lane lives in. */
1033
- type SecondaryLane = 'speech' | 'caption' | 'bgm';
1034
- /** A lane's track kind: the `video_clip` main lane plus the three secondary lanes. */
1035
- type LaneKind = SecondaryLane | 'video_clip';
1036
- /**
1037
- * The four lanes in top-to-bottom stack order — the order a `tracks` list holds
1038
- * them in (see {@link laneRank}).
1039
- *
1040
- * Exported so a document can be seeded with all four lanes up front. That seed is
1041
- * not cosmetic: {@link ensureLaneTrack} is find-then-mint over a `LoroMovableList`,
1042
- * so two concurrent writers that each mint the same absent lane both keep their
1043
- * row, and the merged document holds two tracks for one lane. For the main lane
1044
- * that is fatal — `videoDocumentSchema` allows at most one `video_clip` track, so
1045
- * the merged document stops being projectable at all, symmetrically on both
1046
- * replicas. Pre-seeding every lane makes `ensureLaneTrack` always take its find
1047
- * branch, which removes the race by construction rather than by detection.
1048
- *
1049
- * What closes the race is that a track with the lane's `parts_kind` EXISTS — the
1050
- * lookup is by kind, not by id. So a seed is only safe if it covers every lane:
1051
- * a partial seed leaves the uncovered lanes exactly as exposed as before.
1052
- */
1053
- declare const LANE_KINDS_IN_STACK_ORDER: readonly LaneKind[];
1054
- /**
1055
- * The conventional track id for a lane (`main_track`, `<kind>_track`). Shared by
1056
- * the up-front seed and {@link ensureLaneTrack}'s lazy mint so a document's lane
1057
- * ids do not depend on which of the two created the track.
1058
- *
1059
- * Ids are cosmetic to the merge itself — lane lookup goes by `parts_kind`, so
1060
- * drifting them apart would not reopen the concurrent-mint race. They matter to
1061
- * readers that address a lane by id (the FE editor's panes, fixtures), which is
1062
- * why there is one convention rather than two.
1063
- */
1064
- declare function laneTrackId(kind: LaneKind): string;
1065
- /**
1066
- * Locate a lane's track row in the single `tracks` list by kind, minting an empty
1067
- * row in lane-stacking order if absent (reference/17 §4: lane = `parts_kind`).
1068
- * Ops use this to write authoritative items onto the right lane. The track id
1069
- * comes from {@link laneTrackId}, shared with the up-front seed.
1070
- *
1071
- * The mint branch is a concurrency hazard, not a convenience: see
1072
- * {@link LANE_KINDS_IN_STACK_ORDER}. A document seeded with all four lanes never
1073
- * reaches it.
1074
- */
1075
- declare function ensureLaneTrack(draft: VideoDocumentDraft, kind: LaneKind): TrackDraft;
1076
- /** Find a secondary lane's track row without minting it. */
1077
- declare function findLaneTrack(draft: VideoDocumentDraft, kind: SecondaryLane): TrackDraft | undefined;
1078
- //#endregion
1079
- //#region src/timeline-core/locate.d.ts
1080
- /**
1081
- * Forward positioning for write-time ops (RFC 02 §9.1) — NOT a cascade.
1082
- *
1083
- * The authoritative model stores only `position`; absolute time is derived at
1084
- * read time by the projection. But two write-time ops legitimately need to turn
1085
- * an *absolute target* into a `relative` `position` fact:
1086
- *
1087
- * - moving / reordering main-track video clips re-parents each affected speech
1088
- * to the video clip its (unchanged) absolute landing now falls in (§9.1);
1089
- * - moving a speech places it at a new absolute time, then anchors it to the
1090
- * host clip it lands in.
1091
- *
1092
- * Both need one forward computation: lay out the main track from flow order +
1093
- * effective durations, find the clip whose range contains the target ms, and
1094
- * emit `{ mode:'anchored', anchorPartId, offsetMs }`. This reads the current
1095
- * facts and writes one new fact — it never solves and writes back the whole
1096
- * layout (that stays the projection's job, read-side only).
1097
- */
1098
- interface MainClipRange {
1099
- partId: string;
1100
- startMs: number;
1101
- endMs: number;
1102
- }
1103
- /**
1104
- * The flow-ordered main-track clip ranges (cumulative effective durations from
1105
- * 0). Empty-media gap fillers are not in authoritative state, so this reflects
1106
- * only the real clips the draft stores.
1107
- */
1108
- declare function mainTrackRanges(draft: VideoDocumentDraft): MainClipRange[];
1109
- /**
1110
- * Pick the host video clip an absolute time lands in, with the harness two-sided
1111
- * fallback: before the first clip → first clip; after the last → last clip.
1112
- * Returns null only when there is no clip at all (caller leaves the item as-is).
1113
- */
1114
- declare function hostForAbsMs(ranges: MainClipRange[], absMs: number): MainClipRange | null;
1115
- /**
1116
- * Build an `anchored` time position anchoring `absMs` to the host clip it lands
1117
- * in (offset clamped to a non-negative integer). Falls back to `absolute` when
1118
- * there is no host clip. Pair with `fallbackAbsMs = absMs` on the item.
1119
- */
1120
- declare function relativePositionForAbs(ranges: MainClipRange[], absMs: number): TrackItemTimePosition;
1121
- //#endregion
1122
- export { type Aggregation, type Attachment, type BgmPart, type CaptionPart, type CaptionStyle, type CommitOptions, DEFAULT_UNIT_TIME_MS, type DerivedItemPosition, type DocStorageLike, type DocVersionMark, IMPLEMENTED_SEMANTIC_OP_KINDS, type ImplementedSemanticOpKind, LANE_KINDS_IN_STACK_ORDER, type LaneKind, type MainClipRange, type MakeEmptyPart, ManualSyncDoc, type ManualSyncDocOptions, MedeoHttpDocStorage, type MedeoHttpDocStorageOptions, MemoryDocStorage, MengineAckFailedError, type MengineAuditEntry, type MengineAuditResponse, MengineDocSession, type MengineDocSessionOptions, type MengineDocSessionUpdateEvent, type MengineDocSyncState, type MengineDocumentVersion, type MengineEventStreamOptions, MengineHttpClient, type MengineHttpClientOptions, MengineHttpRequestError, MenginePushRejectedError, type MenginePushResponse, type MenginePushUpdateResponse, type MengineRejectedResponse, type MengineSnapshotResponse, type MengineSseUpdateEvent, type MengineSyncResponse, type MengineUndoState, type MengineUpdateMeta, MirrorVideoDocumentAdapter, type MirrorVideoDocumentOptions, type OpActor, type PartAggregation, type PartKind, type PartUnion, type PlannedSemanticOpKind, type PullFailureReason, type PullResult, type PushOutcome, type PushOutcomeKind, type PushResult, type PushResultKind, SchemaValidator, type SemanticDocumentAdapter, SemanticEditor, type SemanticOpInput, type SemanticOpKind, type SemanticOpName, type SnapshotReadable, type SolvedVideoDocument, type SpeechHostMap, type SpeechPart, type SpeedShift, TIMELINE_SKELETON_DURATION_MS, type Timeline, type TimelineDoc, type TimelineItem, type Track, type TrackDraft, type TrackItem, type TrackItemDraft, type TrackItemTimePosition, type TransactAudit, ValidationError, type VideoClipPart, type VideoDocument, type VideoDocumentDraft, type VideoDocumentMirrorSchema, VideoDocumentValidationError, type VideoDocumentValidationIssue, type VideoDocumentValidationIssueCode, type VideoDraft, type CaptionPart$1 as VideoDraftCaptionPart, type VideoDraftContent, type VideoDraftPartUnion, type Timeline$1 as VideoDraftTimeline, type Track$1 as VideoDraftTrack, type TrackItem$1 as VideoDraftTrackItem, type WaitForServerAckOptions, arrangeMainTrackSeamlessly, assertValidVideoDocument, base64ToBytes, buildInitialVideoDocument, buildSpeechHostMap, bytesToBase64, cascadeAfterVideoClipChanges, createMirrorVideoDocument, createMirrorVideoDocumentAdapter, decodeDocVersionMark, derivePositionFromAbs, encodeDocVersionMark, ensureLaneTrack, fillMainTrackTimeGaps, findLaneTrack, fromVideoDocument, generatePartId, getAt, hostForAbsMs, isEmptyVideoClip, isImplementedSemanticOpKind, isMap, laneTrackId, mainTrackRanges, partDurationMs, partUnionSchema, readMainTrackItems, readMengineEventStream, readPart, readPartDurationMs, readVideoDocumentFromDraft, reassignSpeechesToVideoClipsByTime, recalculateTimelineDuration, relativePositionForAbs, resolveAllSpeechOverlaps, resolveSpeechOverlapByShiftingVideos, safeDurationMs, index_d_exports as schemas, snapshotToPlain, solveVideoDocument, syncAggregatedClipsTimePosition, toVideoDocument, validateVideoDocument, videoDocumentMirrorSchema, videoDocumentSchema };
1
+ import { $ as Track, A as ContainNode, B as Media, C as Caption, D as CaptionText, E as CaptionStyle, F as EntityTable, G as ResourceSystem, H as RelationTable, I as EntityTables, J as TextEdit, K as RichTextSnapshot, L as EntityValues, M as DslDiagnostic, N as DslMutation, O as Clip, P as EntityMutation, Q as TimeAnchor, R as FieldPatch, S as AudioScript, T as CaptionSelection, U as RelationTables, V as RelationMutation, W as RelationValues, X as TextMutation, Y as TextMark, Z as TextRun, _ as defaultIdFactory, a as VideoDraftCaptionStyle, b as tupleHash, c as VideoDraftInput, d as VideoDraftTrack, et as TrackRole, f as VideoDraftTrackItem, g as EntityIdKind, h as DslIdFactory, i as VideoDraft, j as DeepReadonly, k as ContainMutation, l as VideoDraftPartUnion, m as OpActor, n as DEFAULT_UNIT_TIME_MS, o as VideoDraftContent, p as CommitOptions, q as TextDelta, r as VideoCreationSettings, s as VideoDraftContentPartUnion, t as CaptionDisplayCue, tt as Voice, u as VideoDraftTimeline, v as relationId, w as CaptionContent, x as Asset, y as resourceId, z as MedeoDslSnapshot } from "./video-draft-types-c_shaRyq.js";
2
+ import { A as AddDslClip, C as MedeoDslDraft, D as captionContentCodec, E as InvalidAtomic, F as ReplaceDslClipsInput, I as ClipPlacement, M as ClipSource, N as DslClipUpdate, O as isInvalidAtomic, P as MoveDslClipsInput, S as documentFormat, T as medeoDslLoroShape, _ as MedeoDsl, a as toVideoDraft, b as UnsupportedDocumentFormatError, c as DslLayout, d as createInitialMedeoDsl, f as DslEditor, g as CreateMedeoDslOptions, h as VoiceoverResult, i as VideoDraftProjection, j as AddDslClipsInput, k as timeAnchorCodec, l as DslProjectionError, m as VoiceoverCaptionInput, n as ImportedVideoDraft, o as resolveDslLayout, p as ApplyVoiceoverInput, r as fromVideoDraft, s as DslClipLayout, t as FromVideoDraftOptions, u as DslTrackLayout, v as DslAudit, w as MedeoDslLoroState, x as assertDslDocument, y as DslTransaction } from "./index-DKDMTXUe.js";
3
+ import { A as MengineAuditResponse, B as base64ToBytes, C as MengineEventStreamOptions, D as MengineHttpRequestError, E as MengineHttpClientOptions, F as MengineRejectedResponse, I as MengineSnapshotResponse, L as MengineSseUpdateEvent, M as MengineDraftResponse, N as MenginePushResponse, O as MenginePushRejectedError, P as MenginePushUpdateResponse, R as MengineSyncResponse, S as PushResultKind, T as MengineHttpClient, V as bytesToBase64, _ as encodeDocVersionMark, a as MengineDocSessionOptions, b as PullResult, c as DocStorageLike, d as MedeoHttpDocStorageOptions, f as PushOutcome, g as decodeDocVersionMark, i as MengineAckFailedError, j as MengineDocumentVersion, k as MengineAuditEntry, l as MengineUndoState, m as ManualSyncDocOptions, o as MengineDocSyncState, p as PushOutcomeKind, s as WaitForServerAckOptions, t as MemoryDocStorage, u as MedeoHttpDocStorage, v as DocVersionMark, w as readMengineEventStream, x as PushResult, y as PullFailureReason, z as MengineUpdateMeta } from "./index-B7fC7q7P.js";
4
+ import { n as MengineDocSessionUpdateEvent, r as ManualSyncDoc, t as MengineDocSession } from "./index-DQZUkrpg.js";
5
+ export { type AddDslClip, type AddDslClipsInput, type ApplyVoiceoverInput, type Asset, type AudioScript, type Caption, type CaptionContent, type CaptionDisplayCue, type CaptionSelection, type CaptionStyle, type CaptionText, type Clip, type ClipPlacement, type ClipSource, type CommitOptions, type ContainMutation, type ContainNode, type CreateMedeoDslOptions, DEFAULT_UNIT_TIME_MS, type DeepReadonly, type DocStorageLike, type DocVersionMark, type DslAudit, type DslClipLayout, type DslClipUpdate, type DslDiagnostic, DslEditor, type DslIdFactory, type DslLayout, type DslMutation, DslProjectionError, type DslTrackLayout, type DslTransaction, type EntityIdKind, type EntityMutation, type EntityTable, type EntityTables, type EntityValues, type FieldPatch, type FromVideoDraftOptions, type ImportedVideoDraft, type InvalidAtomic, ManualSyncDoc, type ManualSyncDocOptions, MedeoDsl, type MedeoDslDraft, type MedeoDslLoroState, type MedeoDslSnapshot, MedeoHttpDocStorage, type MedeoHttpDocStorageOptions, type Media, MemoryDocStorage, MengineAckFailedError, type MengineAuditEntry, type MengineAuditResponse, MengineDocSession, type MengineDocSessionOptions, type MengineDocSessionUpdateEvent, type MengineDocSyncState, type MengineDocumentVersion, type MengineDraftResponse, type MengineEventStreamOptions, MengineHttpClient, type MengineHttpClientOptions, MengineHttpRequestError, MenginePushRejectedError, type MenginePushResponse, type MenginePushUpdateResponse, type MengineRejectedResponse, type MengineSnapshotResponse, type MengineSseUpdateEvent, type MengineSyncResponse, type MengineUndoState, type MengineUpdateMeta, type MoveDslClipsInput, type OpActor, type PullFailureReason, type PullResult, type PushOutcome, type PushOutcomeKind, type PushResult, type PushResultKind, type RelationMutation, type RelationTable, type RelationTables, type RelationValues, type ReplaceDslClipsInput, type ResourceSystem, type RichTextSnapshot, type TextDelta, type TextEdit, type TextMark, type TextMutation, type TextRun, type TimeAnchor, type Track, type TrackRole, UnsupportedDocumentFormatError, type VideoCreationSettings, type VideoDraft, type VideoDraftCaptionStyle, type VideoDraftContent, type VideoDraftContentPartUnion, type VideoDraftInput, type VideoDraftPartUnion, type VideoDraftProjection, type VideoDraftTimeline, type VideoDraftTrack, type VideoDraftTrackItem, type Voice, type VoiceoverCaptionInput, type VoiceoverResult, type WaitForServerAckOptions, assertDslDocument, base64ToBytes, bytesToBase64, captionContentCodec, createInitialMedeoDsl, decodeDocVersionMark, defaultIdFactory, documentFormat, encodeDocVersionMark, fromVideoDraft, isInvalidAtomic, medeoDslLoroShape, readMengineEventStream, relationId, resolveDslLayout, resourceId, timeAnchorCodec, toVideoDraft, tupleHash };