UNPKG

@sanity/mutate

Version:

Experimental toolkit for working with Sanity mutations in JavaScript & TypeScript

1,100 lines (931 loc) 27.9 kB
import {Observable} from 'rxjs' import {RawPatch} from 'mendoza' import {ReconnectEvent} from '@sanity/client' import {SanityClient} from '@sanity/client' import {WelcomeEvent} from '@sanity/client' export declare interface AccessibleDocumentResult { id: string document: SanityDocumentBase accessible: true } export declare type AnyArray<T = any> = T[] | readonly T[] export declare type AnyEmptyArray = [] | readonly [] export declare type AnyOp = SetOp<unknown> | SetIfMissingOp<unknown> | UnsetOp export declare type ArrayOp = | InsertOp<AnyArray, RelativePosition, Index | KeyedPathElement> | UpsertOp<AnyArray, RelativePosition, Index | KeyedPathElement> | ReplaceOp<AnyArray, Index | KeyedPathElement> | TruncateOp | RemoveOp<Index | KeyedPathElement> export declare type AssignOp<T extends object = object> = { type: 'assign' value: T } export declare type ByIndex<P extends number, T extends AnyArray> = T[P] export declare type Concat< R extends Result<any, any>, Arr extends any[], > = R[1] extends any[] ? Ok<[...R[1], ...Arr]> : R export declare type ConcatInner< R extends Result<any, any>, R2 extends Result<any, any>, > = R2[1] extends any[] ? Concat<R, R2[1]> : R2 export declare type Conflict = { path: Path error: Error base: SanityDocumentBase | undefined local: SanityDocumentBase | undefined } /** * Creates a function that can be used to listen for events that happens in a single document * Features * - builtin retrying and connection recovery (track disconnected state by listening for `reconnect` events) * - builtin mutation event ordering (they might arrive out of order), lost events detection (/listen endpoint doesn't guarantee delivery) and recovery * - discards already-applied mutation events received while fetching the initial document snapshot * @param options */ export declare function createDocumentEventListener(options: { loadDocument: DocumentLoader listenerEvents: Observable< WelcomeEvent | ListenerMutationEvent | ReconnectEvent > }): <Doc extends SanityDocumentBase>( documentId: string, ) => Observable<ListenerEvent> /** * Creates a "dataloader" style document loader that fetches from the /doc endpoint * @param {FetchDocuments} fetchDocuments - The client instance used for fetching documents. * @param options */ export declare function createDocumentLoader( fetchDocuments: FetchDocuments, options?: { durationSelector?: () => Observable<unknown> tag?: string }, ): (key: string) => Observable<DocumentResult> export declare function createDocumentLoaderFromClient( client: SanityClient, options?: { durationSelector?: () => Observable<unknown> tag?: string }, ): (key: string) => Observable<DocumentResult> /** * Creates a function that can be used to listen for document updates * Emits the latest snapshot of the document along with the latest event * @param options */ export declare function createDocumentUpdateListener(options: { listenDocumentEvents: (documentId: string) => Observable<ListenerEvent> }): <Doc extends SanityDocumentBase>( documentId: string, ) => Observable<DocumentUpdate<Doc>> export declare function createIdSetListener( listen: IdSetListenFn, fetch: FetchDocumentIdsFn, ): ( queryFilter: string, params: QueryParams, options?: { tag?: string }, ) => Observable<DocumentIdSetEvent> export declare function createIdSetListenerFromClient( client: SanityClient, ): void export declare type CreateIfNotExistsMutation<Doc extends SanityDocumentBase> = { type: 'createIfNotExists' document: Doc } export declare function createMockBackendAPI(): MockBackendAPI export declare type CreateMutation< Doc extends Optional<SanityDocumentBase, '_id'>, > = { type: 'create' document: Doc } /** * Creates a local dataset that allows subscribing to documents by id and submitting mutations to be optimistically applied * @param backend */ export declare function createOptimisticStore( backend: OptimisticStoreBackend, ): OptimisticStore export declare function createOptimisticStoreClientBackend( client: SanityClient, ): OptimisticStoreBackend export declare function createOptimisticStoreMockBackend( backendAPI: MockBackendAPI, ): OptimisticStoreBackend export declare type CreateOrReplaceMutation<Doc extends SanityDocumentBase> = { type: 'createOrReplace' document: Doc } /** * @param listenDocumentUpdates – a function that takes a document id and returns an observable of document snapshots * @param options */ export declare function createReadOnlyStore( listenDocumentUpdates: DocumentUpdateListener<SanityDocumentBase>, options?: { shutdownDelay?: number }, ): ReadOnlyDocumentStore /** * Creates a (low level) shared listener that will emit 'welcome' for all new subscribers immediately, and thereafter emit every listener event, including welcome, mutation, and reconnects * Useful for cases where you need control of how the listen request is set up */ export declare function createSharedListener( listen: SharedListenerListenFn, options?: ListenerOptions, ): Observable<WelcomeEvent | ListenerMutationEvent | ReconnectEvent> /** * Creates a (low level) shared listener that will emit 'welcome' for all new subscribers immediately, and thereafter emit every listener event, including welcome, mutation, and reconnects * Requires a Sanity client instance */ export declare function createSharedListenerFromClient( client: SanityClient, options?: ListenerOptions, ): Observable<WelcomeEvent | ListenerMutationEvent | ReconnectEvent> export declare type DecOp<Amount extends number> = { type: 'dec' amount: Amount } export declare type DeleteMutation = { type: 'delete' id: string } export declare type DiffMatchPatchOp = { type: 'diffMatchPatch' value: string } export declare type Digit = | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' export declare interface DocEndpointResponse { documents: SanityDocumentBase[] omitted: OmittedDocument[] } export declare type DocumentIdSetEvent = | { type: 'sync' documentIds: string[] } | { type: 'reconnect' } | { type: 'op' op: 'add' | 'remove' documentId: string } export declare type DocumentIdSetState = { status: 'connecting' | 'reconnecting' | 'connected' event: DocumentIdSetEvent | InitialEvent snapshot: string[] } export declare type DocumentLoader = ( documentIds: string, ) => Observable<DocumentResult> export declare type DocumentMap<Doc extends SanityDocumentBase> = { get(id: string): Doc | undefined set(id: string, doc: Doc | undefined): void delete(id: string): void } export declare interface DocumentMutationUpdate< Doc extends SanityDocumentBase, > { documentId: string snapshot: Doc | undefined event: ListenerMutationEvent } export declare interface DocumentReconnectUpdate< Doc extends SanityDocumentBase, > { documentId: string snapshot: Doc | undefined event: ListenerReconnectEvent } export declare type DocumentResult = | AccessibleDocumentResult | InaccessibleDocumentResult export declare interface DocumentSyncUpdate<Doc extends SanityDocumentBase> { documentId: string snapshot: Doc | undefined event: ListenerSyncEvent<Doc> } export declare type DocumentUpdate<Doc extends SanityDocumentBase> = | DocumentSyncUpdate<Doc> | DocumentMutationUpdate<Doc> | DocumentReconnectUpdate<any> export declare type DocumentUpdateListener<Doc extends SanityDocumentBase> = ( id: string, ) => Observable<DocumentUpdate<Doc>> export declare type ElementType<T extends AnyArray> = T extends AnyArray<infer E> ? E : unknown export declare type Err<E> = Result<E, null> export declare type FetchDocumentIdsFn = ( query: string, params?: QueryParams, options?: { tag?: string }, ) => Observable<string[]> export declare type FetchDocuments = ( ids: string[], ) => Observable<DocEndpointResponse> export declare type FindBy<P, T extends AnyArray> = T extends AnyEmptyArray ? undefined : T[0] extends P ? T[0] : T extends [any, ...infer Tail] | readonly [any, ...infer Tail] ? FindBy<P, Tail> : ElementType<T> export declare type FindInArray< P extends KeyedPathElement | number, T extends AnyArray, > = P extends KeyedPathElement ? FindBy<P, T> : P extends number ? ByIndex<P, T> : never export declare type Get< P extends number | KeyedPathElement | Readonly<KeyedPathElement> | string, T, > = T extends AnyArray ? P extends KeyedPathElement | Readonly<KeyedPathElement> | number ? FindInArray<P, T> : undefined : P extends keyof T ? T[P] : never export declare type GetAtPath< P extends readonly PathElement[], T, > = P extends [] ? T : P extends [infer Head, ...infer Tail] ? Head extends PathElement ? Tail extends PathElement[] ? GetAtPath<Tail, Get<Head, T>> : undefined : undefined : undefined export declare function getAtPath<const Head extends PathElement, const T>( path: [head: Head], value: T, ): Get<Head, T> export declare function getAtPath< const Head extends PathElement, const Tail extends PathElement[], T, >(path: [head: Head, ...tail: Tail], value: T): GetAtPath<[Head, ...Tail], T> export declare function getAtPath<T>(path: [], value: T): T export declare function getAtPath(path: Path, value: unknown): unknown export declare type IdSetListenFn = ( query: string, params?: QueryParams, options?: { visibility: 'transaction' events: ['welcome', 'mutation', 'reconnect'] includeResult: false includeMutations: false tag?: string }, ) => Observable<ListenerEndpointEvent> export declare interface InaccessibleDocumentResult { accessible: false id: string reason: InaccessibleReason } export declare type InaccessibleReason = 'existence' | 'permission' export declare type IncOp<Amount extends number> = { type: 'inc' amount: Amount } export declare type Index = number export declare type InitialEvent = { type: 'connect' } export declare type Insert = { before?: string after?: string replace?: string items: any[] } export declare type InsertMethod = 'sorted' | 'prepend' | 'append' export declare type InsertOp< Items extends AnyArray, Pos extends RelativePosition, ReferenceItem extends Index | KeyedPathElement, > = { type: 'insert' referenceItem: ReferenceItem position: Pos items: Items } export declare function isArrayElement( element: PathElement, ): element is KeyedPathElement | number export declare function isElementEqual( segmentA: PathElement, segmentB: PathElement, ): boolean export declare function isEqual(path: Path, otherPath: Path): boolean export declare function isIndexElement(segment: PathElement): segment is number export declare function isKeyedElement( element: PathElement, ): element is KeyedPathElement export declare function isKeyElement( segment: PathElement, ): segment is KeyedPathElement export declare function isPropertyElement( element: PathElement, ): element is string export declare type KeyedPathElement = { _key: string } export declare type ListenerChannelErrorEvent = { type: 'channelError' message: string } export declare type ListenerDisconnectEvent = { type: 'disconnect' reason: string } export declare type ListenerEndpointEvent = | ListenerWelcomeEvent | ListenerMutationEvent | ListenerReconnectEvent | ListenerChannelErrorEvent | ListenerDisconnectEvent export declare type ListenerEvent< Doc extends SanityDocumentBase = SanityDocumentBase, > = ListenerSyncEvent<Doc> | ListenerMutationEvent | ListenerReconnectEvent export declare interface ListenerMutationEvent { type: 'mutation' documentId: string transactionId: string resultRev?: string previousRev?: string effects?: { apply: RawPatch } mutations: SanityMutation[] transition: 'update' | 'appear' | 'disappear' } export declare interface ListenerOptions { /** * Provide a custom filter to the listener. By default, this listener will include all events * Note: make sure the filter includes events from documents you will subscribe to. */ filter?: string /** * Whether to include system documents or not * This will be ignored if a custom filter is provided */ includeSystemDocuments?: boolean /** * How long after the last subscriber is unsubscribed to keep the connection open */ shutdownDelay?: number /** * Include mutations in listener events */ includeMutations?: boolean /** * Request tag */ tag?: string } export declare interface ListenerReconnectEvent { type: 'reconnect' } export declare interface ListenerSyncEvent< Doc extends SanityDocumentBase = SanityDocumentBase, > { type: 'sync' document: Doc | undefined } export declare type ListenerWelcomeEvent = { type: 'welcome' listenerName: string } export declare type MapTuple<T, U> = { [K in keyof T]: U } export declare type Merge<R extends Result<any, any>, E> = R[0] extends null ? Ok<R[1] & E> : R export declare type MergeInner< R extends Result<any, any>, R2 extends Result<any, any>, > = R2[0] extends null ? Merge<R, R2[1]> : R /** * This is the interface that a mock backend instance needs to implement */ export declare interface MockBackendAPI { listen(query: string): Observable<ListenerEndpointEvent> getDocuments(ids: string[]): Observable<DocEndpointResponse> submit(transaction: Transaction): Observable<SubmitResult> } export declare type Mutation<Doc extends SanityDocumentBase = any> = | CreateMutation<Doc> | CreateIfNotExistsMutation<Doc> | CreateOrReplaceMutation<Doc> | DeleteMutation | PatchMutation /** * A mutation group represents an incoming, locally added group of mutations * They can either be transactional or non-transactional * - Transactional means that they must be submitted as a separate transaction (with an optional id) and no other mutations can be mixed with it * – Non-transactional means that they can be combined with other mutations */ export declare type MutationGroup = | NonTransactionalMutationGroup | TransactionalMutationGroup export declare interface MutationResult {} export declare type NodePatch< P extends Path = Path, O extends Operation = Operation, > = { path: P op: O } export declare type NodePatchList = | [NodePatch, ...NodePatch[]] | NodePatch[] | readonly NodePatch[] | readonly [NodePatch, ...NodePatch[]] export declare interface NonTransactionalMutationGroup { transaction: false mutations: Mutation[] } export declare function normalize(path: string | Readonly<Path>): Readonly<Path> export declare type NumberOp = IncOp<number> | DecOp<number> export declare type ObjectOp = AssignOp | UnassignOp export declare type Ok<V> = Result<null, V> export declare interface OmittedDocument { id: string reason: 'existence' | 'permission' } export declare type OnlyDigits<S> = S extends `${infer Head}${infer Tail}` ? Head extends Digit ? Tail extends '' ? true : OnlyDigits<Tail> extends true ? true : false : false : false export declare type Operation = PrimitiveOp | ArrayOp | ObjectOp export declare interface OptimisticDocumentEvent { type: 'optimistic' id: string before: SanityDocumentBase | undefined after: SanityDocumentBase | undefined mutations: Mutation[] stagedChanges: Mutation[] } export declare interface OptimisticStore { meta: { /** * A stream of events for anything that happens in the store */ events: Observable<OptimisticDocumentEvent | RemoteDocumentEvent> /** * A stream of current staged changes */ stage: Observable<MutationGroup[]> /** * A stream of current conflicts. TODO: Needs more work */ conflicts: Observable<Conflict[]> } /** * Applies the given mutations. Mutations are not guaranteed to be submitted in the same transaction * Can this mutate both local and remote documents at the same time */ mutate(mutation: Mutation[]): MutationResult /** * Makes sure the given mutations are posted in a single transaction */ transaction( transaction: | { id?: string mutations: Mutation[] } | Mutation[], ): MutationResult /** * Checkout a document for editing. This is required to be able to see optimistic changes */ listen(id: string): Observable<SanityDocumentBase | undefined> /** * Listen for events for a given document id */ listenEvents( id: string, ): Observable<RemoteDocumentEvent | OptimisticDocumentEvent> /** * Optimize list of pending mutations */ optimize(): void /** * Submit pending mutations */ submit(): Promise<SubmitResult[]> } export declare interface OptimisticStoreBackend { /** * Sets up a subscription to a document * The first event should either be a sync event or an error event. * After that, it should emit mutation events, error events or sync events * @param id */ listen: (id: string) => Observable<ListenerEvent> submit: (mutationGroups: Transaction) => Observable<SubmitResult> } export declare type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>> export declare function parse<const T extends string>(path: T): StringToPath<T> export declare type ParseAllProps<Props extends string[]> = Props extends [ `${infer Head}`, ...infer Tail, ] ? Tail extends string[] ? ConcatInner<ParseProperty<Trim<Head>>, ParseAllProps<Tail>> : ParseProperty<Trim<Head>> : Ok<[]> export declare type ParseError<T extends string = 'unknown'> = T & { error: true } export declare type ParseExpressions<S extends string> = S extends `[${infer Expr}]${infer Remainder}` ? Trim<Remainder> extends '' ? ToArray<ParseInnerExpression<Trim<Expr>>> : ConcatInner< ToArray<ParseInnerExpression<Trim<Expr>>>, ParseExpressions<Remainder> > : Err<ParseError<`Cannot parse object from "${S}"`>> export declare type ParseInnerExpression<S extends string> = S extends '' ? Err<ParseError<'Saw an empty expression'>> : Try<ParseNumber<S>, ParseObject<S>> export declare type ParseKVPair<S extends string> = Split<S, '=='> extends [`${infer LHS}`, `${infer RHS}`] ? ParseValue<Trim<RHS>> extends infer Res ? Res extends [null, infer Value] ? Ok<{ [P in Trim<LHS>]: Value }> : Err< ParseError<`Can't parse right hand side as a value in "${S}" (Invalid value ${RHS})`> > : never : Err<ParseError<`Can't parse key value pair from ${S}`>> export declare type ParseNumber<S extends string> = S extends `${infer Head}${infer Tail}` ? Head extends '-' ? OnlyDigits<Tail> extends true ? Ok<ToNumber<S>> : Err<ParseError<`Invalid integer value "${S}"`>> : OnlyDigits<S> extends true ? Ok<ToNumber<S>> : Err<ParseError<`Invalid integer value "${S}"`>> : Err<ParseError<`Invalid integer value "${S}"`>> export declare type ParseObject<S extends string> = S extends `${infer Pair},${infer Remainder}` ? Trim<Remainder> extends '' ? Ok<Record<never, never>> : MergeInner<ParseKVPair<Pair>, ParseObject<Remainder>> : ParseKVPair<S> export declare type ParseProperty<S extends string> = Trim<S> extends '' ? Err<ParseError<'Empty property'>> : Split<Trim<S>, '[', true> extends [`${infer Prop}`, `${infer Expression}`] ? Trim<Prop> extends '' ? ParseExpressions<Trim<Expression>> : ConcatInner<Ok<[Trim<Prop>]>, ParseExpressions<Trim<Expression>>> : Ok<[Trim<S>]> export declare type ParseValue<S extends string> = string extends S ? Err<ParseError<'ParseValue got generic string type'>> : S extends 'null' ? Ok<null> : S extends 'true' ? Ok<true> : S extends 'false' ? Ok<false> : S extends `"${infer Value}"` ? Ok<Value> : Try< ParseNumber<S>, Err< ParseError<`ParseValue failed. Can't parse "${S}" as a value.`> > > export declare type PatchMutation< Patches extends NodePatchList = NodePatchList, > = { type: 'patch' id: string patches: Patches options?: PatchOptions } export declare type PatchOptions = { ifRevision?: string } export declare type Path = PathElement[] | readonly PathElement[] export declare type PathElement = PropertyName | Index | KeyedPathElement export declare type PrimitiveOp = AnyOp | StringOp | NumberOp export declare type PropertyName = string export declare type QueryParams = Record< string, string | number | boolean | (string | number | boolean)[] > export declare interface ReadOnlyDocumentStore { listenDocument: <Doc extends SanityDocumentBase>( id: string, ) => Observable<DocumentUpdate<Doc>> listenDocuments: < Doc extends SanityDocumentBase, const IdTuple extends string[], >( id: IdTuple, ) => Observable<MapTuple<IdTuple, DocumentUpdate<Doc>>> } export declare type RelativePosition = 'before' | 'after' export declare type RemoteDocumentEvent = RemoteSyncEvent | RemoteMutationEvent export declare interface RemoteMutationEvent { type: 'mutation' id: string before: { local: SanityDocumentBase | undefined remote: SanityDocumentBase | undefined } after: { local: SanityDocumentBase | undefined remote: SanityDocumentBase | undefined } effects?: { apply: RawPatch } previousRev?: string resultRev?: string mutations: Mutation[] rebasedStage: MutationGroup[] } export declare interface RemoteSyncEvent { type: 'sync' id: string before: { local: SanityDocumentBase | undefined remote: SanityDocumentBase | undefined } after: { local: SanityDocumentBase | undefined remote: SanityDocumentBase | undefined } rebasedStage: MutationGroup[] } export declare type RemoveOp<ReferenceItem extends Index | KeyedPathElement> = { type: 'remove' referenceItem: ReferenceItem } export declare type ReplaceOp< Items extends AnyArray, ReferenceItem extends Index | KeyedPathElement, > = { type: 'replace' referenceItem: ReferenceItem items: Items } /** * These are fixed, and it's up to the implementation of the listen function to turn them into request parameters */ export declare interface RequestOptions { events: ['welcome', 'mutation', 'reconnect'] includeResult: false includePreviousRevision: false visibility: 'transaction' effectFormat: 'mendoza' includeMutations?: boolean tag?: string } export declare type Result<E, V> = [E, V] export declare type SafePath<S extends string> = StripError<StringToPath<S>> export declare type SanityCreateIfNotExistsMutation< Doc extends SanityDocumentBase, > = { createIfNotExists: Doc } export declare type SanityCreateMutation<Doc extends SanityDocumentBase> = { create: Doc } export declare type SanityCreateOrReplaceMutation< Doc extends SanityDocumentBase, > = { createOrReplace: Doc } export declare type SanityDecPatch = { id: string dec: { [path: string]: number } } export declare type SanityDeleteMutation = { delete: { id: string } } export declare type SanityDiffMatchPatch = { id: string diffMatchPatch: { [path: string]: string } } export declare interface SanityDocumentBase { _id?: string _type: string _createdAt?: string _updatedAt?: string _rev?: string } export declare type SanityIncPatch = { id: string inc: { [path: string]: number } } export declare type SanityInsertPatch = { id: string insert: Insert } export declare type SanityMutation< Doc extends SanityDocumentBase = SanityDocumentBase, > = | SanityCreateMutation<Doc> | SanityCreateIfNotExistsMutation<Doc> | SanityCreateOrReplaceMutation<Doc> | SanityDeleteMutation | SanityPatchMutation export declare type SanityPatch = | SanitySetPatch | SanityUnsetPatch | SanityInsertPatch | SanitySetIfMissingPatch | SanityDiffMatchPatch | SanityIncPatch | SanityDecPatch export declare type SanityPatchMutation = { patch: | SanitySetPatch | SanitySetIfMissingPatch | SanityDiffMatchPatch | SanityInsertPatch | SanityUnsetPatch } export declare type SanitySetIfMissingPatch = { id: string setIfMissing: { [path: string]: any } } export declare type SanitySetPatch = { id: string set: { [path: string]: any } } export declare type SanityUnsetPatch = { id: string unset: string[] } export declare type SetIfMissingOp<T> = { type: 'setIfMissing' value: T } export declare type SetOp<T> = { type: 'set' value: T } export declare type SharedListenerListenFn = ( query: string, queryParams: QueryParams, options: RequestOptions, ) => Observable<ListenerEndpointEvent> export declare type Split< S extends string, Char extends string, IncludeSeparator extends boolean = false, > = S extends `${infer First}${Char}${infer Remainder}` ? [First, `${IncludeSeparator extends true ? Char : ''}${Remainder}`] : [S] export declare type SplitAll< S extends string, Char extends string, > = S extends `${infer First}${Char}${infer Remainder}` ? [First, ...SplitAll<Remainder, Char>] : [S] export declare function startsWith(parentPath: Path, path: Path): boolean export declare function stringify(pathArray: Path): string export declare type StringOp = DiffMatchPatchOp export declare type StringToPath<S extends string> = Unwrap< ParseAllProps<SplitAll<Trim<S>, '.'>> > export declare type StripError< S extends StringToPath<string> | ParseError<string>, > = S extends ParseError<string> ? never : S export declare interface SubmitResult {} export declare type ToArray<R extends Result<any, any>> = R extends [ infer E, infer V, ] ? E extends null ? V extends any[] ? R : Ok<[R[1]]> : R : R export declare type ToNumber<T extends string> = T extends `${infer N extends number}` ? N : never /** Converts a stream of id set listener events into a state containing the list of document ids */ export declare function toState(options?: { insert?: InsertMethod }): (input$: Observable<DocumentIdSetEvent>) => Observable<DocumentIdSetState> export declare interface Transaction { id?: string mutations: Mutation[] } export declare interface TransactionalMutationGroup { transaction: true id?: string mutations: Mutation[] } export declare type Trim< S extends string, Char extends string = ' ', > = TrimRight<TrimLeft<S, Char>, Char> export declare type TrimLeft< Str extends string, Char extends string = ' ', > = string extends Str ? Str : Str extends `${Char}${infer Trimmed}` ? TrimLeft<Trimmed, Char> : Str export declare type TrimRight< Str extends string, Char extends string = ' ', > = string extends Str ? Str : Str extends `${infer Trimmed}${Char}` ? TrimRight<Trimmed, Char> : Str export declare type TruncateOp = { type: 'truncate' startIndex: number endIndex?: number } export declare type Try<R extends Result<any, any>, Handled> = R[1] extends null ? Handled : R export declare type UnassignOp< K extends readonly string[] = readonly string[], > = { type: 'unassign' keys: K } export declare type UnsetOp = { type: 'unset' } export declare type Unwrap<R extends Result<any, any>> = R extends [ infer E, infer V, ] ? E extends null ? V : E : never export declare type UpsertOp< Items extends AnyArray, Pos extends RelativePosition, ReferenceItem extends Index | KeyedPathElement, > = { type: 'upsert' items: Items referenceItem: ReferenceItem position: Pos } export {}