This repository was archived by the owner on Mar 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathcreate-structure.ts
146 lines (134 loc) · 4.03 KB
/
create-structure.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { unorderedHash, objectHash } from './hash';
// LazyNode is a special case, see the jsdoc comment below
type LazyNodeWithoutHash = Omit<Sanity.GroqCodegen.LazyNode, 'hash'> & {
hashNamespace: string;
hashInput: string;
};
type StructureNodeTypes = Exclude<
Sanity.GroqCodegen.StructureNode['type'],
// LazyNode is a special case, see the jsdoc comment below
'Lazy'
>;
// creates a type of all the `StructureNode`s but without a hash
type StructureNodeWithoutHash = {
[P in StructureNodeTypes]: Omit<
Extract<Sanity.GroqCodegen.StructureNode, { type: P }>,
'hash'
>;
}[StructureNodeTypes];
type InputNode = (StructureNodeWithoutHash | LazyNodeWithoutHash) & {
// adds in `hash` as an optional string for easier use however any
// pre-existing hashes will be overridden
hash?: string;
};
type Transform = (
node: Sanity.GroqCodegen.StructureNode,
) => Sanity.GroqCodegen.StructureNode;
const memoize = (transform: Transform): Transform => {
const cache = new Map<string, Sanity.GroqCodegen.StructureNode>();
return (node) => {
if (cache.has(node.hash)) return cache.get(node.hash)!;
const result = transform(node);
cache.set(node.hash, result);
return result;
};
};
export const simplify = memoize((node: Sanity.GroqCodegen.StructureNode) => {
if (node.type !== 'And' && node.type !== 'Or') return node;
const children = Array.from(
node.children
.map(simplify)
.reduce<Map<string, Sanity.GroqCodegen.StructureNode>>((map, child) => {
if (child.type === node.type) {
for (const nestedChild of child.children) {
map.set(nestedChild.hash, nestedChild);
}
} else {
map.set(child.hash, child);
}
return map;
}, new Map())
.values(),
).sort((a, b) => a.hash.localeCompare(b.hash, 'en'));
if (children.length === 0) return ensureHash({ type: 'Unknown' });
if (children.length === 1) return children[0];
return ensureHash({ ...node, children });
});
function ensureHash({
// remove an pre-existing hash
hash: _hash,
...node
}: InputNode): Sanity.GroqCodegen.StructureNode {
switch (node.type) {
case 'And':
case 'Or': {
const { children, ...rest } = node;
return {
...node,
hash: objectHash([rest, unorderedHash(children.map((i) => i.hash))]),
};
}
case 'Array': {
const { of, ...rest } = node;
return {
...node,
hash: objectHash([rest, of.hash]),
};
}
case 'Object': {
const { properties, ...rest } = node;
return {
...node,
hash: objectHash([
rest,
unorderedHash(properties.map((i) => [i.key, i.value.hash])),
]),
};
}
case 'Tuple': {
const { elements, ...rest } = node;
return {
...node,
hash: objectHash([rest, elements.map((element) => element.hash)]),
};
}
case 'Reference': {
const { to, ...rest } = node;
return {
...node,
hash: objectHash([rest, to.hash]),
};
}
case 'Unknown': {
return { type: 'Unknown', hash: 'unknown' };
}
case 'Lazy': {
const { hashInput, hashNamespace, ...rest } = node;
return {
// this is `rest` on purpose
...rest,
hash: objectHash(['Lazy', hashNamespace, hashInput]),
};
}
default: {
return {
...node,
hash: objectHash(node),
};
}
}
}
/**
* Adds hashes to new `StructureNode`s by looking at the current node's
* properties. If the node has children (e.g. `And`s/`Or`s), then the hash will
* use the direct children's hash as an input (this makes all hash computation
* shallow).
*
* The result of this is then ran through `simplify` function memoized by the
* node's resulting hash.
*
* Note: the `LazyNode` is a special case because it's not possible to derive a
* hash automatically without pulling the lazy value so a `hashNamespace` and
* a `hashInput` are required.
*/
export const createStructure = (node: InputNode) => simplify(ensureHash(node));