@sanity/diff-patch 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/index.cjs +251 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +299 -0
- package/dist/index.d.ts +299 -0
- package/dist/index.js +251 -0
- package/dist/index.js.map +1 -0
- package/package.json +90 -0
- package/src/diffError.ts +23 -0
- package/src/diffPatch.ts +575 -0
- package/src/index.ts +20 -0
- package/src/patches.ts +126 -0
- package/src/paths.ts +59 -0
- package/src/validate.ts +71 -0
package/src/paths.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type {KeyedSanityObject} from './diffPatch.js'
|
|
2
|
+
|
|
3
|
+
const IS_DOTTABLE_RE = /^[A-Za-z_][A-Za-z0-9_]*$/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A segment of a path
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export type PathSegment =
|
|
11
|
+
| string // Property
|
|
12
|
+
| number // Array index
|
|
13
|
+
| {_key: string} // Array `_key` lookup
|
|
14
|
+
| [number | '', number | ''] // From/to array index
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* An array of path segments representing a path in a document
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export type Path = PathSegment[]
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Converts an array path to a string path
|
|
25
|
+
*
|
|
26
|
+
* @param path - The array path to convert
|
|
27
|
+
* @returns A stringified path
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export function pathToString(path: Path): string {
|
|
31
|
+
return path.reduce((target: string, segment: PathSegment, i: number) => {
|
|
32
|
+
if (Array.isArray(segment)) {
|
|
33
|
+
return `${target}[${segment.join(':')}]`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isKeyedObject(segment)) {
|
|
37
|
+
return `${target}[_key=="${segment._key}"]`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (typeof segment === 'number') {
|
|
41
|
+
return `${target}[${segment}]`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof segment === 'string' && !IS_DOTTABLE_RE.test(segment)) {
|
|
45
|
+
return `${target}['${segment}']`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof segment === 'string') {
|
|
49
|
+
const separator = i === 0 ? '' : '.'
|
|
50
|
+
return `${target}${separator}${segment}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new Error(`Unsupported path segment "${segment}"`)
|
|
54
|
+
}, '')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isKeyedObject(obj: any): obj is KeyedSanityObject {
|
|
58
|
+
return typeof obj === 'object' && typeof obj._key === 'string'
|
|
59
|
+
}
|
package/src/validate.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {DiffError} from './diffError.js'
|
|
2
|
+
import type {Path} from './paths.js'
|
|
3
|
+
|
|
4
|
+
const idPattern = /^[a-z0-9][a-z0-9_.-]+$/i
|
|
5
|
+
const propPattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/
|
|
6
|
+
const propStartPattern = /^[a-z_]/i
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validate the given document/subtree for Sanity compatibility
|
|
10
|
+
*
|
|
11
|
+
* @param item - The document or subtree to validate
|
|
12
|
+
* @param path - The path to the current item, for error reporting
|
|
13
|
+
* @returns True if valid, throws otherwise
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export function validateDocument(item: unknown, path: Path = []): boolean {
|
|
17
|
+
if (Array.isArray(item)) {
|
|
18
|
+
return item.every((child, i) => {
|
|
19
|
+
if (Array.isArray(child)) {
|
|
20
|
+
throw new DiffError('Multi-dimensional arrays not supported', path.concat(i))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return validateDocument(child, path.concat(i))
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (typeof item === 'object' && item !== null) {
|
|
28
|
+
const obj = item as {[key: string]: any}
|
|
29
|
+
return Object.keys(obj).every(
|
|
30
|
+
(key) =>
|
|
31
|
+
validateProperty(key, obj[key], path) && validateDocument(obj[key], path.concat(key)),
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate a property for Sanity compatibility
|
|
40
|
+
*
|
|
41
|
+
* @param property - The property to valide
|
|
42
|
+
* @param value - The value of the property
|
|
43
|
+
* @param path - The path of the property, for error reporting
|
|
44
|
+
* @returns The property name, if valid
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export function validateProperty(property: string, value: unknown, path: Path): string {
|
|
48
|
+
if (!propStartPattern.test(property)) {
|
|
49
|
+
throw new DiffError('Keys must start with a letter (a-z)', path.concat(property), value)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!propPattern.test(property)) {
|
|
53
|
+
throw new DiffError(
|
|
54
|
+
'Keys can only contain letters, numbers and underscores',
|
|
55
|
+
path.concat(property),
|
|
56
|
+
value,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (property === '_key' || property === '_ref' || property === '_type') {
|
|
61
|
+
if (typeof value !== 'string') {
|
|
62
|
+
throw new DiffError('Keys must be strings', path.concat(property), value)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!idPattern.test(value)) {
|
|
66
|
+
throw new DiffError('Invalid key - use less exotic characters', path.concat(property), value)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return property
|
|
71
|
+
}
|