sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
1,443 lines • 609 kB
TypeScript
import * as react from "react";
import { CSSProperties, Component, ComponentProps, ComponentType, Dispatch, ElementType, ErrorInfo, FC, FocusEvent, FocusEventHandler, FormEvent, FormEventHandler, HTMLAttributes, HTMLProps, KeyboardEvent, MutableRefObject, PropsWithChildren, PureComponent, ReactElement, ReactNode, Ref, RefAttributes, RefObject, SVGProps, SetStateAction } from "react";
import * as _sanity_types0 from "@sanity/types";
import { ArraySchemaType, AssetMetadataType, AssetSource, AssetSourceSpec, BlockDecoratorDefinition, BlockListDefinition, BlockStyleDefinition, BooleanSchemaType, ConditionalProperty, CrossDatasetReferenceSchemaType, CrossDatasetReferenceValue, CrossDatasetType, CurrentUser, DeprecatedProperty, FieldGroup, File as File$1, FileAsset, FileSchemaType, FileValue, FormNodeValidation, GeopointValue, GlobalDocumentReferenceType, I18nTextRecord, Image, ImageAsset, ImageSchemaType, ImageUrlFitMode, ImageValue, IndexTuple, InitialValueProperty, InitialValueResolverContext, IntrinsicTypeName, KeyedObject, KeyedSegment, MendozaEffectPair, NumberSchemaType, ObjectField, ObjectFieldType, ObjectSchemaType, PatchOperations, Path, PathSegment, PortableTextBlock, PortableTextObject, PortableTextTextBlock, PrepareViewOptions, PreviewValue, Reference, ReferenceSchemaType, ReferenceValue, Role, RuleClass, SanityDocument, SanityDocumentLike, Schema, SchemaType, SchemaTypeDefinition, SearchStrategy, SlugSchemaType, SlugValue, SortOrdering, StrictVersionLayeringOptions, StringSchemaType, TextSchemaType, TransactionLogEventWithEffects, TransactionLogEventWithMutations, UploadState, User, ValidationMarker } from "@sanity/types";
import { BifurClient } from "@sanity/bifur-client";
import * as _sanity_client0 from "@sanity/client";
import { ClientConfig, ClientPerspective, QueryParams, ReconnectEvent, ReleaseDocument, ReleaseDocument as ReleaseDocument$1, ReleaseType, ResetEvent, SanityClient, SanityDocument as SanityDocument$1, SingleActionResult, SingleMutationResult, StackablePerspective, WelcomeBackEvent, WelcomeEvent } from "@sanity/client";
import * as _sanity_ui0 from "@sanity/ui";
import { AvatarPosition, AvatarProps, AvatarSize, AvatarStatus, BadgeProps, BadgeTone, BoxProps, ButtonProps, ButtonTone, CardProps, DialogProps, HotkeysProps, MenuButtonProps, MenuItem, Placement, PopoverProps, ResponsivePaddingProps, ResponsiveWidthProps, Text, TextProps, ThemeColorSchemeKey, ToastParams, TooltipProps } from "@sanity/ui";
import { FlatNamespace, KeyPrefix, Namespace, TFunction, TFunction as TFunction$1, i18n as i18n$1 } from "i18next";
import { MonoTypeOperatorFunction, Observable, OperatorFunction, Subject } from "rxjs";
import { IntentLinkProps, IntentParameters, Router, RouterState, SearchParam } from "sanity/router";
import { ScrollToOptions } from "@tanstack/react-virtual";
import * as _sanity_telemetry0 from "@sanity/telemetry";
import { DRAFTS_FOLDER, DraftId, PublishedId, VERSION_FOLDER, getDraftId, getPublishedId, getVersionFromId, getVersionId, isDraftId, isPublishedId, isVersionId } from "@sanity/client/csm";
import * as sanity__singletons0 from "sanity/_singletons";
import { DocumentDivergencesContextValue, DocumentLimitUpsellContextValue, FieldActionsContextValue, HoveredFieldContextValue, LocaleContextValue, SchedulesContext, SingleDocReleaseContextValue } from "sanity/_singletons";
import { Mutation } from "@sanity/mutator";
import { CanvasResource, FrameMessages, MediaResource, StudioResource, WindowMessages } from "@sanity/message-protocol";
import { DocumentHandle } from "@sanity/sdk";
import { FallbackNs } from "react-i18next";
import { BrowserHistory, HashHistory, History, MemoryHistory } from "history";
import { RootTheme, ThemeColorSchemeKey as ThemeColorSchemeKey$1 } from "@sanity/ui/theme";
import { EditorSelection, HotkeyOptions, InvalidValueResolution, OnCopyFn, OnPasteResultOrPromise, PasteData, Patch, PortableTextEditor, RangeDecoration, RangeDecorationOnMovedDetails, RenderBlockFunction } from "@portabletext/editor";
import { ColorHueKey, ColorTintKey, ColorTints } from "@sanity/color";
import { Subscriber } from "nano-pubsub";
import { ArrayDiff, BooleanDiff, Diff, ItemDiff, NullDiff, NumberDiff, ObjectDiff, StringDiff, StringSegmentChanged, StringSegmentUnchanged, TypeChangeDiff } from "@sanity/diff";
import * as styled_components0 from "styled-components";
import { CSSProperties as CSSProperties$1, ExecutionProps } from "styled-components";
import * as date_fns_formatRelative0 from "date-fns/formatRelative";
import { ResizeObserverEntry } from "@juggle/resize-observer";
import { ThrottleSettings } from "lodash-es/throttle.js";
import { DEFAULT_ANNOTATIONS, DEFAULT_DECORATORS } from "@sanity/schema";
import * as styled_components_dist_types0 from "/home/runner/work/sanity/sanity/node_modules/.pnpm/@sanity+styled-components@6.1.24_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@sanity/styled-components/dist/types.js";
import { PortableTextMemberSchemaTypes } from "@portabletext/sanity-bridge";
import { ImageUrlBuilder, ImageUrlBuilder as ImageUrlBuilder$1 } from "@sanity/image-url";
import { MarkdownShortcutsPluginProps } from "@portabletext/plugin-markdown-shortcuts";
import { PasteLinkPluginProps } from "@portabletext/plugin-paste-link";
import { TypographyPluginProps } from "@portabletext/plugin-typography";
import { Node } from "@sanity/comlink";
interface CreateSanityMediaLibrarySourceProps {
i18nKey?: string;
icon?: React.ComponentType;
libraryId: string | null;
name?: string;
}
/**
* Create a new image asset source for the Media Library
*
* @beta
*/
declare function createSanityMediaLibraryImageSource(props: CreateSanityMediaLibrarySourceProps): AssetSource;
/**
* Create a new file asset source for the Media Library
*
* @beta
*/
declare function createSanityMediaLibraryFileSource(props: CreateSanityMediaLibrarySourceProps): AssetSource;
interface SharedProps$1 {
children?: ReactNode;
header: string;
width: ResponsiveWidthProps['width'];
}
interface DialogProps$2 extends SharedProps$1 {
type: 'dialog';
id?: string;
autofocus?: boolean;
onClose?: () => void;
}
interface PopoverProps$3 extends SharedProps$1 {
type: 'popover';
legacy_referenceElement: HTMLElement | null;
onClose: () => void;
}
/**
* @beta
* Creates a dialog or a popover for editing content.
* Handles presence and virtual scrolling.
*/
declare function EditPortal(props: PopoverProps$3 | DialogProps$2): React.JSX.Element;
interface SharedProps {
children?: ReactNode;
header: string;
width: ResponsiveWidthProps['width'];
}
interface DialogProps$1 extends SharedProps {
type: 'dialog';
id?: string;
autofocus?: boolean;
onClose?: () => void;
}
interface PopoverProps$2 extends SharedProps {
type: 'popover';
id?: string;
legacy_referenceElement: HTMLElement | null;
onClose: () => void;
}
/**
* @beta
* Creates a dialog or a popover for editing content.
* Handles presence and virtual scrolling.
*
* When multiple dialogs are open, only the top-most dialog is visible.
* Non-top dialogs are hidden via CSS while preserving their state.
*/
declare function EnhancedObjectDialog(props: PopoverProps$2 | DialogProps$1): React.JSX.Element;
type JsonObject = { [Key in string]: KeyValueStoreValue } & { [Key in string]?: KeyValueStoreValue | undefined };
type JsonArray = KeyValueStoreValue[] | readonly KeyValueStoreValue[];
type JsonPrimitive = string | number | boolean | null;
/** @internal */
type KeyValueStoreValue = JsonPrimitive | JsonObject | JsonArray;
/** @internal */
interface KeyValueStore {
getKey(key: string): Observable<KeyValueStoreValue | null>;
setKey(key: string, value: KeyValueStoreValue): Promise<KeyValueStoreValue>;
}
/** @internal */
declare function createKeyValueStore(options: {
client: SanityClient;
}): KeyValueStore;
/**
* The interface used by the Studio that produces a `SanityClient` and
* `CurrentUser` that gets passed to the resulting `Workspace`s and `Source`s.
*
* NOTE: This interface is primarily for internal use. Refer to
* `createAuthStore` instead.
*
* @beta
* @hidden
*/
interface AuthStore {
/**
* Emits `AuthState`s. This should update when the user's auth state changes.
* E.g. After a login, a new `AuthState` could be emitted with a non-null
* `currentUser` and `authenticated: true`
*
* NOTE: all auth store implementations should emit on subscribe using
* something like shareReplay(1) to ensure all new subscribers get an
* `AuthState` value on subscribe
*/
state: Observable<AuthState>;
/**
* Emits auth tokens, or `null` if not configured to use them or they do not exist
*/
token?: Observable<string | null>;
/**
* Custom auth stores are expected to implement a UI that initiates the user's
* authentication. For the typical case in `createAuthStore`, this means
* loading the providers and showing them as options to the user.
*/
LoginComponent?: ComponentType<LoginComponentProps>;
/**
* Custom auth stores can implement a function that runs when the user logs
* out. The implementation is expected to remove all credentials both locally
* and on the server.
*/
logout?: () => void;
/**
* Custom auth stores can implement a function that is designated to run when
* the Studio loads (e.g. to trade a session ID for a token in cookie-less
* mode). Within the Studio, this is called within the `AuthBoundary`.
*/
handleCallbackUrl?: () => Promise<HandleCallbackResult>;
}
/**
* The unit an `AuthStore` emits to determine the user's authentication state.
*
* @beta
* @hidden
*/
interface AuthState {
/**
* Similar to a logged-in flag. This state is used in places like the
* `AuthBoundary` to determine whether or not it should render the
* `NotAuthenticatedComponent`. Implementers may choose to set this to `true`
* while also also emitting a `currentUser` of `null` if a `null` user is
* accepted (e.g. a project that doesn't require a login)
*/
authenticated: boolean;
/**
* The value of the user logged in or `null` if none is provided
*/
currentUser: CurrentUser | null;
/**
* A client that is expected to be pre-configured to allow for any downstream
* requests in the Studio
*/
client: SanityClient;
}
/**
* @beta
* @hidden
*/
type LoginComponentProps = {
projectId: string; /** @deprecated use redirectPath instead */
basePath: string;
redirectPath?: string;
} | {
projectId: string;
redirectPath: string; /** @deprecated use redirectPath instead */
basePath?: string;
};
/**
* Result returned from `handleCallbackUrl` describing what happened during
* the auth callback flow. Used for telemetry and diagnostics.
*
* @internal
*/
interface HandleCallbackResult {
/** The login method configured for this auth store (e.g. `'cookie'` or `'token'`). */
loginMethod: LoginMethod;
/**
* Which auth flow was taken:
* - `'already-authenticated'`: User was already authenticated; no exchange needed.
* - `'cookie-auth'`: Attempted cookie-based authentication via `/users/me`.
* - `'token-exchange'`: Traded a session ID for a persistent token.
*/
flow: 'already-authenticated' | 'cookie-auth' | 'token-exchange';
/** Whether the auth flow completed successfully. */
success: boolean;
/** Total wall-clock time for the callback handling, in milliseconds. */
durationMs: number;
/** Time spent on the session-to-token exchange specifically. Only set for `'token-exchange'` flow. */
tokenExchangeDurationMs?: number;
/** Human-readable reason for failure. Only set when `success` is `false`. */
failureReason?: string;
}
/** @internal */
interface AuthStoreOptions extends AuthConfig {
clientFactory?: (options: ClientConfig) => SanityClient;
projectId: string;
dataset: string;
}
/**
* @internal
*/
declare function _createAuthStore({
clientFactory: clientFactoryOption,
projectId,
dataset,
apiHost,
loginMethod,
...providerOptions
}: AuthStoreOptions): AuthStore;
/**
* @internal
*/
declare const createAuthStore: typeof _createAuthStore;
/** @internal */
interface MockAuthStoreOptions {
currentUser: CurrentUser | null;
client: SanityClient;
}
/**
* Creates a mock `AuthStore` (for testing) that emits an `AuthState` derived
* from the `client` and `currentUser` given.
*
* @internal
*/
declare function createMockAuthStore({
client,
currentUser
}: MockAuthStoreOptions): AuthStore;
/** @internal */
declare function getProviderTitle(provider?: string): string | undefined;
/**
* Duck-type check for whether or not this looks like an auth store
*
* @param maybeStore - Item to check if matches the AuthStore interface
* @returns True if auth store, false otherwise
* @internal
*/
declare function isAuthStore(maybeStore: unknown): maybeStore is AuthStore;
/**
* Check whether the provided login method is compatible with cookieless auth, e.g. whether any
* authentication token found in localStorage should be acknowledged.
*
* @internal
*/
declare function isCookielessCompatibleLoginMethod(loginMethod: LoginMethod): loginMethod is CookielessCompatibleLoginMethod;
/** @internal */
interface ConnectionStatusStore {
connectionStatus$: Observable<ConnectionStatus>;
}
/** @internal */
type ConnectingStatus = {
type: 'connecting';
};
/** @internal */
type ErrorStatus = {
type: 'error';
error: Error;
attemptNo: number;
isOffline: boolean;
retryAt: Date;
};
/** @internal */
type RetryingStatus = {
type: 'retrying';
};
/** @internal */
type ConnectedStatus = {
type: 'connected';
lastHeartbeat: Date;
};
/** @internal */
declare const CONNECTING: ConnectingStatus;
/** @internal */
type ConnectionStatus = ConnectingStatus | ErrorStatus | ConnectedStatus | RetryingStatus;
/** @internal */
declare const onRetry: () => void;
/** @internal */
interface ConnectionStatusStoreOptions {
bifur: BifurClient;
}
/**
* This is the beginning of what should be the data store tracking connection status in the Sanity studio.
*
* @internal
*/
declare function createConnectionStatusStore({
bifur
}: ConnectionStatusStoreOptions): ConnectionStatusStore;
/** @internal */
interface CorsOriginErrorOptions {
projectId?: string;
isStaging: boolean;
}
/** @internal */
declare class CorsOriginError extends Error {
projectId?: string;
isStaging: boolean;
constructor({
projectId,
isStaging
}: CorsOriginErrorOptions);
}
/** @internal */
type BetaBadgeProps = Omit<BadgeProps, 'mode' | 'tone'>;
/** @internal */
declare function BetaBadge(props: BetaBadgeProps & Omit<HTMLProps<HTMLDivElement>, 'ref'>): react.JSX.Element;
/** @internal */
type CommandListElementType = 'input' | 'list';
/** @internal */
type CommandListGetItemDisabledCallback = (virtualIndex: number) => boolean;
/** @internal */
type CommandListGetItemKeyCallback = (virtualIndex: number) => number | string;
/** @internal */
type CommandListGetItemSelectedCallback = (virtualIndex: number) => boolean;
/** @internal */
type CommandListItemContext = {
activeIndex: number | null;
disabled?: boolean;
selected?: boolean;
virtualIndex: number;
};
/** @internal */
type CommandListRenderItemCallback<T> = (item: T, context: CommandListItemContext) => ReactNode;
/** @internal */
interface CommandListHandle {
focusInputElement: () => void;
focusListElement: () => void;
getTopIndex: () => number;
scrollToIndex: (index: number) => void;
}
/** @internal */
interface CommandListProps<T = any> extends ResponsivePaddingProps {
/** The data attribute to apply to any active virtual list items */
activeItemDataAttr?: string;
/** `aria-label` to apply to the virtual list container element */
ariaLabel: string;
/** Whether `aria-multiselectable` is enabled on the virtual list container element */
ariaMultiselectable?: boolean;
/** Automatically focus the input or virtual list */
autoFocus?: CommandListElementType;
/** Whether the virtual list can receive focus */
canReceiveFocus?: boolean;
/** Pixel offset of the virtual list focus ring. Negative values will cause the focus ring to appear inset */
focusRingOffset?: number;
/** Force a fixed height for all virtual list children and skip measurement (faster). */
fixedHeight?: boolean;
/** Custom function to map disabled items */
getItemDisabled?: CommandListGetItemDisabledCallback;
/** Custom function to map virtual list items to custom keys */
getItemKey?: CommandListGetItemKeyCallback;
/** Custom function to map selected items */
getItemSelected?: CommandListGetItemSelectedCallback;
/** Scroll alignment of the initial active index */
initialScrollAlign?: ScrollToOptions['align'];
/** Initial active index on mount */
initialIndex?: number;
/** Input element to associate with this virtual list. Associated inputs will receive focus and handle key events */
inputElement?: HTMLInputElement | null;
/** Estimated height for each list item */
itemHeight: number;
/** Virtual list item values, accessible to all rendered item components */
items: T[];
/** Callback fired when the virtual list is within `onEndReachedIndexThreshold` of rendered content */
onEndReached?: () => void;
/** Number of items from the end of the virtual list before which `onEndReached` is triggered */
onEndReachedIndexOffset?: number;
/** Only show selection state when the virtual list is active (is hovered or has focus) */
onlyShowSelectionWhenActive?: boolean;
/** Number of items to render above and below the visible area*/
overscan?: number;
/** Rendered component in virtual lists */
renderItem: CommandListRenderItemCallback<T>;
/** `data-testid` to apply to outermost container */
testId?: string;
/** Allow wraparound keyboard navigation between first and last items */
wrapAround?: boolean;
}
/**
* Renders a Command List with support for the following:
*
* - Keyboard navigation (↑ / ↓ / ENTER) to children with a specified container (`childContainerRef`)
* - Focus redirection when clicking child elements
* - Pointer blocking when navigating with arrow keys (to ensure that only one active state is visible at any given time)
* - ARIA attributes to define a `combobox` input that controls a separate `listbox`
*
* @internal
*/
declare const CommandList: react.NamedExoticComponent<CommandListProps<any> & react.RefAttributes<CommandListHandle>>;
type BaseButtonProps = Pick<ButtonProps, 'as' | 'icon' | 'iconRight' | 'justify' | 'loading' | 'mode' | 'paddingY' | 'paddingLeft' | 'selected' | 'tone' | 'type' | 'width'> & {
size?: 'default' | 'large';
radius?: 'full';
};
type ButtonWithText = {
text: string;
tooltipProps?: TooltipProps$1 | null;
icon?: ButtonProps['icon'];
};
type IconButton = {
text?: undefined;
icon?: ButtonProps['icon'];
/**
* When using a button with an icon, tooltipProps are required to enforce consistency in UI.
*/
tooltipProps: TooltipProps$1 | null;
};
/** @internal */
type ButtonProps$1 = BaseButtonProps & (ButtonWithText | IconButton);
/**
* Customized Sanity UI <Button> with pre-defined layout options.
*
* @internal
*/
declare const Button: react.ForwardRefExoticComponent<(Omit<Pick<ButtonProps, "as" | "icon" | "iconRight" | "justify" | "loading" | "mode" | "paddingLeft" | "paddingY" | "selected" | "tone" | "type" | "width"> & {
size?: "default" | "large" | undefined;
radius?: "full" | undefined;
} & ButtonWithText & Omit<HTMLProps<HTMLButtonElement>, "as" | "size" | "title">, "ref"> | Omit<Pick<ButtonProps, "as" | "icon" | "iconRight" | "justify" | "loading" | "mode" | "paddingLeft" | "paddingY" | "selected" | "tone" | "type" | "width"> & {
size?: "default" | "large" | undefined;
radius?: "full" | undefined;
} & IconButton & Omit<HTMLProps<HTMLButtonElement>, "as" | "size" | "title">, "ref">) & react.RefAttributes<HTMLButtonElement>>;
/** @internal */
type MenuButtonProps$1 = Omit<MenuButtonProps, 'popover'> & {
popover?: Omit<PopoverProps, 'animate' | 'content' | 'open'>;
};
/** @internal */
type TooltipProps$1 = Omit<TooltipProps, 'arrow' | 'padding' | 'shadow'> & {
hotkeys?: HotkeysProps['keys'];
};
/** @internal */
type PopoverProps$1 = PopoverProps;
type ContextMenuButtonProps = Pick<ButtonProps$1, 'mode' | 'selected' | 'size' | 'tone' | 'tooltipProps' | 'loading'>;
/**
* Simple context menu button (with horizontal ellipsis icon) with shared localization.
*
* @internal
*/
declare const ContextMenuButton: react.ForwardRefExoticComponent<ContextMenuButtonProps & Pick<HTMLProps<HTMLButtonElement>, "disabled" | "hidden" | "onClick"> & react.RefAttributes<HTMLButtonElement>>;
/**
* Indicates the type of document variant, either `draft`, `version` or `published`.
* Draft documents are prefixed with `drafts.`.
* Version documents are prefixed with `versions.<versionName>`
* The rest are considered published documents.
* @public
*/
type DocumentVariantType = 'draft' | 'version' | 'published';
/**
* Takes a document id and returns the variant type for that document
* If it's a document that starts with `version.` it's a `version` document.
* If it's a document that starts with `drafts.` it's a `draft` document.
* Otherwise, it's a `published` document.
* @public
* */
declare function getDocumentVariantType(documentId: string): DocumentVariantType;
/**
* @internal
*/
declare const Chip: react.ForwardRefExoticComponent<Omit<Omit<_sanity_ui0.ButtonProps & Omit<react.HTMLProps<HTMLButtonElement>, "as" | "width">, "ref"> & react.RefAttributes<HTMLButtonElement>, "ref"> & react.RefAttributes<unknown>>;
/**
*
* Checks if the document ID `documentId` has the same ID as `equalsDocumentId`,
* ignoring the draft prefix.
*
* @public
*
* @param documentId - The document ID to check
* @param equalsDocumentId - The document ID to check against
*
* @example
* Draft vs published document ID, but representing the same document:
* ```
* // Prints "true":
* console.log(documentIdEquals('drafts.agot', 'agot'));
* ```
* @example
* Different documents:
* ```
* // Prints "false":
* console.log(documentIdEquals('hp-tcos', 'hp-hbp'));
* ```
*
* @returns `true` if the document IDs are equal, `false` otherwise
*/
declare function documentIdEquals(documentId: string, equalsDocumentId: string): boolean;
/** @internal */
declare function isDraft(document: SanityDocumentLike): boolean;
/**
* TODO: Improve return type based on presence of `version` option.
*
* @internal
*/
declare function getIdPair(id: string, {
version
}?: {
version?: string;
}): {
draftId: DraftId;
publishedId: PublishedId;
versionId?: string;
};
/**
* System bundles are sets of documents owned by the system.
*
* - Draft documents contain data that has not yet been published. These documents all exist in the "drafts" path.
* - Published documents contain data that has been published. These documents all exist in the root path.
*
* These differ to user bundles, which are created when a user establishes a custom set of documents
* (e.g. by creating a release).
*
* @public
*/
declare const systemBundles: readonly ["drafts", "published"];
/**
* System bundles are sets of documents owned by the system.
*
* - Draft documents contain data that has not yet been published. These documents all exist in the "drafts" path.
* - Published documents contain data that has been published. These documents all exist in the root path.
*
* These differ to user bundles, which are created when a user establishes a custom set of documents
* (e.g. by creating a release).
*
* @public
*/
type SystemBundle = 'drafts' | 'published';
/** @internal */
declare function isSystemBundle(maybeSystemBundle: unknown): maybeSystemBundle is SystemBundle;
/** @internal */
type SystemBundleName = 'draft' | 'published';
/**
* `isSystemBundle` should be preferred, but some parts of the codebase currently use the singular
* "draft" name instead of the plural "drafts".
*
* @internal
*/
declare function isSystemBundleName(maybeSystemBundleName: unknown): maybeSystemBundleName is SystemBundleName;
/**
* @internal
* Given a perspective stack and a document id, returns true if the document id matches any of the provided perspectives
* e.g. `idMatchesPerspective('['summer'], 'versions.summer.foo') === true`
* e.g. `idMatchesPerspective('['drafts', 'summer'], 'versions.summer.foo') === true`
* e.g. `idMatchesPerspective('['drafts'], 'versions.summer.foo') === false`
* e.g. `idMatchesPerspective('['drafts', 'summer'], 'versions.winter.foo') === false`
*
* Note: a published id will match any perspective
* e.g. `idMatchesPerspective('['drafts', 'summer'], 'foo') === true`
*/
declare function idMatchesPerspective(perspectiveStack: StackablePerspective[], documentId: string): boolean;
/** @internal */
declare function createDraftFrom(document: SanityDocument): SanityDocument;
/** @internal */
declare function newDraftFrom(document: SanityDocument): SanityDocument;
/** @internal */
declare function createPublishedFrom(document: SanityDocument): SanityDocument;
/**
* Takes a list of documents and collates draft/published pairs into single entries
* `{id: <published id>, draft?: <draft document>, published?: <published document>}`
*
* Note: because Map is ordered by insertion key the resulting array will be ordered by whichever
* version appeared first
*
* @internal
*/
interface CollatedHit<T extends {
_id: string;
} = {
_id: string;
}> {
id: string;
type: string;
draft?: T;
published?: T;
versions: T[];
}
/** @internal */
declare function collate<T extends {
_id: string;
_type: string;
}>(documents: T[]): CollatedHit<T>[];
/** @internal */
declare function removeDupes(documents: SanityDocumentLike[]): SanityDocumentLike[];
/**
* @beta
*/
type ReleaseId = string;
/**
* A value representing a perspective, including the data describing it. This is either the name of a
* system bundle, or a document describing a release.
*
* @public
*/
type TargetPerspective = ReleaseDocument | SystemBundle | string;
/**
* @beta
* @deprecated Use `TargetPerspective` instead.
*/
type SelectedPerspective = TargetPerspective;
/**
* @beta
*/
type PerspectiveStack = ExtractArray<ClientPerspective>;
/**
* @beta
*/
interface PerspectiveContextValue {
selectedPerspectiveName: 'published' | ReleaseId | undefined;
/**
* The releaseId as `r<string>`; it will be undefined if the selected perspective is `published` or `drafts`
*/
selectedReleaseId: ReleaseId | undefined;
selectedPerspective: TargetPerspective;
/**
* The stacked array of perspectives ids ordered chronologically to represent the state of documents at the given point in time.
* It can be used as the perspective param in the client to get the correct view of the documents.
* @returns ["published"] | ["drafts"] | ["releaseId2", "releaseId1", "drafts"]
*/
perspectiveStack: PerspectiveStack;
excludedPerspectives: string[];
}
/**
* @internal
*/
type ExtractArray<Union> = Union extends unknown[] ? Union : never;
/**
* @internal
*/
type ReleasesNavMenuItemPropsGetter = (content: {
perspective: TargetPerspective;
}) => Partial<ComponentProps<typeof MenuItem>>;
/**
* @internal
*/
declare const VersionChip: react.NamedExoticComponent<{
disabled?: boolean | undefined;
selected: boolean;
tooltipContent?: ReactNode;
onClick: () => void;
text: string;
contextMenuPortal?: boolean | undefined;
tone: "caution" | "critical" | "default" | "neutral" | "positive" | "primary" | "suggest";
locked?: boolean | undefined;
onCopyToDraftsNavigate: () => void;
contextValues: {
documentId: string;
documentType: string;
releases: ReleaseDocument[];
releasesLoading: boolean;
bundleId: string;
isVersion: boolean;
disabled?: boolean | undefined;
isGoingToUnpublish?: boolean | undefined;
release?: ReleaseDocument | undefined;
};
}>;
/** @internal */
type ReleaseAvatarIconProps = {
release: TargetPerspective;
tone?: never;
releaseType?: never;
} | {
releaseType: ReleaseType;
tone?: never;
release?: never;
} | {
/**
* @deprecated - Prefer `release` or `releaseType`.
*/
tone: BadgeTone;
release?: never;
releaseType?: never;
};
declare const ReleaseAvatarIcon: ({
tone,
release,
releaseType
}: ReleaseAvatarIconProps) => react.JSX.Element;
declare function ReleaseAvatar({
fontSize,
padding,
...iconProps
}: ReleaseAvatarIconProps & {
fontSize?: number;
padding?: number;
}): React.JSX.Element;
/** @internal */
interface ReleaseTitleDetails {
displayTitle: string;
fullTitle: string;
isTruncated: boolean;
}
/** @internal */
interface ReleaseTitleProps {
title: string | undefined;
fallback: string;
enableTooltip?: boolean;
tooltipMaxWidth?: string;
children?: (details: ReleaseTitleDetails) => ReactElement;
textProps?: Omit<TextProps, 'children'> & {
style?: CSSProperties;
};
}
/** @internal */
declare function ReleaseTitle(props: ReleaseTitleProps): ReactNode;
/**
* @internal
*/
declare const VersionInlineBadge: ({
children,
$tone
}: PropsWithChildren<{
$tone?: "caution" | "critical" | "default" | "neutral" | "positive" | "primary" | "suggest" | undefined;
}>) => react.JSX.Element;
/**
* @internal
*/
declare const getVersionInlineBadge: (release?: TargetPerspective | undefined) => FC<{
children?: react.ReactNode;
}>;
interface ObserveDocumentAPIConfig {
dataset?: string;
projectId?: string;
apiVersion?: string;
}
type DocumentIdSetObserverState = {
status: 'reconnecting' | 'connected';
documentIds: string[];
};
/** @internal */
type Id = string;
/**
* @hidden
* @beta */
type Previewable = ({
_id: string;
} | {
_type: string;
} | {
_system?: {
delete: boolean;
};
} | {
_ref: string;
_dataset?: string;
_projectId?: string;
}) & {
/**
* optional object used to attach meta data to the prepared result.
* currently used to add a flag for the invalid preview error fallback and
* insufficient permissions fallback
* @internal
*/
_internalMeta?: {
type?: string;
};
};
/**
* TODO: unify with content path from `@sanity/types`
*
*
* @hidden
* @beta
*/
type PreviewPath = FieldName[];
/** @internal */
type Selection = [id: Id, fields: FieldName[]];
/**
* @hidden
* @beta */
type FieldName = string;
/** @internal */
interface AvailabilityResponse {
omitted: {
id: string;
reason: 'existence' | 'permission';
}[];
}
/** @internal */
type AvailabilityReason = 'READABLE' | 'PERMISSION_DENIED' | 'NOT_FOUND';
/**
* @hidden
* @beta */
type PreviewableType = SchemaType | CrossDatasetType | GlobalDocumentReferenceType;
/**
* @hidden
* @beta */
interface ApiConfig {
projectId: string;
dataset: string;
}
/**
* @hidden
* @beta */
type DocumentAvailability = {
available: true;
reason: 'READABLE';
} | {
available: false;
reason: 'PERMISSION_DENIED' | 'NOT_FOUND' | 'VERSION_DELETED';
};
/**
* @hidden
* @beta */
interface DraftsModelDocumentAvailability {
/**
* document readability for the published document
*/
published: DocumentAvailability;
/**
* document readability for the draft document
*/
draft: DocumentAvailability;
/**
* document readability for the version document
*/
version?: DocumentAvailability;
}
/**
* @hidden
* @beta */
interface DocumentStackAvailability {
/**
* Document id
*/
id: string;
/**
* Availability for the document in this stack
*/
availability: DocumentAvailability;
}
/**
* @hidden
* @beta */
interface DraftsModelDocument<T extends SanityDocumentLike = SanityDocumentLike> {
id: string;
type: string | null;
draft: {
availability: DocumentAvailability;
snapshot: T | undefined;
};
published: {
availability: DocumentAvailability;
snapshot: T | undefined;
};
version?: {
availability: DocumentAvailability;
snapshot: T | undefined;
};
}
/**
* Event emitted to notify preview subscribers when they need to refetch a document being previewed
* - 'connected' will happen when the store is connected to the invalidation channel, both initially and after a reconnect after a connection loss
* - 'mutation' will happen when a document has been mutated and the store needs to refetch a document
* @hidden
* @beta
*/
type InvalidationChannelEvent = {
type: 'connected';
} | {
type: 'mutation';
documentId: string;
visibility: string;
};
/**
* @hidden
* @beta */
interface PreparedSnapshot {
type?: PreviewableType;
snapshot: PreviewValue | null | undefined;
}
/** @internal */
type ObserveDocumentTypeFromIdFn = (id: string, apiConfig?: ApiConfig, perspective?: StackablePerspective[]) => Observable<string | undefined>;
/**
* @hidden
* @beta */
interface ObservePathsFn {
(value: Previewable, paths: (string | PreviewPath)[], apiConfig?: ApiConfig, perspective?: StackablePerspective[]): Observable<PreviewValue | SanityDocumentLike | Reference | string | null>;
}
/**
* @hidden
* @beta */
interface ObserveDocumentAvailabilityFn {
(id: string, options?: {
version?: string;
}): Observable<{
draft: DocumentAvailability;
published: DocumentAvailability;
version?: DocumentAvailability;
}>;
}
/**
* @hidden
* @beta */
type ObserveForPreviewFn = (value: Previewable, type: PreviewableType, options?: {
viewOptions?: PrepareViewOptions;
perspective?: StackablePerspective[];
apiConfig?: ApiConfig;
}) => Observable<PreparedSnapshot>;
/**
* The document preview store supports subscribing to content for previewing purposes.
* Documents observed by this store will be kept in sync and receive real-time updates from all collaborators,
* but has no support for optimistic updates, so any local edits will require a server round-trip before becoming visible,
* which means this store is less suitable for real-time editing scenarios.
*
* @hidden
* @beta */
interface DocumentPreviewStore {
observePaths: ObservePathsFn;
observeForPreview: ObserveForPreviewFn;
observeDocumentTypeFromId: (id: string, apiConfig?: ApiConfig, perspective?: StackablePerspective[]) => Observable<string | undefined>;
/**
*
* @hidden
* @beta
*/
unstable_observeDocumentPairAvailability: (id: string, options?: {
version?: string;
}) => Observable<DraftsModelDocumentAvailability>;
/**
*
* @hidden
* @beta
*/
unstable_observeDocumentStackAvailability: (id: string, perspectiveStack: StackablePerspective[]) => Observable<DocumentStackAvailability[]>;
unstable_observePathsDocumentPair: <T extends SanityDocument = SanityDocument>(id: string, paths: PreviewPath[], options?: {
version?: string;
}) => Observable<DraftsModelDocument<T>>;
/**
* Observes a set of document IDs that matches the given groq-filter. The document ids are returned in ascending order and will update in real-time
* Whenever a document appears or disappears from the set, a new array with the updated set of IDs will be pushed to subscribers.
* The query is performed once, initially, and thereafter the set of ids are patched based on the `appear` and `disappear`
* transitions on the received listener events.
* This provides a lightweight way of subscribing to a list of ids for simple cases where you just want to subscribe to a set of documents ids
* that matches a particular filter.
* @hidden
* @beta
* @param filter - A groq filter to use for the document set
* @param params - Parameters to use with the groq filter
* @param options - Options for the observer
* @param apiVersion - Specify the API version to use for the query
*/
unstable_observeDocumentIdSet: (filter: string, params?: QueryParams, options?: {
/**
* Where to insert new items into the set. Defaults to 'sorted' which is based on the lexicographic order of the id
*/
insert?: 'sorted' | 'prepend' | 'append';
apiVersion?: string;
}) => Observable<DocumentIdSetObserverState>;
/**
* Observe a complete document with the given ID
* @hidden
* @beta
*/
unstable_observeDocument: (id: string, clientConfig?: ObserveDocumentAPIConfig) => Observable<SanityDocument | undefined>;
/**
* Observe a list of complete documents with the given IDs
* @hidden
* @beta
*/
unstable_observeDocuments: (ids: string[], clientConfig?: ObserveDocumentAPIConfig) => Observable<(SanityDocument | undefined)[]>;
}
/** @internal */
interface DocumentPreviewStoreOptions {
client: SanityClient;
}
/** @internal */
declare function createDocumentPreviewStore({
client
}: DocumentPreviewStoreOptions): DocumentPreviewStore;
interface DocumentPerspectiveProps {
documentId: string;
}
interface DocumentPerspectiveState {
data: string[];
error?: unknown;
loading: boolean;
}
/**
* Fetches the document versions for a given document
* @param props - document Id of the document (might include release id)
* @returns - data: document versions, loading, errors
* @hidden
* @beta
*/
declare function useDocumentVersions(props: DocumentPerspectiveProps): DocumentPerspectiveState;
interface useDocumentVersionTypeSortedListState {
sortedDocumentList: ReleaseDocument[];
}
/**
* Fetches the document versions for a given document and sorts them by release type
*
* @param documentId - document id related to the document version list
* @returns object with sortedDocumentList
*
* @beta
*/
declare const useDocumentVersionTypeSortedList: ({
documentId
}: {
documentId: string;
}) => useDocumentVersionTypeSortedListState;
/** @internal */
declare const useIsReleaseActive: () => boolean;
/**
* Returns a boolean if the document has only versions
*
* @param documentId - document id related to the document version list
* @returns if the document has only versions
*
* @beta
*/
declare const useOnlyHasVersions: ({
documentId
}: {
documentId: string;
}) => boolean;
interface VersionOperationsValue {
createVersion: (releaseId: ReleaseId, documentId: string) => Promise<void>;
discardVersion: (releaseId: string, documentId: string) => Promise<SingleActionResult>;
unpublishVersion: (documentId: string) => Promise<SingleActionResult>;
revertUnpublishVersion: (documentId: string) => Promise<SingleActionResult>;
}
/** @internal */
declare function useVersionOperations(): VersionOperationsValue;
/**
* Sorts releases by their release type and created date.
* @internal
*/
declare function sortReleases(releases?: ReleaseDocument[]): ReleaseDocument[];
/**
* This is used to draw the bar that wraps the diff components in the changes panel
*
* @internal
*/
declare const ChangeFieldWrapper: (props: {
path: Path;
children: ReactNode;
hasRevertHover: boolean;
}) => react.JSX.Element;
/** @internal */
interface ChangeIndicatorProps {
path: Path;
hasFocus: boolean;
isChanged: boolean;
withHoverEffect?: boolean;
}
/** @internal */
declare function ChangeIndicator(props: ChangeIndicatorProps & Omit<HTMLProps<HTMLDivElement>, 'as'>): react.JSX.Element;
/** @internal */
interface ConnectorContextValue {
isReviewChangesOpen: boolean;
onOpenReviewChanges: () => void | undefined;
onSetFocus: (nextPath: Path) => void | undefined;
isInteractive?: boolean;
}
/** @internal */
interface ChangeConnectorRootProps {
children: ReactNode;
className?: string;
isReviewChangesOpen: boolean;
onOpenReviewChanges: () => void;
onSetFocus: (path: Path) => void;
}
/** @internal */
declare function ChangeConnectorRoot({
children,
className,
isReviewChangesOpen,
onOpenReviewChanges,
onSetFocus,
...restProps
}: ChangeConnectorRootProps): react.JSX.Element;
/** @internal */
type Reported<Value> = [string, Value];
/** @internal */
type ReporterHook<Payload> = (id: string | null, value: () => Payload, isEqual?: IsEqualFunction<Payload>) => void;
/** @internal */
type IsEqualFunction<Value> = (a: Value | null, b: Value | null) => boolean;
/** @internal */
interface TrackerContextStore<Value> {
add: (id: string, value: Value) => void;
update: (id: string, value: Value) => void;
remove: (id: string) => void;
}
/** @internal */
type TrackerContextGetSnapshot<Value> = [string, Value][];
/** @internal */
declare function useTrackerStore<Value>(): {
store: TrackerContextStore<Value>;
snapshot: TrackerContextGetSnapshot<Value>;
};
/** @internal */
declare function useTrackerStoreReporter<Value>(store: TrackerContextStore<Value> | null, id: string | null, value: () => Value, isEqual?: IsEqualFunction<Value>): void;
/** @internal */
interface TrackedChange {
element: HTMLElement | null;
path: Path;
isChanged: boolean;
hasFocus: boolean;
hasHover: boolean;
hasRevertHover: boolean;
zIndex: number;
}
/** @internal */
interface TrackedArea {
element: HTMLElement | null;
}
/** @internal */
type ChangeIndicatorTrackerContextValue = TrackedChange;
declare function ChangeIndicatorsTrackerComponent(props: {
children: React.ReactNode;
}): react.JSX.Element;
/**
* @internal
*/
declare const ChangeIndicatorsTracker: react.MemoExoticComponent<typeof ChangeIndicatorsTrackerComponent>;
/**
* @internal
*/
declare function useChangeIndicatorsReportedValues(): TrackerContextGetSnapshot<ChangeIndicatorTrackerContextValue>;
/**
* @internal
*/
declare const useChangeIndicatorsReporter: ReporterHook<ChangeIndicatorTrackerContextValue>;
/**
* @beta
* @hidden
*/
interface CommentDeleteDialogProps {
commentId: string;
error: Error | null;
isParent: boolean;
loading: boolean;
onClose: () => void;
onConfirm: (id: string) => void;
}
/**
* @beta
* @hidden
*/
declare function CommentDeleteDialog(props: CommentDeleteDialogProps): react.JSX.Element;
/**
* @internal
*/
declare const CommentDisabledIcon: react.ForwardRefExoticComponent<Omit<SVGProps<SVGSVGElement>, "ref"> & react.RefAttributes<SVGSVGElement>>;
/**
*
* @deprecated Calling `useClient()` without specifying an API version is deprecated - specify a date to prevent breaking changes, e.g. `useClient({apiVersion: "2025-02-07"})`.
*
* React hook that returns a configured Sanity client instance based on the given configuration.
* Automatically uses the correct project and dataset based on the current active workspace.
*
* @public
* @returns A configured Sanity client instance
* @remarks The client instance is automatically memoized based on API version
* @remarks The client will fallback to `v2025-02-07` of the API
* @example Instantiating a client
* ```ts
* function MyComponent() {
* const client = useClient({apiVersion: '2021-06-07'})
* // ... do something with client instance ...
* }
* ```
*/
declare function useClient(): SanityClient;
/**
* React hook that returns a configured Sanity client instance based on the given configuration.
* Automatically uses the correct project and dataset based on the current active workspace.
*
* @public
* @param clientOptions - Options for the client. Specifying
* {@link https://www.sanity.io/docs/api-versioning | apiVersion} is required in order to
* prevent breaking changes if studio changes the API version used in other places.
* See {@link SourceClientOptions}
* @returns A configured Sanity client instance
* @remarks The client instance is automatically memoized based on API version
* @example Instantiating a client
* ```ts
* function MyComponent() {
* const client = useClient({apiVersion: '2021-06-07'})
* // ... do something with client instance ...
* }
* ```
*/
declare function useClient(clientOptions: SourceClientOptions): SanityClient;
/**
* Workaround to support conditional toast (e.g. a toast that is visible as long as a condition holds true)
* @hidden
* @internal
*/
declare function useConditionalToast(params: ToastParams & {
id: string;
enabled?: boolean;
delay?: number;
}): void;
/** @internal */
type ConnectionState = 'connecting' | 'reconnecting' | 'connected';
/** @internal */
declare function useConnectionState(publishedDocId: string, docTypeName: string, version?: string): ConnectionState;
/**
* React hook that returns the name of the current dataset
*
* @public
* @returns The name of the current dataset
* @example Using the `useDataset` hook
* ```ts
* function MyComponent() {
* const dataset = useDataset()
* // ... do something with the dataset name ...
* }
* ```
*/
declare function useDataset(): string;
/**
* Options for the `useDateTimeFormat` hook
*
* @public
*/
type UseDateTimeFormatOptions = Omit<Intl.DateTimeFormatOptions, 'fractionalSecondDigits'>;
/**
* Returns an instance of `Intl.DateTimeFormat` that uses the currently selected locale,
* and enables locale and culture-sensitive date formatting.
*
* @param options - Optional options for the date/time formatter
* @returns Instance of `Intl.DateTimeFormat`
* @public
*/
declare function useDateTimeFormat(options?: UseDateTimeFormatOptions): Intl.DateTimeFormat;
/**
* Hook to register a dialog in the stack and check if it's the top-most one.
*
* @beta
*/
declare function useDialogStack({
path
}?: {
path?: Path;
}): {
/** Unique ID for this dialog instance */dialogId: string; /** The current top dialog entry */
topEntry: sanity__singletons0.DialogStackEntry | null; /** The full stack of dialog entries (each has id and path) */
stack: sanity__singletons0.DialogStackEntry[]; /** Check if this dialog is on top */
isTop: boolean; /** Close dialogs */
close: (options?: {
toParent?: boolean | undefined;
} | undefined) => void; /** Navigate to a specific path, updating the form path and cleaning up the stack */
navigateTo: (path: Path) => void;
};
/** @internal */
type CommitFunction = (mutation: Mutation['params']) => Promise<unknown>;
/**
* @hidden
* @beta */
interface DocumentRebaseEvent {
type: 'rebase';
document: SanityDocument;
remoteMutations: MutationPayload[];
localMutations: MutationPayload[];
}
/**
* @hidden
* @beta */
interface DocumentMutationEvent {
type: 'mutation';
document: SanityDocument;
mutations: MutationPayload[];
origin: 'local' | 'remote';
}
/**
* @hidden
* @beta */
interface SnapshotEvent {
type: 'snapshot';
document: SanityDocument;
}
/**
* @hidden
* @beta */
interface CommittedEvent {
type: 'committed';
}
/**
* @hidden
* @beta */
interface DocumentRemoteMutationEvent {
type: 'remoteMutation';
head: SanityDocument;
transactionId: string;
author: string;
timestamp: Date;
effects: {
apply: unknown;
revert: unknown;
};
}
/**
* @hidden
* @beta */
type RemoteSnapshotEvent = DocumentRemoteMutationEvent | SnapshotEvent;
/**
* @hidden
* @beta */
interface MutationPayload {
create?: any;
createIfNotExists?: any;
createOrReplace?: any;
delete?: any;
patch?: any;
}
/** @internal */
interface MutationEvent {
type: 'mutation';
documentId: string;
transactionId: string;
mutations: MutationPayload[];
effects: {
apply: unknown;
revert: unknown;
};
previousRev: string;
resultRev: string;
transactionTotalEvents: number;
transactionCurrentEvent: number;
messageReceivedAt: string;
visibility: 'transaction' | 'query';
transition: 'update' | 'appear' | 'disappear';
}
/** @internal */
interface PendingMutationsEvent {
type: 'pending';
phase: 'begin' | 'end';
}
/** @internal */
interface IdPair {
draftId: string;
publishedId: string;
versionId?: string;
}
interface ListenerSequenceState {
/**
* Tracks the latest revision from the server that can be applied locally
* Once we receive a mutation event that has a `previousRev` that equals `base.revision`
* we will move `base.revision` to the event's `resultRev`
* `base.revision` will be undefined if document doesn't exist.
* `base` is `undefined` until the snapshot event is received
*/
base: {
revision: string | undefined;
} | undefined;
/**
* Array of events to pass on to the stream, e.g. when mutation applies to current head revision, or a chain is complete
*/
emitEvents: ListenerEvent[];
/**
* Buffer to keep track of events that doesn't line up in a [previousRev, resultRev] -- [previousRev, resultRev] sequence
* This can happen if events arrive out of order, or if an event in the middle for some reason gets lost
*/
buffer: MutationEvent[];
}
/**
* Takes an input observable of listener events that might arrive out of order, and emits them in sequence
* If we receive mutation events that doesn't line up in [previousRev, resultRev] pairs we'll put them in a buffer and
* check if we have an unbroken chain every time we receive a new event
*
* If the buffer grows beyond `maxBufferSize`, or if `resolveChainDeadline` milliseconds passes before the chain resolves
* an OutOfSyncError will be thrown on the stream
*
* @internal
*/
declare class OutOfSyncError extends Error {
/**
* Attach state to the error for debugging/reporting
*/
state: ListenerSequenceState;
constructor(message: string, state: ListenerSequenceState);
}
/** @internal */
interface InitialSnapshotEvent {
type: 'snapshot';
documentId: string;
document: SanityDocument | null;
}
/**
* @internal
*/
interface LatencyReportEvent {
shard?: string;
latencyMs: number;
transactionId: string;
}
/** @internal */
interface DocumentPairLoadedEvent {
durationMs: number;
fromCache: boolean;
hasPublished: boolean;
hasDraft: boolean;
hasVersion: boolean;
}
/** @internal */
interface DocumentStoreExtraOptions {
tag?: string;
/**
* Called when we recover from sync error
* Meant for error tracking / telemetry purposes
* @param error - the {@link OutOfSyncError} recovered from
*/
onSyncErrorRecovery?(error: OutOfSyncError): void;
onReportLatency?: (event: LatencyReportEvent) => void;
onSlowCommit?: () => void;
onDocumentPairLoaded?: (event: DocumentPairLoadedEvent) => void;
}
/** @internal */
type ListenerEvent = MutationEvent | ReconnectEvent | InitialSnapshotEvent | PendingMutation