import type { MaybeRefOrGetter, Ref, ShallowRef } from 'vue'; import * as Y from 'yjs'; import { Awareness } from 'y-protocols/awareness'; import { HocuspocusProvider } from '@hocuspocus/provider'; import type { Resource } from '@opencloud-eu/web-client'; import type { YjsAdapter } from './types'; export declare const YjsStatus: { readonly Connecting: "connecting"; readonly Connected: "connected"; readonly Disconnected: "disconnected"; readonly Local: "local"; }; export type YjsStatus = (typeof YjsStatus)[keyof typeof YjsStatus]; /** * Whether a session has a transport state worth showing. A session that is local only or has * not started yet collaborates with nobody, so there is nothing to report. */ export declare function hasVisibleYjsStatus(status: YjsStatus | null): boolean; /** * Why the Yjs server refused the handshake. Mirrors `DeniedReason` in * `services/yjs/src/server.ts`, keep the two in sync. */ export declare const YjsDeniedReason: { readonly TokenInvalid: "token-invalid"; readonly AccessDenied: "access-denied"; readonly MalformedDocument: "malformed-document"; readonly ServerError: "server-error"; }; export type YjsDeniedReason = (typeof YjsDeniedReason)[keyof typeof YjsDeniedReason]; export interface YjsSessionOptions { /** The file the session is bound to. Its id forms the room name, its etag drives staleness detection. */ resource: MaybeRefOrGetter; /** Native file content, used to seed an empty Y.Doc. */ currentContent: MaybeRefOrGetter; /** * Holds the session back until `currentContent` has been fetched. Starting * earlier would hydrate - and publish to every peer - an empty document. */ enabled: MaybeRefOrGetter; /** * Read-only clients never seed the shared room and never recover a stale * doc. They only hydrate a private copy while the room is empty, see * `runInitialHydration`. */ isReadOnly: MaybeRefOrGetter; /** Translates between the native file format and the doc's shared types. */ adapter: MaybeRefOrGetter; /** * Namespace for the Yjs room. Editor apps with incompatible Y.Doc schemas * can open the same file, so they MUST land in separate rooms. */ documentPrefix: MaybeRefOrGetter; /** The Y.Doc changed through a real edit (local or remote). */ onContentChange: (content: string) => void; /** A peer saved: the given content is now what's on disk. */ onServerContentChange: (content: string) => void; /** A peer save propagated a fresh etag through the room. */ onEtagChange: (etag: string) => void; /** Whether the caller holds edits that are not on disk yet. */ hasUnsavedChanges: MaybeRefOrGetter; /** The session gave up the room because the file changed outside it. */ onConflict: () => void; /** The room has been updated with an external write. Fired once per update. */ onExternalUpdate?: () => void; } /** Outcome of {@link YjsSession.applyExternalUpdate}. */ export type ExternalUpdateResult = 'recovered' | 'conflicted' | 'skipped'; export interface YjsSession { ydoc: ShallowRef; awareness: ShallowRef; provider: ShallowRef; status: ShallowRef; /** * False until initial sync and the hydration decision have settled. In * remote mode that decision is a round trip to the Yjs server. Consumers * gate the editor mount on this to avoid a brief empty-editor flash while * hydration runs. */ isReady: ShallowRef; /** * True after a forced disconnect. The editor * should stay mounted with the last-known content but flip read-only, and * the user should be asked to reload. */ isLockedForReload: Ref; /** Set when the persisted state was stale or Yjs auth failed. */ error: ShallowRef; /** * True after the session left the room over an external change it cannot * follow without discarding the caller's unsaved work. The Y.Doc stays, so * the editor keeps showing that work. */ isConflicted: ShallowRef; /** * Give up the room because the file moved outside it. Idempotent, and a * no-op in local mode. `notify` off when the caller already surfaced the * conflict itself. */ markConflicted: (notify?: boolean) => void; /** * Whether the file now on disk under `etag` was written by this room. * A peer's save is already merged into our Y.Doc, so republishing over it * loses nothing. A write from anywhere else never entered the room, so * overwriting it would destroy content nobody here has seen. */ wasWrittenByRoom: (etag: string, timeoutMs?: number) => Promise; /** * Announce that the caller is about to write the content it is holding * right now, so the session stamps the matching doc state once the write * lands - the doc usually moves on while the PUT is in flight. */ beginSave: () => void; /** * The merged room state, serialized now and registered as what the caller * is about to write. Used to retry a conflicted save without dropping the * peer edits that caused the conflict. */ serializeMerged: () => Promise; /** * Whether a write announced with this etag came from inside the room, i.e. * the room's etag stamp already names it. Always false in local mode. */ isRoomWrite: (etag: string) => boolean; /** * Stamp the given etag on the room without claiming a save, so peers keep * their dirty state. */ adoptEtag: (etag: string) => void; /** * The file changed outside the room and the caller fetched the new body. * Recovers the room from it when nothing is unsaved, otherwise flags the * room and conflicts. */ applyExternalUpdate: (update: { content: string; etag: string; }) => Promise; } /** * How long an SSE-triggered check waits for a peer's etag stamp before it * treats the write as external. */ export declare const EXTERNAL_UPDATE_ETAG_GRACE_MS = 2000; export declare function buildYjsRoomName({ documentPrefix, fileId, webVersion }: { documentPrefix?: string; fileId: string; webVersion?: string; }): string; /** * Owns a Yjs session for a single file: the Y.Doc, the optional Hocuspocus * provider, hydration and stale-state recovery. * * It knows nothing about editors. The caller mounts whatever editor it likes * against the returned `ydoc` / `awareness`, and supplies a {@link YjsAdapter} * that translates between the native file format and the doc's shared types. */ export declare function useYjsSession(options: YjsSessionOptions): YjsSession;