1 |
|
2 | import v8 from 'v8';
|
3 |
|
4 | const nameToCtor: Map<string, Class<*>> = new Map();
|
5 | const ctorToName: Map<Class<*>, string> = new Map();
|
6 |
|
7 | export function registerSerializableClass(name: string, ctor: Class<*>) {
|
8 | if (nameToCtor.has(name)) {
|
9 | throw new Error('Name already registered with serializer');
|
10 | }
|
11 |
|
12 | if (ctorToName.has(ctor)) {
|
13 | throw new Error('Class already registered with serializer');
|
14 | }
|
15 |
|
16 | nameToCtor.set(name, ctor);
|
17 | ctorToName.set(ctor, name);
|
18 | }
|
19 |
|
20 | export function unregisterSerializableClass(name: string, ctor: Class<*>) {
|
21 | if (nameToCtor.get(name) === ctor) {
|
22 | nameToCtor.delete(name);
|
23 | }
|
24 |
|
25 | if (ctorToName.get(ctor) === name) {
|
26 | ctorToName.delete(ctor);
|
27 | }
|
28 | }
|
29 |
|
30 | function shallowCopy(object: any) {
|
31 | if (object && typeof object === 'object') {
|
32 | if (Array.isArray(object)) {
|
33 | return [...object];
|
34 | }
|
35 |
|
36 | if (object instanceof Map) {
|
37 | return new Map(object);
|
38 | }
|
39 |
|
40 | if (object instanceof Set) {
|
41 | return new Set(object);
|
42 | }
|
43 |
|
44 | return Object.create(
|
45 | Object.getPrototypeOf(object),
|
46 | Object.getOwnPropertyDescriptors(object),
|
47 | );
|
48 | }
|
49 |
|
50 | return object;
|
51 | }
|
52 |
|
53 | function isBuffer(object) {
|
54 | return (
|
55 | object.buffer instanceof ArrayBuffer ||
|
56 | object.buffer instanceof SharedArrayBuffer
|
57 | );
|
58 | }
|
59 |
|
60 | function mapObject(object: any, fn: (val: any) => any, preOrder = false): any {
|
61 | let cache = new Map();
|
62 | let memo = new Map();
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | let memoizedFn = (val: any) => {
|
68 | let res = memo.get(val);
|
69 | if (res == null) {
|
70 | res = fn(val);
|
71 | memo.set(val, res);
|
72 | }
|
73 |
|
74 | return res;
|
75 | };
|
76 |
|
77 | let walk = (object: any, shouldCopy = false) => {
|
78 |
|
79 | if (cache.has(object)) {
|
80 | return cache.get(object);
|
81 | }
|
82 |
|
83 | let result = object;
|
84 | cache.set(object, result);
|
85 |
|
86 | let processKey = (key: any, value: any) => {
|
87 | let newValue = value;
|
88 | if (preOrder && value && typeof value === 'object') {
|
89 | newValue = memoizedFn(value);
|
90 | }
|
91 |
|
92 |
|
93 | if (newValue && typeof newValue === 'object' && newValue.$$raw !== true) {
|
94 | newValue = walk(newValue, newValue === value);
|
95 | }
|
96 |
|
97 | if (!preOrder && newValue && typeof newValue === 'object') {
|
98 | newValue = memoizedFn(newValue);
|
99 | }
|
100 |
|
101 | if (newValue !== value) {
|
102 |
|
103 | if (object === result && preOrder && shouldCopy) {
|
104 | result = shallowCopy(object);
|
105 | cache.set(object, result);
|
106 | }
|
107 |
|
108 |
|
109 | if (result instanceof Map) {
|
110 | result.set(key, newValue);
|
111 | } else if (result instanceof Set) {
|
112 |
|
113 | result.delete(value);
|
114 | result.add(newValue);
|
115 | } else {
|
116 | result[key] = newValue;
|
117 | }
|
118 | }
|
119 | };
|
120 |
|
121 |
|
122 | if (Array.isArray(object)) {
|
123 | for (let i = 0; i < object.length; i++) {
|
124 | processKey(i, object[i]);
|
125 | }
|
126 | } else if (object instanceof Map || object instanceof Set) {
|
127 | for (let [key, val] of object.entries()) {
|
128 | processKey(key, val);
|
129 | }
|
130 | } else if (!isBuffer(object)) {
|
131 | for (let key in object) {
|
132 | processKey(key, object[key]);
|
133 | }
|
134 | }
|
135 |
|
136 | return result;
|
137 | };
|
138 |
|
139 | let mapped = memoizedFn(object);
|
140 | if (mapped && typeof mapped === 'object' && mapped.$$raw !== true) {
|
141 | return walk(mapped, mapped === object);
|
142 | }
|
143 |
|
144 | return mapped;
|
145 | }
|
146 |
|
147 | export function prepareForSerialization(object: any) {
|
148 | return mapObject(
|
149 | object,
|
150 | value => {
|
151 |
|
152 | if (
|
153 | value &&
|
154 | typeof value === 'object' &&
|
155 | typeof value.constructor === 'function'
|
156 | ) {
|
157 | let type = ctorToName.get(value.constructor);
|
158 | if (type != null) {
|
159 | let serialized = value;
|
160 | let raw = false;
|
161 | if (value && typeof value.serialize === 'function') {
|
162 |
|
163 | serialized = value.serialize();
|
164 | raw = (serialized && serialized.$$raw) ?? true;
|
165 | if (serialized) {
|
166 | delete serialized.$$raw;
|
167 | }
|
168 | }
|
169 |
|
170 | return {
|
171 | $$type: type,
|
172 | $$raw: raw,
|
173 | value: {...serialized},
|
174 | };
|
175 | }
|
176 | }
|
177 |
|
178 | return value;
|
179 | },
|
180 | true,
|
181 | );
|
182 | }
|
183 |
|
184 | export function restoreDeserializedObject(object: any) {
|
185 | return mapObject(object, value => {
|
186 |
|
187 | if (value && value.$$type) {
|
188 | let ctor = nameToCtor.get(value.$$type);
|
189 | if (ctor == null) {
|
190 | throw new Error(
|
191 | `Expected constructor ${value.$$type} to be registered with serializer to deserialize`,
|
192 | );
|
193 | }
|
194 |
|
195 | if (typeof ctor.deserialize === 'function') {
|
196 | return ctor.deserialize(value.value);
|
197 | }
|
198 |
|
199 | value = value.value;
|
200 | Object.setPrototypeOf(value, ctor.prototype);
|
201 | }
|
202 |
|
203 | return value;
|
204 | });
|
205 | }
|
206 |
|
207 | export function serialize(object: any): Buffer {
|
208 | let mapped = prepareForSerialization(object);
|
209 |
|
210 | return v8.serialize(mapped);
|
211 | }
|
212 |
|
213 | export function deserialize(buffer: Buffer): any {
|
214 |
|
215 | let obj = v8.deserialize(buffer);
|
216 | return restoreDeserializedObject(obj);
|
217 | }
|