UNPKG

@bjoerge/mutiny

Version:

Tiny toolkit for working with Sanity mutations in JavaScript & TypeScript

751 lines (628 loc) 18.1 kB
import {Observable} from 'rxjs' import {RawPatch} from 'mendoza' 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 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 } export declare interface ContentLakeStore { 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 */ observe(id: string): Observable<SanityDocumentBase | undefined> /** * Observe events for a given document id */ observeEvents( id: string, ): Observable<RemoteDocumentEvent | OptimisticDocumentEvent> /** * Optimize list of pending mutations */ optimize(): void /** * Submit pending mutations */ submit(): Promise<SubmitResult[]> } export declare function createContentLakeStore( backend: StoreBackend, ): ContentLakeStore export declare type CreateIfNotExistsMutation<Doc extends SanityDocumentBase> = { type: 'createIfNotExists' document: Doc } export declare type CreateMutation< Doc extends Optional<SanityDocumentBase, '_id'>, > = { type: 'create' document: Doc } export declare type CreateOrReplaceMutation<Doc extends SanityDocumentBase> = { type: 'createOrReplace' document: Doc } export declare type Dataset<Doc extends SanityDocumentBase> = { get(id: string): Doc | undefined set(id: string, doc: Doc | undefined): void delete(id: string): void } 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 type ElementType<T extends AnyArray> = T extends AnyArray<infer E> ? E : unknown export declare type Err<E> = Result<E, null> 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 IncOp<Amount extends number> = { type: 'inc' amount: Amount } export declare type Index = number export declare type Insert = { before?: string after?: string replace?: string items: any[] } 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 interface ListenerMutationEvent { type: 'mutation' transactionId: string effects: Required<SanityMutationEvent>['effects']['apply'] mutations: Required<SanityMutationEvent>['mutations'] } export declare interface ListenerReconnectEvent { type: 'reconnect' } export declare interface ListenerSyncEvent { type: 'sync' transactionId?: string document: SanityDocumentBase | undefined } 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 export declare type Mutation<Doc extends SanityDocumentBase = any> = | CreateMutation<Doc> | CreateIfNotExistsMutation<Doc> | CreateOrReplaceMutation<Doc> | DeleteMutation | PatchMutation 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 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 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 RelativePosition = 'before' | 'after' export declare type RemoteDocumentEvent = RemoteSyncEvent | RemoteMutationEvent export declare type RemoteListenerEvent = | ListenerSyncEvent | ListenerMutationEvent | ListenerReconnectEvent export declare interface RemoteMutationEvent { type: 'mutation' id: string before: { local: SanityDocumentBase | undefined remote: SanityDocumentBase | undefined } after: { local: SanityDocumentBase | undefined remote: SanityDocumentBase | undefined } effects: RawPatch 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 ReplaceOp< Items extends AnyArray, ReferenceItem extends Index | KeyedPathElement, > = { type: 'replace' referenceItem: ReferenceItem items: Items } 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 SanityDeleteMutation = { delete: { id: string } } export declare type SanityDiffMatchPatch = { id: string diffMatchPatch: { [path: string]: string } } export declare type SanityDocumentBase = { _id?: string _type: string _createdAt?: string _updatedAt?: string _rev?: string } 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 SanityMutationEvent = { type: 'mutation' documentId: string eventId: string identity: string mutations: SanityMutation[] previousRev?: string resultRev?: string result?: SanityDocumentBase previous?: SanityDocumentBase | null effects?: { apply: unknown[] revert: unknown[] } timestamp: string transactionId: string transition: 'update' | 'appear' | 'disappear' visibility: 'query' | 'transaction' } 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 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 interface StoreBackend { /** * 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 */ observe: (id: string) => Observable<RemoteListenerEvent> submit: (mutationGroups: Transaction[]) => Observable<SubmitResult> } 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 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 {}