-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathdraft.ts
153 lines (138 loc) · 4.38 KB
/
draft.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
147
148
149
150
151
152
153
import { DraftType, Mark, ProxyDraft } from '../interface';
import { dataTypes, PROXY_DRAFT } from '../constant';
import { has } from './proto';
export function latest<T = any>(proxyDraft: ProxyDraft): T {
return proxyDraft.copy ?? proxyDraft.original;
}
/**
* Check if the value is a draft
*/
export function isDraft(target: any) {
return !!getProxyDraft(target);
}
export function getProxyDraft<T extends any>(value: T): ProxyDraft | null {
if (typeof value !== 'object') return null;
return (value as { [PROXY_DRAFT]: any })?.[PROXY_DRAFT];
}
export function getValue<T extends object>(value: T): T {
const proxyDraft = getProxyDraft(value);
return proxyDraft ? proxyDraft.copy ?? proxyDraft.original : value;
}
/**
* Check if a value is draftable
*/
export function isDraftable(value: any, options?: { mark?: Mark<any, any> }) {
if (!value || typeof value !== 'object') return false;
let markResult: any;
return (
Object.getPrototypeOf(value) === Object.prototype ||
Array.isArray(value) ||
value instanceof Map ||
value instanceof Set ||
(!!options?.mark &&
((markResult = options.mark(value, dataTypes)) === dataTypes.immutable ||
typeof markResult === 'function'))
);
}
export function getPath(
target: ProxyDraft,
path: any[] = []
): (string | number | object)[] | null {
if (Object.hasOwnProperty.call(target, 'key')) {
// check if the parent is a draft and the original value is not equal to the current value
const parentCopy = target.parent!.copy;
const proxyDraft = getProxyDraft(get(parentCopy, target.key!));
if (proxyDraft !== null && proxyDraft?.original !== target.original) {
return null;
}
const isSet = target.parent!.type === DraftType.Set;
const key = isSet
? Array.from(target.parent!.setMap!.keys()).indexOf(target.key)
: target.key;
// check if the key is still in the next state parent
if (
!((isSet && parentCopy.size > (key as number)) || has(parentCopy, key!))
)
return null;
path.push(key);
}
if (target.parent) {
return getPath(target.parent, path);
}
// `target` is root draft.
path.reverse();
try {
// check if the path is valid
resolvePath(target.copy, path);
} catch (e) {
return null;
}
return path;
}
export function getType(target: any) {
if (Array.isArray(target)) return DraftType.Array;
if (target instanceof Map) return DraftType.Map;
if (target instanceof Set) return DraftType.Set;
return DraftType.Object;
}
export function get(target: any, key: PropertyKey) {
return getType(target) === DraftType.Map ? target.get(key) : target[key];
}
export function set(target: any, key: PropertyKey, value: any) {
const type = getType(target);
if (type === DraftType.Map) {
target.set(key, value);
} else {
target[key] = value;
}
}
export function peek(target: any, key: PropertyKey) {
const state = getProxyDraft(target);
const source = state ? latest(state) : target;
return source[key];
}
export function isEqual(x: any, y: any) {
if (x === y) {
return x !== 0 || 1 / x === 1 / y;
} else {
return x !== x && y !== y;
}
}
export function revokeProxy(proxyDraft: ProxyDraft | null) {
if (!proxyDraft) return;
while (proxyDraft.finalities.revoke.length > 0) {
const revoke = proxyDraft.finalities.revoke.pop()!;
revoke();
}
}
// handle JSON Pointer path with spec https://www.rfc-editor.org/rfc/rfc6901
export function escapePath(path: string[], pathAsArray: boolean) {
return pathAsArray
? path
: ['']
.concat(path)
.map((_item) => {
const item = `${_item}`;
if (item.indexOf('/') === -1 && item.indexOf('~') === -1) return item;
return item.replace(/~/g, '~0').replace(/\//g, '~1');
})
.join('/');
}
export function unescapePath(path: string | (string | number)[]) {
if (Array.isArray(path)) return path;
return path
.split('/')
.map((_item) => _item.replace(/~1/g, '/').replace(/~0/g, '~'))
.slice(1);
}
export function resolvePath(base: any, path: (string | number)[]) {
for (let index = 0; index < path.length - 1; index += 1) {
const key = path[index];
// use `index` in Set draft
base = get(getType(base) === DraftType.Set ? Array.from(base) : base, key);
if (typeof base !== 'object') {
throw new Error(`Cannot resolve patch at '${path.join('/')}'.`);
}
}
return base;
}