UNPKG

@bjoerge/mutiny

Version:

Tiny toolkit for working with Sanity mutations in JavaScript & TypeScript

861 lines (727 loc) 22.5 kB
import {Call} from 'hotscript' import {Numbers} from 'hotscript' import {Tuples} from 'hotscript' export declare type AdjustIndex< Pos extends 'before' | 'after', Index extends number, > = Pos extends 'before' ? Index : Call<Numbers.Add, Index, 1> 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 ApplyAtIndex< Index extends number, Tail extends AnyArray, Op extends Operation, Arr extends AnyArray, > = [ ...Call<Tuples.Take<Index, Arr>>, ApplyAtPath<Tail, Op, Arr[Index]>, ...Call<Tuples.Drop<Call<Numbers.Add<Index, 1>>, Arr>>, ] export declare type ApplyAtPath< Pth extends Path, Op extends Operation, Node, > = Pth extends EmptyArray ? ApplyOp<Op, Node> : Pth extends [infer Head, ...infer Tail] ? Node extends AnyArray ? ApplyInArray<Head, Tail, Op, Node> : Node extends { [K in string]: unknown } ? ApplyInObject<Head, Tail, Op, Node> : never : never export declare type ApplyAtSelector< Selector extends KeyedPathElement, Tail extends AnyArray, Op extends Operation, Arr extends AnyArray, > = FirstIndexOf<0, Selector, Arr> extends infer Index ? Index extends number ? ApplyAtIndex<Index, Tail, Op, Arr> : Arr : Arr export declare type ApplyInArray< ItemSelector, Tail extends AnyArray, Op extends Operation, Arr extends AnyArray, > = ItemSelector extends number ? ApplyAtIndex<ItemSelector, Tail, Op, Arr> : ItemSelector extends KeyedPathElement ? ApplyAtSelector<ItemSelector, Tail, Op, Arr> : never export declare function applyInCollection<Doc extends SanityDocumentBase>( collection: Doc[], mutations: Mutation | Mutation[], ): Doc[] export declare function applyInIndex< Doc extends SanityDocumentBase, Index extends DocumentIndex<ToStored<Doc>>, >(index: Index, mutations: Mutation<Doc>[]): Index export declare type ApplyInObject< Head, Tail extends AnyArray, Op extends Operation, Node, > = Head extends keyof Node ? { [K in keyof Node]: K extends Head ? ApplyAtPath<Tail, Op, PickOrUndef<Node, Head>> : Node[K] } : Tail extends EmptyArray ? Head extends string ? Format< Node & { [K in Head]: ApplyOp<Op, undefined> } > : never : Node export declare type ApplyNodePatch<Patch extends NodePatch, Node> = Patch extends NodePatch<infer P, infer Op> ? ApplyAtPath<P, Op, Node> : ApplyAtPath<Patch['path'], Patch['op'], Node> export declare function applyNodePatch< const Patch extends NodePatch, const Doc, >(patch: Patch, document: Doc): ApplyNodePatch<Patch, Doc> export declare type ApplyOp< O extends Operation, Current, > = Current extends never ? never : O extends SetOp<infer Next> ? Next : O extends UnsetOp ? undefined : O extends IncOp<infer Amount> ? Current extends number ? number extends Current ? number : Call<Numbers.Add, Current, Amount> : Current : O extends DecOp<infer Amount> ? Current extends number ? number extends Current ? number : Call<Numbers.Sub, Current, Amount> : Current : O extends InsertOp<infer Items, infer Pos, infer Ref> ? Current extends AnyArray<unknown> ? ArrayInsert< NormalizeReadOnlyArray<Current>, NormalizeReadOnlyArray<Items>, Pos, Ref > : Current : O extends ReplaceOp<infer Items, infer Ref> ? Current extends any[] ? (ArrayElement<Items> | ArrayElement<Current>)[] : never : O extends AssignOp<infer U> ? Assign<Current, U> : O extends SetIfMissingOp<infer V> ? Current extends undefined | null ? V : Current : O extends UnassignOp<infer U> ? { [K in keyof Current as Exclude< K, ArrayElement<U> >]: Current[K] } : O extends DiffMatchPatchOp ? string : never export declare function applyOp<const Op extends AnyOp, const CurrentValue>( op: Op, currentValue: CurrentValue, ): ApplyOp<Op, CurrentValue> export declare function applyOp< const Op extends NumberOp, const CurrentValue extends number, >(op: Op, currentValue: CurrentValue): ApplyOp<Op, CurrentValue> export declare function applyOp< const Op extends StringOp, const CurrentValue extends string, >(op: Op, currentValue: CurrentValue): ApplyOp<Op, CurrentValue> export declare function applyOp< const Op extends ObjectOp, const CurrentValue extends { [k in keyof any]: unknown }, >(op: Op, currentValue: CurrentValue): ApplyOp<Op, CurrentValue> export declare function applyOp< const Op extends ArrayOp, const CurrentValue extends AnyArray, >(op: Op, currentValue: CurrentValue): ApplyOp<Op, CurrentValue> export declare type ApplyPatches<Patches, Node> = Patches extends [ infer HeadPatch, ...infer TailPatch, ] ? HeadPatch extends NodePatch ? TailPatch extends [] ? ApplyNodePatch<HeadPatch, Node> : TailPatch extends NodePatch[] ? ApplyPatches<TailPatch, ApplyNodePatch<HeadPatch, Node>> : Node : Node : Node export declare function applyPatches<Patches extends NodePatchList, const Doc>( patches: Patches, document: Doc, ): ApplyPatches<NormalizeReadOnlyArray<Patches>, Doc> export declare type ApplyPatchMutation< Mutation extends PatchMutation, Doc extends SanityDocumentBase, > = Mutation extends PatchMutation<infer Patches> ? ApplyPatches<NormalizeReadOnlyArray<Patches>, Doc> : Doc export declare function applyPatchMutation< const Mutation extends PatchMutation, const Doc extends SanityDocumentBase, >(mutation: Mutation, document: Doc): ApplyPatchMutation<Mutation, Doc> export declare type ArrayElement<A> = A extends readonly (infer T)[] ? T : never export declare type ArrayInsert< Current extends unknown[], Items extends unknown[], Pos extends 'before' | 'after', Ref extends number | KeyedPathElement, > = Current extends (infer E)[] ? number extends Ref ? (E | ArrayElement<Items>)[] : Ref extends number ? InsertAtIndex<Current, Items, Pos, Ref> : (E | ArrayElement<Items>)[] : Current export declare type ArrayLength<T extends AnyArray> = T extends never[] ? 0 : T['length'] export declare type ArrayOp = | InsertOp<AnyArray, RelativePosition, Index | KeyedPathElement> | UpsertOp<AnyArray, RelativePosition, Index | KeyedPathElement> | ReplaceOp<AnyArray, Index | KeyedPathElement> | TruncateOp export declare type Assign<Current, Attrs> = { [K in keyof Attrs | keyof Current]: K extends keyof Attrs ? Attrs[K] : K extends keyof Current ? Current[K] : never } export declare function assignId<Doc extends SanityDocumentBase>( doc: Doc, generateId: () => string, ): Doc & { _id: string } export declare type AssignOp<T extends object = object> = { type: 'assign' value: T } export declare type Between< Num extends number, Min extends number, Max extends number, > = Call<Numbers.GreaterThanOrEqual<Num, Min>> extends true ? Call<Numbers.LessThanOrEqual<Num, Max>> extends true ? true : false : false 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 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 const createStore: <Doc extends SanityDocumentBase>( initialEntries?: Doc[], ) => { readonly version: number entries: () => [string, Format<ToStored<Doc & SanityDocumentBase>>][] get: <Id extends string>( id: Id, ) => Format< Omit<Format<ToStored<Doc & SanityDocumentBase>>, '_id'> & { _id: Id } > apply: (mutations: Mutation[] | Mutation) => 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 DocumentIndex<Doc extends SanityDocumentBase> = { [id: string]: Doc } export declare type ElementType<T extends AnyArray> = T extends AnyArray<infer E> ? E : unknown export declare type EmptyArray = never[] | readonly never[] | [] | readonly [] 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 FirstIndexOf< StartIndex extends number, Selector extends KeyedPathElement, Arr extends AnyArray, > = Arr extends [infer Head, ...infer Tail] ? Head extends Selector ? StartIndex : FirstIndexOf<Call<Numbers.Add<StartIndex>, 1>, Selector, Tail> : null /** * Formats an intersection object type, so it outputs as `{"foo": 1, "bar": 1}` instead of `{"foo": 1} & {"bar": 2}`` */ export declare type Format<A> = A extends { [Key in keyof A]: A[Key] } ? { [Key in keyof A]: A[Key] } : A 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 function hasId(doc: SanityDocumentBase): doc is StoredDocument export declare type IncOp<Amount extends number> = { type: 'inc' amount: Amount } export declare type Index = number export declare type InsertAtIndex< Current extends unknown[], Values extends unknown[], Pos extends 'before' | 'after', Index extends number, > = _InsertAtIndex< Current, Values, Pos, NormalizeIndex<Index, ArrayLength<Current>> > export declare type _InsertAtIndex< Current extends unknown[], Values extends unknown[], Pos extends 'before' | 'after', NormalizedIndex extends number, > = Between<NormalizedIndex, 0, ArrayLength<Current>> extends true ? SplitAtPos<Current, NormalizedIndex, Pos> extends [infer Head, infer Tail] ? Head extends AnyArray ? Tail extends AnyArray ? [ ...(Head extends never[] ? [] : Head), ...Values, ...(Tail extends never[] ? [] : Tail), ] : never : never : never : Current 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 LastIndexOnEmptyArray<Index, Length> = Length extends 0 ? Index extends -1 ? true : false : false 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 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 function normalize(path: string | Readonly<Path>): Readonly<Path> export declare type NormalizeIndex< Index extends number, Length extends number, > = LastIndexOnEmptyArray<Index, Length> extends true ? 0 : Call<Numbers.LessThan<Index, 0>> extends true ? Call<Numbers.Add, Length, Index> : Index export declare type NormalizeReadOnlyArray<T> = T extends readonly [ infer NP, ...infer Rest, ] ? [NP, ...Rest] : T extends readonly (infer NP)[] ? NP[] : T 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 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 PickOrUndef<T, Head> = Head extends keyof T ? T[Head] : undefined export declare type PrimitiveOp = AnyOp | StringOp | NumberOp export declare type PropertyName = string export declare type RelativePosition = 'before' | 'after' export declare type ReplaceOp< Items extends AnyArray, ReferenceItem extends Index | KeyedPathElement, > = { type: 'replace' referenceItem: ReferenceItem items: Items } export declare type RequiredSelect<T, K extends keyof T> = Omit<T, K> & { [P in K]-?: T[P] } export declare type Result<E, V> = [E, V] export declare type SafePath<S extends string> = StripError<StringToPath<S>> export declare type SanityDocumentBase = { _id?: string _type: string _createdAt?: string _updatedAt?: string _rev?: 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 type SplitAtPos< Current extends unknown[], NormalizedIndex extends number, Pos extends 'before' | 'after', > = Call<Tuples.SplitAt<AdjustIndex<Pos, NormalizedIndex>, Current>> export declare function startsWith(parentPath: Path, path: Path): boolean export declare type StoredDocument = ToStored<SanityDocumentBase> 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 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 ToIdentified<Doc extends SanityDocumentBase> = RequiredSelect<Doc, '_id'> export declare type ToNumber<T extends string> = T extends `${infer N extends number}` ? N : never export declare type ToStored<Doc extends SanityDocumentBase> = Doc & Required<SanityDocumentBase> 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 {}