import type { ListmonkClient } from "@listmonk-ops/openapi"; export interface TemplateVersionSnapshot { id: number; name: string; type: string; subject: string; body: string; bodySource?: string; } export interface TemplateRegistryVersion { versionId: string; capturedAt: string; /** Global order reserved immediately before the remote template read. */ captureOrder?: number; /** Latest template observation order already merged when this read began. */ previousCaptureOrder?: number; /** Prior observed version in this write epoch, or its active starting version. */ previousVersionId?: string; /** Registry head revision observed immediately before this capture's read. */ captureHeadRevision?: number; hash: string; note?: string; snapshot: TemplateVersionSnapshot; } export interface TemplateRegistryTemplateRecord { templateId: number; templateName: string; /** * The stored version whose content is live in Listmonk, as far as the * registry last observed: a sync points it at the capture of the live * content, and a promotion or rollback at the version it wrote. An * unpinned rollback re-verifies it against the live template. */ activeVersionId?: string; /** * Version whose content matched the latest current observation or managed * write. */ latestObservedVersionId?: string; /** * Monotonic counter of registry-managed template writes. It advances * even when a write restores the same active version, so a pinned retry * can tell an untouched registry from one that went A → X → A. */ headRevision?: number; /** Newest sequenced read, including unchanged syncs and verified no-ops. */ latestCaptureOrder?: number; versions: TemplateRegistryVersion[]; } export interface TemplateRegistryStore { version: 1; /** Monotonic order assigned to each template read across concurrent clients. */ captureSequence?: number; templates: Record; } export interface TemplateRegistrySyncOptions { templateIds?: number[]; note?: string; onCaptureError?: (failure: Readonly<{ templateId: number; error: unknown; }>) => void | Promise; } export interface TemplateRegistrySyncResult { storePath: string; capturedAt: string; createdVersions: number; unchangedTemplates: number; errors: string[]; templates: Array<{ templateId: number; templateName: string; versionId?: string; changed: boolean; hash: string; }>; } export interface TemplatePromoteResult { templateId: number; templateName: string; versionId: string; activeVersionId: string; /** Registry head revision after this promotion; echo it to pin a later rollback retry. */ headRevision: number; promotedAt: string; /** False when the target version already matched the remote template (a no-op that issues no write). */ promoted: boolean; } export interface TemplateRollbackResult { templateId: number; templateName: string; versionId: string; activeVersionId: string; /** Registry head revision after this rollback; echo it to pin a retry. */ headRevision: number; promotedAt: string; /** False when the requested rollback was already applied. */ rolledBack: boolean; } export declare class TemplateRegistryWriteTransactionError extends Error { constructor(message: string, cause: unknown); } /** * The live Listmonk template changed outside the registry since the last * sync: its content matches neither the active version nor the latest * capture, so the registry cannot tell which stored version is live. An * unpinned rollback fails closed with this error instead of guessing a * target. */ export declare class TemplateRegistryDriftError extends Error { readonly templateId: number; readonly liveHash: string; readonly activeVersionId?: string; /** Older stored versions whose content equals the live template. */ readonly matchingVersionIds: readonly string[]; constructor(details: Readonly<{ templateId: number; liveHash: string; activeVersionId?: string; matchingVersionIds: readonly string[]; }>); } export type TemplateLiveVersionResolution = Readonly<{ /** Which registry reference identified the live content. */ status: "active" | "latest"; version: TemplateRegistryVersion; }> | Readonly<{ status: "drifted"; /** Older stored versions whose content equals the live template. */ matchingVersionIds: readonly string[]; }>; /** * The snapshot Listmonk stores when the registry writes `snapshot`. Verified * against Listmonk 6.2: an update stores a non-transactional template's name * as its subject. That is the only rewrite accepted as the written version; * any other difference — such as a body_source Listmonk kept — is a * different version. Returns `snapshot` itself when the write stores it * unchanged. */ export declare function normalizeTemplateSnapshotForWrite(snapshot: TemplateVersionSnapshot): TemplateVersionSnapshot; /** * Resolve which stored version is live in Listmonk from the live content * hash. The active version wins when it holds the live content: it * disambiguates duplicate content, such as an older version a promotion or * rollback made active. Otherwise the latest observed version matches when * nothing changed since the last observation. Other content is treated as * registry-external drift, even when it equals an older stored version whose * position in the live history is unknown. A sync applies the same rule, so * rollback resolves exactly the version a sync would mark active without * recording anything new. */ export declare function resolveTemplateLiveVersion(record: Pick, liveHash: string): TemplateLiveVersionResolution; /** * Select the recorded predecessor of the version resolved as live. A linked * `previousVersionId` takes precedence; older versions without a link fall * back to capture order. Unpinned, live content the registry cannot place * fails closed with {@link TemplateRegistryDriftError}. An explicit * `toVersionId` must still equal the selected target, so a pinned retry after * an intervening change conflicts instead of rolling elsewhere; under drift * the pin authorizes overwriting the unrecorded live content, and the target * stays relative to the registry's active version. */ export declare function selectTemplateRollbackTarget(record: Pick, liveHash: string, toVersionId?: string): TemplateRegistryVersion; export declare function syncTemplateRegistry(client: ListmonkClient, options?: TemplateRegistrySyncOptions): Promise; export declare function getTemplateRegistryHistory(templateId: number): Promise<{ storePath: string; templateId: number; templateName: string; activeVersionId?: string; headRevision: number; versions: TemplateRegistryVersion[]; }>; export declare function promoteTemplateVersion(client: ListmonkClient, templateId: number, versionId: string, options?: { expectedRemoteHash?: string; force?: boolean; }): Promise; export declare function rollbackTemplateVersion(client: ListmonkClient, templateId: number, options?: { /** * Target pin: the version the caller expects the rollback to write. * It must be the version preceding the live one (or already active), * so a retry after the registry moved conflicts instead of rolling * elsewhere. When the live content changed outside the registry, the * pin authorizes overwriting it relative to the active version. */ toVersionId?: string; /** * Source pin: the active version the caller observed; a mismatch * conflicts. A cycle that promotes the original version back restores * this pin's match — pair it with the head pin to catch it. */ fromVersionId?: string; /** * Head pin: the registry head revision the caller observed (echo the * head_revision from the original attempt or registry-history). Unlike * the source pin it survives an A → B → A cycle that restores both the * version id and the remote content, because the counter moved on. */ expectedHeadRevision?: number; /** Remote drift pin: the remote template hash the caller observed. */ expectedRemoteHash?: string; }): Promise;