/** * The persistence contract. * * Mango does not know how a host stores annotations, and should not: the same * viewer runs against a static file, a repository's own API, and a IIIF * annotation server. What it does know is the shape of the conversation — * pages are loaded, annotations are created and updated and deleted, a create * may come back with a different identifier than it went out with, and an * update may lose a race. * * Only a host-callback adapter ships. A W3C Annotation Protocol adapter would * be a second implementation of this interface and nothing above it would * change, which is the point of writing the interface down before there is a * second one. */ import { type CanonicalAnnotation, type CanonicalAnnotationPage, type JsonObject } from '@mango-iiif/w3c-parser'; import type { ResolvedAnnotation } from '../../iiif/annotationResolver'; /** Why a repository call failed, in terms the UI can act on. */ export type RepositoryErrorKind = /** The host rejected the change; retrying unchanged will fail again. */ 'rejected' /** Someone else changed it first. The user has to choose what to keep. */ | 'conflict' /** The caller is not permitted to do this. */ | 'forbidden' /** Transport failed. Retrying may work. */ | 'network' /** The request was abandoned. */ | 'cancelled'; export declare class RepositoryError extends Error { readonly kind: RepositoryErrorKind; /** The version the server holds, when it told us. */ readonly serverVersion?: string | undefined; constructor(kind: RepositoryErrorKind, message: string, /** The version the server holds, when it told us. */ serverVersion?: string | undefined); get retryable(): boolean; } /** An annotation as stored, with whatever the host uses for optimistic locking. */ export type StoredAnnotation = { annotation: CanonicalAnnotation; /** ETag or revision. Passed back on update so the host can detect conflicts. */ version?: string; }; export type LoadedPage = { page: CanonicalAnnotationPage; annotations: ResolvedAnnotation[]; versions: Map; }; export type AnnotationRepository = { /** Loads one page of annotations. */ loadPage(pageId: string, options?: { signal?: AbortSignal; }): Promise; /** Lists the pages available for a Canvas. */ listPages(canvasId: string, options?: { signal?: AbortSignal; }): Promise; /** * Creates an annotation. * * The returned annotation is authoritative: a host that mints identifiers * server-side returns one carrying the assigned id, and the caller replaces * the draft id with it rather than assuming the one it sent was kept. */ create(annotation: CanonicalAnnotation, options?: { pageId?: string; signal?: AbortSignal; }): Promise; update(annotation: CanonicalAnnotation, options?: { version?: string; signal?: AbortSignal; }): Promise; delete(annotationId: string, options?: { version?: string; signal?: AbortSignal; }): Promise; }; /** * What a host implements. * * Plain JSON in both directions, so a host can wire this to `fetch`, to a * framework's data layer, or to an in-memory store in a test, without importing * anything from Mango. Every callback is optional; an absent one means the * operation is unsupported and is reported as `forbidden` rather than failing * silently. */ export type AnnotationHostCallbacks = { loadPage?: (pageId: string, signal?: AbortSignal) => Promise; listPages?: (canvasId: string, signal?: AbortSignal) => Promise; create?: (annotation: JsonObject, context: { pageId?: string; signal?: AbortSignal; }) => Promise<{ annotation?: unknown; id?: string; version?: string; } | void>; update?: (annotation: JsonObject, context: { version?: string; signal?: AbortSignal; }) => Promise<{ annotation?: unknown; version?: string; } | void>; delete?: (annotationId: string, context: { version?: string; signal?: AbortSignal; }) => Promise; }; export declare const createHostRepository: (callbacks: AnnotationHostCallbacks) => AnnotationRepository; /** * Swaps a draft identifier for the one the server assigned. * * Done across the page rather than on the annotation alone, so page membership * and any reference to the draft id move together. Half-updating leaves the * page pointing at an identifier that no longer exists, which surfaces later as * an annotation that cannot be selected. */ export declare const adoptAssignedId: (page: CanonicalAnnotationPage, draftId: string, assignedId: string) => CanonicalAnnotationPage;