forked from oakserver/oak
-
Notifications
You must be signed in to change notification settings - Fork 0
/
structured_clone.ts
177 lines (168 loc) · 4.85 KB
/
structured_clone.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Copyright 2018-2023 the oak authors. All rights reserved. MIT license.
export type StructuredClonable =
| { [key: string]: StructuredClonable }
| Array<StructuredClonable>
| ArrayBuffer
| ArrayBufferView
// deno-lint-ignore ban-types
| BigInt
| bigint
| Blob
// deno-lint-ignore ban-types
| Boolean
| boolean
| Date
| Error
| EvalError
| Map<StructuredClonable, StructuredClonable>
// deno-lint-ignore ban-types
| Number
| number
| RangeError
| ReferenceError
| RegExp
| Set<StructuredClonable>
// deno-lint-ignore ban-types
| String
| string
| SyntaxError
| TypeError
| URIError;
/** Internal functions on the `Deno.core` namespace */
interface DenoCore {
deserialize(value: unknown): StructuredClonable;
serialize(value: StructuredClonable): unknown;
}
const objectCloneMemo = new WeakMap();
function cloneArrayBuffer(
srcBuffer: ArrayBuffer,
srcByteOffset: number,
srcLength: number,
// deno-lint-ignore no-explicit-any
_cloneConstructor: any,
) {
// this function fudges the return type but SharedArrayBuffer is disabled for a while anyway
return srcBuffer.slice(
srcByteOffset,
srcByteOffset + srcLength,
);
}
/** A loose approximation for structured cloning, used when the `Deno.core`
* APIs are not available. */
// deno-lint-ignore no-explicit-any
function cloneValue(value: any): any {
switch (typeof value) {
case "number":
case "string":
case "boolean":
case "undefined":
case "bigint":
return value;
case "object": {
if (objectCloneMemo.has(value)) {
return objectCloneMemo.get(value);
}
if (value === null) {
return value;
}
if (value instanceof Date) {
return new Date(value.valueOf());
}
if (value instanceof RegExp) {
return new RegExp(value);
}
if (value instanceof SharedArrayBuffer) {
return value;
}
if (value instanceof ArrayBuffer) {
const cloned = cloneArrayBuffer(
value,
0,
value.byteLength,
ArrayBuffer,
);
objectCloneMemo.set(value, cloned);
return cloned;
}
if (ArrayBuffer.isView(value)) {
const clonedBuffer = cloneValue(value.buffer);
// Use DataViewConstructor type purely for type-checking, can be a
// DataView or TypedArray. They use the same constructor signature,
// only DataView has a length in bytes and TypedArrays use a length in
// terms of elements, so we adjust for that.
let length;
if (value instanceof DataView) {
length = value.byteLength;
} else {
// deno-lint-ignore no-explicit-any
length = (value as any).length;
}
// deno-lint-ignore no-explicit-any
return new (value.constructor as any)(
clonedBuffer,
value.byteOffset,
length,
);
}
if (value instanceof Map) {
const clonedMap = new Map();
objectCloneMemo.set(value, clonedMap);
value.forEach((v, k) => {
clonedMap.set(cloneValue(k), cloneValue(v));
});
return clonedMap;
}
if (value instanceof Set) {
// assumes that cloneValue still takes only one argument
const clonedSet = new Set([...value].map(cloneValue));
objectCloneMemo.set(value, clonedSet);
return clonedSet;
}
// default for objects
// deno-lint-ignore no-explicit-any
const clonedObj: Record<any, any> = {};
objectCloneMemo.set(value, clonedObj);
const sourceKeys = Object.getOwnPropertyNames(value);
for (const key of sourceKeys) {
clonedObj[key] = cloneValue(value[key]);
}
Reflect.setPrototypeOf(clonedObj, Reflect.getPrototypeOf(value));
return clonedObj;
}
case "symbol":
case "function":
default:
throw new DOMException("Uncloneable value in stream", "DataCloneError");
}
}
// deno-lint-ignore no-explicit-any
const core = (Deno as any)?.core as DenoCore | undefined;
const structuredClone: ((value: unknown) => unknown) | undefined =
// deno-lint-ignore no-explicit-any
(globalThis as any).structuredClone;
/**
* Provides structured cloning
* @param value
* @returns
*/
function sc<T extends StructuredClonable>(value: T): T {
return structuredClone
? structuredClone(value)
: core
? core.deserialize(core.serialize(value))
: cloneValue(value);
}
/** Clones a state object, skipping any values that cannot be cloned. */
// deno-lint-ignore no-explicit-any
export function cloneState<S extends Record<string, any>>(state: S): S {
const clone = {} as S;
for (const [key, value] of Object.entries(state)) {
try {
const clonedValue = sc(value);
clone[key as keyof S] = clonedValue;
} catch {
// we just no-op values that cannot be cloned
}
}
return clone;
}