-
Notifications
You must be signed in to change notification settings - Fork 6
/
unflatten.ts
125 lines (113 loc) · 3.3 KB
/
unflatten.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
import {
Deferred,
HOLE,
NAN,
NEGATIVE_INFINITY,
NEGATIVE_ZERO,
POSITIVE_INFINITY,
TYPE_BIGINT,
TYPE_DATE,
TYPE_MAP,
TYPE_PROMISE,
TYPE_REGEXP,
TYPE_SET,
TYPE_SYMBOL,
UNDEFINED,
type ThisDecode,
TYPE_ERROR,
} from "./utils.js";
const globalObj = (
typeof window !== "undefined"
? window
: typeof globalThis !== "undefined"
? globalThis
: undefined
) as Record<string, typeof Error> | undefined;
export function unflatten(this: ThisDecode, parsed: unknown): unknown {
if (typeof parsed === "number") return hydrate.call(this, parsed);
if (!Array.isArray(parsed) || !parsed.length) throw new SyntaxError();
const startIndex = this.values.length;
this.values.push(...parsed);
this.hydrated.length = this.values.length;
return hydrate.call(this, startIndex);
}
function hydrate(this: ThisDecode, index: number) {
const { hydrated, values, deferred } = this;
switch (index) {
case UNDEFINED:
return;
case NAN:
return NaN;
case POSITIVE_INFINITY:
return Infinity;
case NEGATIVE_INFINITY:
return -Infinity;
case NEGATIVE_ZERO:
return -0;
}
if (hydrated[index]) return hydrated[index];
const value = values[index];
if (!value || typeof value !== "object") return (hydrated[index] = value);
if (Array.isArray(value)) {
if (typeof value[0] === "string") {
switch (value[0]) {
case TYPE_DATE:
return (hydrated[index] = new Date(value[1]));
case TYPE_BIGINT:
return (hydrated[index] = BigInt(value[1]));
case TYPE_REGEXP:
return (hydrated[index] = new RegExp(value[1], value[2]));
case TYPE_SYMBOL:
return (hydrated[index] = Symbol.for(value[1]));
case TYPE_SET:
const set = new Set();
hydrated[index] = set;
for (let i = 1; i < value.length; i++)
set.add(hydrate.call(this, value[i]));
return set;
case TYPE_MAP:
const map = new Map();
hydrated[index] = map;
for (let i = 1; i < value.length; i += 2) {
map.set(
hydrate.call(this, value[i]),
hydrate.call(this, value[i + 1])
);
}
return map;
case TYPE_PROMISE:
if (hydrated[value[1]]) {
return (hydrated[index] = hydrated[value[1]]);
} else {
const d = new Deferred();
deferred[value[1]] = d;
return (hydrated[index] = d.promise);
}
case TYPE_ERROR:
const [, message, errorType] = value;
let error =
errorType && globalObj && globalObj[errorType]
? new globalObj[errorType](message)
: new Error(message);
hydrated[index] = error;
return error;
default:
throw new SyntaxError();
}
} else {
const array: unknown[] = [];
hydrated[index] = array;
for (let i = 0; i < value.length; i++) {
const n = value[i];
if (n !== HOLE) array[i] = hydrate.call(this, n);
}
return array;
}
} else {
const object: Record<string, unknown> = {};
hydrated[index] = object;
for (const key in value)
object[key] = hydrate.call(this, (value as Record<string, number>)[key]);
return object;
}
}