@mengine/medeo-client 2.1.0 → 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.
@@ -0,0 +1,728 @@
1
+ import { M as DslDiagnostic, o as VideoDraftContent, p as CommitOptions } from "./video-draft-types-c_shaRyq.js";
2
+ import { LoroDoc, PeerID, VersionVector } 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 MengineDraftResponse {
12
+ content: VideoDraftContent;
13
+ diagnostics: readonly DslDiagnostic[];
14
+ version: MengineDocumentVersion;
15
+ }
16
+ interface MengineDocumentVersion {
17
+ update_seq: number;
18
+ /** base64 `VersionVector.encode` of the server oplog — the sync anchor. */
19
+ server_vv: string;
20
+ /** base64 `encodeFrontiers` of the server oplog heads. */
21
+ frontiers: string;
22
+ }
23
+ interface MengineSnapshotResponse {
24
+ snapshot: string;
25
+ version: MengineDocumentVersion;
26
+ }
27
+ /** Business + causal metadata for one update (audit / rollback anchor). */
28
+ interface MengineUpdateMeta {
29
+ semantic_op: string | null;
30
+ payload: unknown;
31
+ intent: string | null;
32
+ /**
33
+ * Who authored the op, as recorded in the Loro commit message (see `OpActor`).
34
+ * Null for writes with no acting user — bootstrap, repair — and for Changes
35
+ * written before attribution existed. Self-reported by the authoring client;
36
+ * the server does not verify it against the request identity.
37
+ */
38
+ actor: {
39
+ user_id: string;
40
+ role: string;
41
+ } | null;
42
+ message: string | null;
43
+ parse_error: boolean;
44
+ peer: string;
45
+ counter: number;
46
+ lamport: number;
47
+ timestamp: number;
48
+ frontiers: string;
49
+ }
50
+ /**
51
+ * Response to `GET .../sync?from=<vv b64>`: the ops the caller is missing as one
52
+ * merged Loro update blob (`export({mode:'update', from})`) plus the server's
53
+ * current oplog VV. Loro resolves the causal partial order inside the blob, so
54
+ * there is no per-update envelope; `import` is idempotent, so an already-current
55
+ * caller gets a framing-only blob that applies as a no-op. Mirrors the server's
56
+ * `SyncWireResponse`.
57
+ */
58
+ interface MengineSyncResponse {
59
+ /** base64 merged Loro update blob (empty string when the caller is current). */
60
+ update: string;
61
+ server_vv: string;
62
+ }
63
+ interface MengineAuditEntry extends MengineUpdateMeta {
64
+ update_seq: number;
65
+ }
66
+ interface MengineAuditResponse {
67
+ entries: MengineAuditEntry[];
68
+ }
69
+ interface MenginePushUpdateResponse {
70
+ kind: 'ack' | 'duplicate';
71
+ update_seq: number | null;
72
+ version: MengineDocumentVersion;
73
+ }
74
+ interface MengineRejectedResponse {
75
+ kind: 'rejected';
76
+ code: string;
77
+ message: string;
78
+ server_version: MengineDocumentVersion;
79
+ }
80
+ interface MengineSseUpdateEvent {
81
+ update_seq: number;
82
+ updates: string[];
83
+ meta: MengineUpdateMeta;
84
+ version: MengineDocumentVersion;
85
+ }
86
+ type MenginePushResponse = MenginePushUpdateResponse | MengineRejectedResponse;
87
+ //#endregion
88
+ //#region src/client/http-client.d.ts
89
+ interface MengineHttpClientOptions {
90
+ docId: string;
91
+ httpOrigin: string;
92
+ /**
93
+ * Bearer token for the `authorization` header. Accepts a static string or a
94
+ * getter evaluated per request — prefer the getter in the browser editor so
95
+ * each request carries the current login JWT (same credential the REST link
96
+ * sends), rather than a value snapshotted before login hydrates or gone stale
97
+ * after a refresh. A literal `user_dev` is the local dev/relay-test stub.
98
+ */
99
+ authToken?: string | (() => string | undefined);
100
+ /**
101
+ * The end-user id sent as the `medeo-user-id` header. Accepts either a static
102
+ * string or a getter evaluated per request. Prefer the getter when the login
103
+ * state hydrates asynchronously (e.g. the browser editor): the client is
104
+ * constructed eagerly but each request reads the latest id, so an id that is
105
+ * not yet ready at construction time is picked up once it settles — no need to
106
+ * defer client/session creation until auth is ready.
107
+ */
108
+ userId?: string | (() => string | undefined);
109
+ fetchImpl?: typeof fetch;
110
+ }
111
+ declare class MengineHttpRequestError extends Error {
112
+ readonly status: number;
113
+ readonly payload: unknown;
114
+ constructor(status: number, payload: unknown);
115
+ }
116
+ /**
117
+ * A push the server refused on its merits (`kind: 'rejected'`), as opposed to a
118
+ * transport failure. Carries the server's machine `code` so callers can branch on
119
+ * *why* rather than on an HTTP status:
120
+ *
121
+ * - `missing_dependency` (server answers 409) — retryable: the update depends on
122
+ * ops the server log lacks, so catching up and re-exporting resolves it.
123
+ * - `corrupt_update` (server answers 422) — never valid, retrying cannot help.
124
+ *
125
+ * Distinct from {@link MengineHttpRequestError} (transport/status-level failure)
126
+ * and from a raw `fetch` rejection (network down): the three are separate classes
127
+ * so a caller can tell "the server said no" from "the server never answered".
128
+ */
129
+ declare class MenginePushRejectedError extends Error {
130
+ readonly code: string;
131
+ readonly serverMessage: string;
132
+ readonly serverVersion: MengineDocumentVersion | undefined;
133
+ readonly status: number | undefined;
134
+ constructor(code: string, serverMessage: string, serverVersion: MengineDocumentVersion | undefined, status: number | undefined);
135
+ }
136
+ declare class MengineHttpClient {
137
+ private readonly options;
138
+ private readonly fetchImpl;
139
+ constructor(options: MengineHttpClientOptions);
140
+ fetchSnapshot(): Promise<MengineSnapshotResponse>;
141
+ /** Read-only compatibility content and diagnostics at the same causal version. */
142
+ fetchDraft(): Promise<MengineDraftResponse>;
143
+ fetchDraftAt(updateSeq: number): Promise<MengineDraftResponse>;
144
+ /**
145
+ * Loro VV-diff pull: send the caller's oplog `VersionVector.encode` as `from`
146
+ * (omit for a full pull) and receive exactly the updates it is missing plus
147
+ * the server's current VV. Replaces the old integer `after_update_id` cursor.
148
+ */
149
+ sync(fromVV?: Uint8Array): Promise<MengineSyncResponse>;
150
+ /** Audit trail: extracted metadata per accepted update, in log order. */
151
+ audit(): Promise<MengineAuditResponse>;
152
+ /**
153
+ * Append one Loro update to the server log.
154
+ *
155
+ * Resolves for both accepted outcomes and hands the verdict back verbatim —
156
+ * `ack` (appended, `update_seq` allocated) and `duplicate` (already known, no
157
+ * row appended). A `duplicate` is NOT an error, but it is also not an ack: it
158
+ * means the bytes contributed nothing, so a caller waiting for its own write to
159
+ * land must be able to tell them apart. Hence the verdict is returned rather
160
+ * than collapsed into `void`.
161
+ *
162
+ * Rejections raise {@link MenginePushRejectedError} carrying the server's
163
+ * machine `code`. The server answers them with 409/422, so the failure arrives
164
+ * as a non-ok response; this method re-reads the parsed body to recover `code` /
165
+ * `server_version` instead of leaving the caller a bare status. Transport-level
166
+ * failures stay {@link MengineHttpRequestError}, and a dead network keeps
167
+ * surfacing as the underlying `fetch` rejection.
168
+ */
169
+ pushUpdate(update: Uint8Array): Promise<MenginePushUpdateResponse>;
170
+ eventsUrl(): string;
171
+ headers(): Headers;
172
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
173
+ private requestJson;
174
+ private endpoint;
175
+ }
176
+ //#endregion
177
+ //#region src/client/sse.d.ts
178
+ interface MengineEventStreamOptions {
179
+ client: MengineHttpClient;
180
+ signal?: AbortSignal;
181
+ /** Fired once the stream response is established (HTTP ok, body readable). */
182
+ onOpen?(): void;
183
+ onUpdate(event: MengineSseUpdateEvent): void;
184
+ /** A committed update omitted from live transport; fetch the missing operations by VV. */
185
+ onResync?(): void;
186
+ }
187
+ declare function readMengineEventStream(options: MengineEventStreamOptions): Promise<void>;
188
+ //#endregion
189
+ //#region src/manual-sync/types.d.ts
190
+ /**
191
+ * Opaque marker for "the document state I last looked at".
192
+ *
193
+ * Deliberately not a number. Under mengine there is no monotonic document
194
+ * version to compare: `meta.version` is stamped once at legacy ingest and then
195
+ * frozen, and the server's `update_seq` counts every writer's pushes, so neither
196
+ * can answer "has this document changed since I last read it". The answer comes
197
+ * from comparing version vectors.
198
+ *
199
+ * Opaque means: no arithmetic, no ordering, no comparison other than handing it
200
+ * back to {@link ManualSyncDoc.hasChangedSince}. Two version vectors are only
201
+ * partially ordered — concurrent ones are neither greater nor equal, and Loro's
202
+ * own `compare` returns `undefined` for them — so `mark1 > mark2` or sorting a
203
+ * list of marks has no meaning to get wrong.
204
+ *
205
+ * Persisting one IS supported, via {@link encodeDocVersionMark} /
206
+ * {@link decodeDocVersionMark}. Storing an opaque blob and reading it back does
207
+ * not order or compute anything, and callers that compare across turns (rather
208
+ * than within one process) need it: the legacy integer version this replaces was
209
+ * itself persisted between agent turns.
210
+ */
211
+ interface DocVersionMark {
212
+ readonly __brand: 'mengine-doc-version-mark';
213
+ /** Encoded oplog `VersionVector` at the moment the mark was taken. */
214
+ readonly encoded: Uint8Array;
215
+ }
216
+ /** Why a `pull()` did not complete. Mirrors the push failure taxonomy. */
217
+ type PullFailureReason = 'failed';
218
+ /**
219
+ * Result of a `pull()`.
220
+ *
221
+ * `ok: false` is a first-class outcome, not an exception: per the M2/A ruling a
222
+ * failed pull must not hard-fail the tool call, matching legacy's tolerance for
223
+ * read failures (`getDraftVersion` swallows to null, `DraftVersionDetector`
224
+ * catches and skips). The caller decides whether to proceed on a possibly-stale
225
+ * document, so the degradation has to be visible in the return value.
226
+ */
227
+ type PullResult = {
228
+ ok: true; /** True when the pull actually advanced the local document. */
229
+ changed: boolean;
230
+ } | {
231
+ ok: false;
232
+ reason: PullFailureReason;
233
+ error: Error;
234
+ };
235
+ /**
236
+ * What the server did with a `push()`.
237
+ *
238
+ * The first four mirror the server's verdict taxonomy (see `PushOutcomeKind`);
239
+ * `nothing_to_push` is a local short-circuit — no request was made because the
240
+ * document holds no ops the server lacks.
241
+ */
242
+ type PushResultKind = 'ack' | 'duplicate' | 'rejected' | 'failed' | 'nothing_to_push';
243
+ /**
244
+ * Result of a `push()`.
245
+ *
246
+ * `collaborated` is the load-bearing field. The legacy Drizzle path fails loudly
247
+ * on concurrent writes (`Optimistic lock failed`); mengine merges silently by
248
+ * design (M0/S2 accepted CRDT merge semantics). Migrating without surfacing this
249
+ * would replace a path that reports conflicts with one that hides them, so every
250
+ * successful push says whether the server held ops the local document did not.
251
+ *
252
+ * Its meaning is "the server has ops you don't", NOT "there was a conflict" — the
253
+ * same user's other browser tab counts. It is therefore suitable for driving a
254
+ * re-pull and for informing the caller, but not for raising an alarm on its own.
255
+ */
256
+ interface PushResult {
257
+ kind: PushResultKind;
258
+ /** Server document sequence observed after a successful `ack` or `duplicate`. */
259
+ mengineUpdateSeq?: number | undefined;
260
+ /** @deprecated Ack-only sequence retained until consumers adopt `mengineUpdateSeq`. */
261
+ updateSeq?: number | undefined;
262
+ /** Whether the server held ops the local document lacked. See above. */
263
+ collaborated: boolean;
264
+ /** Server machine code for `rejected` (`missing_dependency` / `corrupt_update`). */
265
+ code?: string | undefined;
266
+ /** Underlying error for `rejected` / `failed`. */
267
+ error?: Error | undefined;
268
+ }
269
+ //#endregion
270
+ //#region src/manual-sync/doc-version-mark.d.ts
271
+ /**
272
+ * Encode a {@link DocVersionMark} for storage or transport.
273
+ *
274
+ * The mark stays opaque across the round trip — the string is not a version
275
+ * number and must not be compared, ordered, or parsed. Its only use is
276
+ * {@link decodeDocVersionMark} followed by `hasChangedSince`.
277
+ *
278
+ * Callers that persist this should know the encoded length grows with the number
279
+ * of peers that have ever written to the document (one counter each), and the FE
280
+ * mints a fresh peer per page load. Still small in practice (a few hundred bytes
281
+ * for dozens of peers), but it grows with document age rather than size; version
282
+ * vector compaction is deferred to a later phase.
283
+ */
284
+ declare function encodeDocVersionMark(mark: DocVersionMark): string;
285
+ /**
286
+ * Rebuild a mark from {@link encodeDocVersionMark}'s output.
287
+ *
288
+ * Returns `undefined` for input this did not produce (a legacy integer version,
289
+ * a truncated value, an empty string). That is the honest answer — "I cannot
290
+ * establish what you last saw" — and callers should treat it as "no baseline"
291
+ * rather than as "unchanged". Decoding does not validate the bytes as a version
292
+ * vector; `hasChangedSince` reports "changed" for an undecodable mark, which is
293
+ * the conservative direction.
294
+ */
295
+ declare function decodeDocVersionMark(encoded: string): DocVersionMark | undefined;
296
+ //#endregion
297
+ //#region src/manual-sync/manual-sync-transport.d.ts
298
+ interface ManualSyncDocOptions {
299
+ client: MengineHttpClient;
300
+ peerId?: PeerID;
301
+ }
302
+ /** Shared explicit pull/push lifecycle; model-specific editors never implement transport. */
303
+ declare class ManualSyncTransport {
304
+ private readonly client;
305
+ protected readonly doc: LoroDoc;
306
+ private watermark;
307
+ private disposed;
308
+ protected constructor(client: MengineHttpClient, doc: LoroDoc, watermark: VersionVector);
309
+ editorPeerId(): PeerID;
310
+ protected assertActive(): void;
311
+ dispose(): void;
312
+ /**
313
+ * Mark the document state the caller has just observed, for a later
314
+ * {@link hasChangedSince}.
315
+ *
316
+ * This pair replaces the legacy integer-version comparison that
317
+ * `DraftVersionDetector` used to tell the LLM "the draft was modified
318
+ * externally, reload before editing". Comparing Loro version vectors covers
319
+ * all collaborative edits without a business revision field in the document.
320
+ *
321
+ * Same capability, not a stronger one: like the legacy detector, this only
322
+ * reports what changed between two moments the caller chose to sample.
323
+ */
324
+ versionMark(): DocVersionMark;
325
+ /**
326
+ * Has the document moved since `mark` was taken?
327
+ *
328
+ * Reports any advance, whoever caused it — including this document's own edits.
329
+ * The caller decides what is interesting: a detector sampling once per turn is
330
+ * asking "did anything happen", and its own edits legitimately count.
331
+ */
332
+ hasChangedSince(mark: DocVersionMark): boolean;
333
+ /**
334
+ * Fetch and merge everything the server has that this document lacks.
335
+ *
336
+ * Must run *before* the editor on each tool call. The editor validates
337
+ * against the local document, so editing a stale one validates against a world
338
+ * that no longer exists: the ADR 0015 spike confirmed that without pull-first
339
+ * an edit to a clip another writer had already deleted passes validation and is
340
+ * accepted by the server. Pulling afterwards cannot undo that.
341
+ *
342
+ * A failure is returned, not thrown — a transient network blip must not make
343
+ * the tool unusable (M2/A ruling; legacy tolerates read failures the same way).
344
+ * The caller proceeds on a possibly-stale document knowingly.
345
+ */
346
+ pull(): Promise<PullResult>;
347
+ /**
348
+ * Push every local op the server has not confirmed, and report the verdict.
349
+ *
350
+ * The return value is the durability answer a tool needs before claiming
351
+ * success: only `ack` / `duplicate` mean the server holds the ops. This is why
352
+ * this class exists rather than an ack-waiter — with a direct call, "did it
353
+ * land" is simply the result.
354
+ *
355
+ * `duplicate` counts as durable: the bytes added nothing *because* the server
356
+ * already had them.
357
+ */
358
+ push(): Promise<PushResult>;
359
+ /** Decode a wire `server_vv`, tolerating absence/corruption (never throws). */
360
+ private serverVVFrom;
361
+ }
362
+ //#endregion
363
+ //#region src/storage/medeo-http-doc-storage.d.ts
364
+ interface MedeoHttpDocStorageOptions {
365
+ docId: string;
366
+ client: MengineHttpClient;
367
+ sseReconnectDelayMs?: number;
368
+ readonlyMode?: boolean;
369
+ }
370
+ /**
371
+ * What the server did with one pushed update, as observed by this storage.
372
+ *
373
+ * `ack` / `duplicate` mirror the server verdict. `rejected` is a refusal on the
374
+ * merits (carrying the machine `code`: `missing_dependency` is retryable after
375
+ * catch-up, `corrupt_update` never is). `failed` is everything else — transport
376
+ * error, non-2xx status, dead network — i.e. the server's answer is unknown.
377
+ *
378
+ * The four are kept distinct because the recovery differs per case, and because
379
+ * the whole point of M1 is that a caller can tell "written" from "not written".
380
+ */
381
+ type PushOutcomeKind = 'ack' | 'duplicate' | 'rejected' | 'failed';
382
+ interface PushOutcome {
383
+ kind: PushOutcomeKind;
384
+ /** Server-allocated sequence number; only present for `ack`. */
385
+ updateSeq?: number | undefined;
386
+ /**
387
+ * The server oplog version vector *after* handling this push (`ack` and
388
+ * `duplicate` both carry it; encoded `VersionVector`).
389
+ *
390
+ * This is what an "is my write durable" waiter keys on, and it is deliberately
391
+ * the ONLY correlation handle here. An earlier revision also carried the pushed
392
+ * bytes so a waiter could match its own update; that was removed because byte
393
+ * identity is not a reliable match — the same ops can reach the server either
394
+ * as the individual blob the push job carried or as a merged
395
+ * `export({from: serverVV})` blob produced by the synchronizer's sync path.
396
+ * Version coverage is reliable, and it makes `duplicate` satisfy a waiter
397
+ * correctly: the bytes appended nothing precisely because the server already
398
+ * held them. Offering both invited the wrong one.
399
+ */
400
+ serverVV?: Uint8Array | undefined;
401
+ /** Server machine code for `rejected` (e.g. `missing_dependency`). */
402
+ code?: string | undefined;
403
+ /** The underlying error for `rejected` / `failed`. */
404
+ error?: Error | undefined;
405
+ }
406
+ /**
407
+ * Adapts the mengine-server HTTP/SSE protocol to the engine `DocStorage`
408
+ * contract so `ClientServerSynchronizer` can treat it as a remote peer.
409
+ *
410
+ * Deliberately thin (mirrors the socket `DocStorage` in the playground): it
411
+ * forwards live SSE updates and exposes a version-vector diff, and keeps NO
412
+ * sync state of its own.
413
+ *
414
+ * - `getDocDiff(docId, knownVersion)` pulls the server-computed VV-diff via
415
+ * `GET /sync?from=<vv>` — the synchronizer passes the real `doc.version()`, so
416
+ * the response carries exactly the ops the doc is missing. `getDoc` (full
417
+ * `/snapshot`) stays for cold start, when the caller holds no version yet.
418
+ * - `pushDocUpdate` forwards a Loro update; the server appends it.
419
+ * - `subscribeDocUpdate` registers a callback for live SSE updates. It does no
420
+ * catch-up and keeps no cursor: after an SSE drop the connection reports a
421
+ * status change, and the synchronizer re-runs its cycle to catch up via
422
+ * `getDocDiff(doc.version())`. `LoroDoc.import` is idempotent (OpId/VV), so
423
+ * re-forwarded or echoed updates are harmless.
424
+ *
425
+ * It is bound to a single `docId` because `MengineHttpClient` is per-document.
426
+ */
427
+ declare class MedeoHttpDocStorage implements DocStorage {
428
+ private readonly options;
429
+ readonly connection: Connection;
430
+ private readonly client;
431
+ private readonly docId;
432
+ private readonly events;
433
+ constructor(options: MedeoHttpDocStorageOptions);
434
+ get isReadonly(): boolean;
435
+ getDoc(docId: string): Promise<DocSnapshotRecord | null>;
436
+ getDocDiff(docId: string, knownVersion?: Uint8Array): Promise<DocDiff | null>;
437
+ /**
438
+ * Forward one update to the server, returning what the server says it now holds.
439
+ *
440
+ * The returned `server_vv` is the server's own statement about itself, computed
441
+ * inside the write transaction. A caller tracking "what the remote has" can
442
+ * adopt it directly, which is strictly better than inferring that bound from
443
+ * the pushed blob: it also covers ops other peers wrote, so those stop being
444
+ * re-sent on every later push. `ack` and `duplicate` both carry it.
445
+ *
446
+ * The verdict itself stays on {@link subscribePushOutcome}, which reports
447
+ * failures too — a return value cannot. Previously the verdict was read and
448
+ * dropped, so a `duplicate` (bytes contributed nothing) was indistinguishable
449
+ * from a successful write.
450
+ *
451
+ * The error is still rethrown after being published: the synchronizer treats a
452
+ * throw as "retry this cycle", and swallowing it here would strand the update.
453
+ * Publishing is therefore additive observability, not error handling.
454
+ */
455
+ pushDocUpdate(update: DocUpdate, _origin: unknown): Promise<DocPushReceipt>;
456
+ /**
457
+ * Observe the server's verdict for every pushed update, including failures.
458
+ *
459
+ * This is the loud channel the `void`-returning `DocStorage.pushDocUpdate`
460
+ * cannot express. Consumers that need "did my write land" (the agent's
461
+ * tool-level ack wait) subscribe here.
462
+ */
463
+ subscribePushOutcome(callback: (outcome: PushOutcome) => void): () => void;
464
+ /** Observe live hints that require VV catch-up without replacing the SSE connection. */
465
+ subscribeResync(callback: () => void): () => void;
466
+ /** Authoritative HTTP reads and accepted pushes, never inferred from SSE bytes. */
467
+ subscribeServerVersion(callback: (version: Uint8Array) => void): () => void;
468
+ private reportServerVersion;
469
+ deleteDoc(_docId: string): Promise<void>;
470
+ subscribeDocUpdate(callback: (update: DocUpdate, origin: unknown) => void): () => void;
471
+ private assertDocId;
472
+ private emitUpdate;
473
+ }
474
+ //#endregion
475
+ //#region src/session/document-mutation-guard.d.ts
476
+ /**
477
+ * @internal
478
+ * Shared by synchronous mutation entry points for one document. The session
479
+ * closes it before teardown callbacks can attempt another write.
480
+ */
481
+ declare class DocumentMutationGuard {
482
+ private readonly readonlyMode;
483
+ private running;
484
+ private closed;
485
+ constructor(readonlyMode?: boolean);
486
+ run<T>(mutation: () => T): T;
487
+ close(): void;
488
+ }
489
+ //#endregion
490
+ //#region src/session/document-undo-manager.d.ts
491
+ /** Availability for this document's current editing session, not server revisions. */
492
+ interface MengineUndoState {
493
+ readonly canUndo: boolean;
494
+ readonly canRedo: boolean;
495
+ }
496
+ //#endregion
497
+ //#region src/session/types.d.ts
498
+ /**
499
+ * Local storage contract the runtime depends on. Aliased to the engine
500
+ * `DocStorage` so browsers can inject `IndexedDBDocStorage` and Node can inject
501
+ * `MemoryDocStorage` without medeo-client taking a hard dependency on either
502
+ * concrete implementation.
503
+ */
504
+ type DocStorageLike = DocStorage;
505
+ //#endregion
506
+ //#region src/session/document-session.d.ts
507
+ interface DocumentSessionUpdateEvent<Snapshot> {
508
+ source: 'remote' | 'local';
509
+ snapshot: Snapshot;
510
+ }
511
+ /**
512
+ * Raised by {@link DocumentSession.waitForServerAck} when the local edits it
513
+ * was asked to confirm were not acknowledged as durable.
514
+ *
515
+ * Named `...AckFailed`, not `...AckTimeout`: two of the three `reason` values are
516
+ * not timeouts, and the earlier name made callers reach for a retry-after-delay
517
+ * that is wrong for `rejected`.
518
+ *
519
+ * The distinction the caller needs is "was it written": if this throws, treat the
520
+ * write as NOT durable. `reason` says which failure it was, and `code` carries the
521
+ * server's machine code when the push was rejected on its merits:
522
+ *
523
+ * - `timeout` — no verdict within the deadline. Ambiguous by nature: the push may
524
+ * still land later. The caller should surface it as unconfirmed, not as "failed".
525
+ * - `rejected` — the server refused. `missing_dependency` is retryable after
526
+ * catch-up; `corrupt_update` never is.
527
+ * - `failed` — transport/network failure; the server's answer is unknown.
528
+ */
529
+ declare class MengineAckFailedError extends Error {
530
+ readonly reason: 'timeout' | 'rejected' | 'failed';
531
+ readonly code: string | undefined;
532
+ readonly cause: Error | undefined;
533
+ constructor(reason: 'timeout' | 'rejected' | 'failed', code: string | undefined, cause: Error | undefined, message: string);
534
+ }
535
+ interface WaitForServerAckOptions {
536
+ /** Deadline in ms. Rejects with reason `timeout` when it elapses. */
537
+ timeoutMs?: number;
538
+ }
539
+ /** Document-level confirmation is independent of local operation completion. */
540
+ interface MengineDocSyncState {
541
+ /** Remote-only imports do not create a new local confirmation obligation. */
542
+ readonly confirmation: 'unknown' | 'pending' | 'confirmed';
543
+ /** Transport/activity phase. Idle does not imply confirmation; failure details
544
+ * remain available through push outcomes and confirmation wait errors. */
545
+ readonly phase: 'connecting' | 'idle' | 'syncing' | 'offline' | 'failed';
546
+ }
547
+ interface MengineDocSessionOptions {
548
+ docId: string;
549
+ client: MengineHttpClient;
550
+ peerId?: PeerID;
551
+ /**
552
+ * Local storage peer. Defaults to in-memory so the session works in Node.
553
+ * Browsers should pass an `IndexedDBDocStorage` for refresh/cross-tab support.
554
+ */
555
+ localStorage?: DocStorageLike;
556
+ sseReconnectDelayMs?: number;
557
+ /**
558
+ * Keep remote synchronization read-only: pull snapshot/diffs and subscribe to
559
+ * SSE updates, but never POST local Loro updates to the server.
560
+ */
561
+ readonlyMode?: boolean;
562
+ }
563
+ interface DocumentSessionBinding<Snapshot, Editor, Document> {
564
+ doc: LoroDoc;
565
+ document: Document;
566
+ editor: Editor;
567
+ snapshot(): Snapshot;
568
+ subscribe(listener: (source: 'local' | 'remote') => void): () => void;
569
+ dispose(): void;
570
+ }
571
+ type DocumentSessionModel<Snapshot, Editor, Document> = (doc: LoroDoc, guard: DocumentMutationGuard) => DocumentSessionBinding<Snapshot, Editor, Document>;
572
+ /** Shared binary synchronization lifecycle. Models attach only after initial loading succeeds. */
573
+ declare class DocumentSession<Snapshot, Editor, Document> {
574
+ private readonly options;
575
+ private readonly model;
576
+ private readonly docId;
577
+ private readonly local;
578
+ private readonly server;
579
+ private readonly synchronizer;
580
+ private readonly manager;
581
+ private readonly events;
582
+ private readonly disposables;
583
+ private readonly mutationGuard;
584
+ private adapterValue;
585
+ private editorValue;
586
+ private undoManagerValue;
587
+ private started;
588
+ private startTask;
589
+ private destroyed;
590
+ private lifetime;
591
+ private hasConnected;
592
+ private initialVersionValue;
593
+ private localVersionValue;
594
+ private pushErrorValue;
595
+ /**
596
+ * Latest server oplog version seen on an authoritative HTTP response. Lets `waitForServerAck`
597
+ * return without waiting when the server is already known to cover the local
598
+ * doc (e.g. nothing was edited since the last confirmed push).
599
+ */
600
+ private serverVVValue;
601
+ constructor(options: MengineDocSessionOptions, model: DocumentSessionModel<Snapshot, Editor, Document>);
602
+ /**
603
+ * Observe every push verdict, including `duplicate` and failures.
604
+ *
605
+ * Exposed so a host can log/meter the four outcomes (rfc/05 §3 asks for
606
+ * explicit ack/rejected semantics). Most callers want the higher-level
607
+ * {@link waitForServerAck} instead.
608
+ */
609
+ subscribePushOutcome(callback: (outcome: PushOutcome) => void): () => void;
610
+ /**
611
+ * Resolve once the server has durably accepted the local commits as of
612
+ * *now* — the capability an Agent tool needs to answer "did my edit land?"
613
+ * before reporting success.
614
+ *
615
+ * Call it right after the editor ops whose durability matters. It snapshots the
616
+ * latest local commit's version (or the loaded baseline before any edit) and
617
+ * resolves when an authoritative server version covers it. Remote-only
618
+ * imports do not extend this target, matching the displayed confirmation state.
619
+ *
620
+ * Version coverage, not byte identity, is the acceptance test — for two reasons:
621
+ * the ops may reach the server inside a merged blob rather than as the exact
622
+ * bytes a push job carried, and a `duplicate` verdict then correctly satisfies
623
+ * the wait (the bytes appended nothing *because* the server already had them).
624
+ * A `rejected` / `failed` outcome rejects immediately with that reason rather
625
+ * than burning the whole timeout, since neither will resolve by waiting.
626
+ *
627
+ * Returns early when the server is already known to be current, so a caller
628
+ * with nothing outstanding does not block.
629
+ *
630
+ * @throws {MengineAckFailedError} on timeout, rejection, or transport failure.
631
+ */
632
+ waitForServerAck(options?: WaitForServerAckOptions): Promise<void>;
633
+ /**
634
+ * The editor for local edits. Each op method validates, writes, and commits
635
+ * itself as one SemanticOp (single commit carrying its audit message), so
636
+ * callers just call `session.editor.someOp(...)` — there is no separate commit
637
+ * step. The committed change drives DocManager's local-update push.
638
+ */
639
+ get editor(): Editor;
640
+ /**
641
+ * Opaque version token of the local oplog (base64 `VersionVector.encode`).
642
+ * Equality-comparable only: equal means no observed change (local or
643
+ * remote-arrived) since the token was taken. Throws when not started.
644
+ */
645
+ version(): string;
646
+ protected get document(): Document;
647
+ /** Current document snapshot (read model). */
648
+ snapshot(): Snapshot;
649
+ /** Current-session undo/redo availability. Loading and closed sessions report false. */
650
+ getUndoState(): MengineUndoState;
651
+ /**
652
+ * Supplies current availability immediately, then coalesces stack changes in a
653
+ * microtask. Session teardown publishes empty availability synchronously.
654
+ */
655
+ subscribeUndoState(cb: (state: MengineUndoState) => void): () => void;
656
+ /**
657
+ * Undo a local edit immediately; server confirmation remains asynchronous.
658
+ * On a shared LWW field this writes its prior value, potentially overwriting a
659
+ * later remote edit to that same field. Returns false if no update is produced.
660
+ */
661
+ undo(options?: CommitOptions): boolean;
662
+ /**
663
+ * Restore the state visible before undo without rerunning external side effects.
664
+ * Returns false if no update is produced; durable confirmation is separate.
665
+ */
666
+ redo(options?: CommitOptions): boolean;
667
+ /**
668
+ * Combine existing semantic operations (e.g. trim a clip and adjust its volume)
669
+ * or repeated commits during one drag gesture into a single undo step. A gesture
670
+ * that only previews locally and commits once on release needs no group.
671
+ * Each commit is immediately visible locally and enters sync independently;
672
+ * a later failure does not roll it back.
673
+ * Always pair with endUndoGroup in a finally block. Conflicting remote imports
674
+ * can end/split the group early, and unrelated local edits would join it.
675
+ * Complete asynchronous asset preparation before opening the group.
676
+ */
677
+ beginUndoGroup(): void;
678
+ /** End a gesture, including one already split by a conflicting remote import. */
679
+ endUndoGroup(): void;
680
+ /**
681
+ * Start the sync stack and connect the document.
682
+ *
683
+ * Returns after DocManager loads a local snapshot, or after the first remote
684
+ * sync when no local snapshot exists. A valid empty document is ready too.
685
+ * Cached documents can open before SSE connects; subsequent remote changes
686
+ * surface through `subscribe`.
687
+ */
688
+ start(): Promise<Snapshot>;
689
+ private open;
690
+ getSyncState(): MengineDocSyncState;
691
+ private confirmationTarget;
692
+ /** Immediately supplies current state; unsubscribe when the host changes docs. */
693
+ subscribeSyncState(cb: (state: MengineDocSyncState) => void): () => void;
694
+ private emitSyncState;
695
+ subscribe(cb: (event: DocumentSessionUpdateEvent<Snapshot>) => void): () => void;
696
+ onStateChange(cb: (state: DocState) => void): () => void;
697
+ getState(): DocState;
698
+ destroy(): void;
699
+ /** Local absence needs a remote sync; empty business content does not. */
700
+ private waitForInitialLoad;
701
+ }
702
+ //#endregion
703
+ //#region src/storage/memory-doc-storage.d.ts
704
+ /**
705
+ * Runtime-neutral local `DocStorage` backed by in-process memory.
706
+ *
707
+ * `IndexedDBDocStorage` is the browser-side local storage, but it requires
708
+ * `indexedDB`/`idb`, which is absent in Node (FE/agent tests, unit tests, SSR).
709
+ * The mengine runtime injects this implementation as the local peer in those
710
+ * environments so the same `DocManager` + `ClientServerSynchronizer` wiring
711
+ * works without a browser. It mirrors the merge-on-read and update-sequence
712
+ * behavior of `IndexedDBDocStorage` so sync semantics are identical.
713
+ */
714
+ declare class MemoryDocStorage extends BaseDocStorage {
715
+ readonly connection: Connection;
716
+ private readonly entries;
717
+ constructor(options?: DocStorageOptions);
718
+ pushDocUpdate(update: DocUpdate, origin: unknown): Promise<DocPushReceipt>;
719
+ deleteDoc(docId: string): Promise<void>;
720
+ protected getDocSnapshot(docId: string): Promise<DocSnapshotRecord | null>;
721
+ protected setDocSnapshot(snapshot: DocSnapshotRecord): Promise<boolean>;
722
+ protected getDocUpdates(docId: string): Promise<DocUpdateRecord[]>;
723
+ protected markUpdatesMerged(docId: string, updates: DocUpdateRecord[]): Promise<number>;
724
+ private entry;
725
+ private now;
726
+ }
727
+ //#endregion
728
+ export { MengineAuditResponse as A, base64ToBytes as B, MengineEventStreamOptions as C, MengineHttpRequestError as D, MengineHttpClientOptions as E, MengineRejectedResponse as F, MengineSnapshotResponse as I, MengineSseUpdateEvent as L, MengineDraftResponse as M, MenginePushResponse as N, MenginePushRejectedError as O, MenginePushUpdateResponse as P, MengineSyncResponse as R, PushResultKind as S, MengineHttpClient as T, bytesToBase64 as V, encodeDocVersionMark as _, MengineDocSessionOptions as a, PullResult as b, DocStorageLike as c, MedeoHttpDocStorageOptions as d, PushOutcome as f, decodeDocVersionMark as g, ManualSyncTransport as h, MengineAckFailedError as i, MengineDocumentVersion as j, MengineAuditEntry as k, MengineUndoState as l, ManualSyncDocOptions as m, DocumentSession as n, MengineDocSyncState as o, PushOutcomeKind as p, DocumentSessionUpdateEvent as r, WaitForServerAckOptions as s, MemoryDocStorage as t, MedeoHttpDocStorage as u, DocVersionMark as v, readMengineEventStream as w, PushResult as x, PullFailureReason as y, MengineUpdateMeta as z };