Skip to content

Commit

Permalink
feat: add converter for form patches to mutate patches (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
bjoerge authored Jan 9, 2025
1 parent e3fdadd commit bd384fe
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 1 deletion.
9 changes: 9 additions & 0 deletions src/encoders/form-compat/compat-path.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {expectTypeOf, test} from 'vitest'

import {type Path} from '../../path'
import {type CompatPath} from './form-patch-types'

test('compat paths', () => {
expectTypeOf<Path>().toMatchTypeOf<CompatPath | Readonly<CompatPath>>()
expectTypeOf<CompatPath>().toMatchTypeOf<Path>()
})
65 changes: 65 additions & 0 deletions src/encoders/form-compat/encode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {at} from '../../mutations/creators'
import {
diffMatchPatch,
insert,
set,
setIfMissing,
unset,
} from '../../mutations/operations/creators'
import {type NodePatch} from '../../mutations/types'
import {type KeyedPathElement} from '../../path'
import {
type CompatPath,
type FormPatchLike,
type FormPatchPath,
} from './form-patch-types'

function assertCompatible(formPatchPath: FormPatchPath): CompatPath {
if (formPatchPath.length === 0) {
return formPatchPath as never[]
}
for (const element of formPatchPath) {
if (Array.isArray(element)) {
throw new Error('Form patch paths cannot include arrays')
}
}
return formPatchPath as CompatPath
}

/**
* Convert a Sanity form patch (ie emitted from an input component) to a {@link NodePatch}
* Note the lack of encodeMutation here. Sanity forms never emit *mutations*, only patches
* @param patches - Array of {@link FormPatchLike}
* @internal
*/
export function encodePatches(patches: FormPatchLike[]): NodePatch[] {
return patches.map(formPatch => {
const path = assertCompatible(formPatch.path)
if (formPatch.type === 'unset') {
return at(path, unset())
}
if (formPatch.type === 'set') {
return at(path, set(formPatch.value))
}
if (formPatch.type === 'setIfMissing') {
return at(path, setIfMissing(formPatch.value))
}
if (formPatch.type === 'insert') {
const arrayPath = path.slice(0, -1)
const itemRef = formPatch.path[formPatch.path.length - 1]
return at(
arrayPath,
insert(
formPatch.items,
formPatch.position,
itemRef as number | KeyedPathElement,
),
)
}
if (formPatch.type === 'diffMatchPatch') {
return at(path, diffMatchPatch(formPatch.value))
}
// @ts-expect-error - should be exhaustive
throw new Error(`Unknown patch type ${formPatch.type}`)
})
}
150 changes: 150 additions & 0 deletions src/encoders/form-compat/form-patch-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* Inlined from the 'sanity' package
*/
import {type ElementType} from '../../path'

/**
* @deprecated
*/
export type FormPatchPathKeyedSegment = {_key: string}
/**
* @deprecated
*/
export type FormPatchPathIndexTuple = [number | '', number | '']

/**
* @deprecated
*/
export type FormPatchPathSegment =
| string
| number
| FormPatchPathKeyedSegment
| FormPatchPathIndexTuple

/**
* @deprecated
*/
export type FormPatchPath = FormPatchPathSegment[]

/**
* A variant of the FormPath type that never contains index tupes
*/
export type CompatPath = Exclude<
ElementType<FormPatchPath>,
FormPatchPathIndexTuple
>[]

/**
*
* @internal
* @deprecated
*/
export type FormPatchJSONValue =
| number
| string
| boolean
| {[key: string]: FormPatchJSONValue}
| FormPatchJSONValue[]

/**
*
* @internal
* @deprecated
*/
export type FormPatchOrigin = 'remote' | 'local' | 'internal'

/**
*
* @internal
* @deprecated
*/
export interface FormSetPatch {
path: FormPatchPath
type: 'set'
value: FormPatchJSONValue
}

/**
*
* @internal
* @deprecated
*/
export interface FormIncPatch {
path: FormPatchPath
type: 'inc'
value: FormPatchJSONValue
}

/**
*
* @internal
* @deprecated
*/
export interface FormDecPatch {
path: FormPatchPath
type: 'dec'
value: FormPatchJSONValue
}

/**
*
* @internal
* @deprecated
*/
export interface FormSetIfMissingPatch {
path: FormPatchPath
type: 'setIfMissing'
value: FormPatchJSONValue
}

/**
*
* @internal
* @deprecated
*/
export interface FormUnsetPatch {
path: FormPatchPath
type: 'unset'
}

/**
*
* @internal
* @deprecated
*/
export type FormInsertPatchPosition = 'before' | 'after'

/**
*
* @internal
* @deprecated
*/
export interface FormInsertPatch {
path: FormPatchPath
type: 'insert'
position: FormInsertPatchPosition
items: FormPatchJSONValue[]
}

/**
*
* @internal
* @deprecated
*/
export interface FormDiffMatchPatch {
path: FormPatchPath
type: 'diffMatchPatch'
value: string
}

/**
*
* @internal
* @deprecated
*/
export type FormPatchLike =
| FormSetPatch
| FormSetIfMissingPatch
| FormUnsetPatch
| FormInsertPatch
| FormDiffMatchPatch
2 changes: 2 additions & 0 deletions src/encoders/form-compat/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {encodePatches} from './encode'
export * from './form-patch-types'
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as CompactEncoder from './encoders/compact'
import * as FormCompatEncoder from './encoders/form-compat'
import * as SanityEncoder from './encoders/sanity'
import * as CompactFormatter from './formatters/compact'

export * from './mutations/autoKeys'
export * from './mutations/creators'
export * from './mutations/operations/creators'
export {CompactEncoder, SanityEncoder}
export {CompactEncoder, FormCompatEncoder, SanityEncoder}

export {CompactFormatter}

Expand Down

0 comments on commit bd384fe

Please sign in to comment.