@bjoerge/mutiny
Version:
Tiny toolkit for working with Sanity mutations in JavaScript & TypeScript
861 lines (727 loc) • 22.5 kB
text/typescript
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 {}