1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 | const createHash = require("../util/createHash");
|
8 | const ArraySerializer = require("./ArraySerializer");
|
9 | const DateObjectSerializer = require("./DateObjectSerializer");
|
10 | const ErrorObjectSerializer = require("./ErrorObjectSerializer");
|
11 | const MapObjectSerializer = require("./MapObjectSerializer");
|
12 | const NullPrototypeObjectSerializer = require("./NullPrototypeObjectSerializer");
|
13 | const PlainObjectSerializer = require("./PlainObjectSerializer");
|
14 | const RegExpObjectSerializer = require("./RegExpObjectSerializer");
|
15 | const SerializerMiddleware = require("./SerializerMiddleware");
|
16 | const SetObjectSerializer = require("./SetObjectSerializer");
|
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 | const setSetSize = (set, size) => {
|
63 | let i = 0;
|
64 | for (const item of set) {
|
65 | if (i++ >= size) {
|
66 | set.delete(item);
|
67 | }
|
68 | }
|
69 | };
|
70 |
|
71 | const setMapSize = (map, size) => {
|
72 | let i = 0;
|
73 | for (const item of map.keys()) {
|
74 | if (i++ >= size) {
|
75 | map.delete(item);
|
76 | }
|
77 | }
|
78 | };
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | const toHash = (buffer, hashFunction) => {
|
86 | const hash = createHash(hashFunction);
|
87 | hash.update(buffer);
|
88 | return (hash.digest("latin1"));
|
89 | };
|
90 |
|
91 | const ESCAPE = null;
|
92 | const ESCAPE_ESCAPE_VALUE = null;
|
93 | const ESCAPE_END_OBJECT = true;
|
94 | const ESCAPE_UNDEFINED = false;
|
95 |
|
96 | const CURRENT_VERSION = 2;
|
97 |
|
98 | const serializers = new Map();
|
99 | const serializerInversed = new Map();
|
100 |
|
101 | const loadedRequests = new Set();
|
102 |
|
103 | const NOT_SERIALIZABLE = {};
|
104 |
|
105 | const jsTypes = new Map();
|
106 | jsTypes.set(Object, new PlainObjectSerializer());
|
107 | jsTypes.set(Array, new ArraySerializer());
|
108 | jsTypes.set(null, new NullPrototypeObjectSerializer());
|
109 | jsTypes.set(Map, new MapObjectSerializer());
|
110 | jsTypes.set(Set, new SetObjectSerializer());
|
111 | jsTypes.set(Date, new DateObjectSerializer());
|
112 | jsTypes.set(RegExp, new RegExpObjectSerializer());
|
113 | jsTypes.set(Error, new ErrorObjectSerializer(Error));
|
114 | jsTypes.set(EvalError, new ErrorObjectSerializer(EvalError));
|
115 | jsTypes.set(RangeError, new ErrorObjectSerializer(RangeError));
|
116 | jsTypes.set(ReferenceError, new ErrorObjectSerializer(ReferenceError));
|
117 | jsTypes.set(SyntaxError, new ErrorObjectSerializer(SyntaxError));
|
118 | jsTypes.set(TypeError, new ErrorObjectSerializer(TypeError));
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | if (exports.constructor !== Object) {
|
124 | const Obj = (exports.constructor);
|
125 | const Fn = (Obj.constructor);
|
126 | for (const [type, config] of Array.from(jsTypes)) {
|
127 | if (type) {
|
128 | const Type = new Fn(`return ${type.name};`)();
|
129 | jsTypes.set(Type, config);
|
130 | }
|
131 | }
|
132 | }
|
133 |
|
134 | {
|
135 | let i = 1;
|
136 | for (const [type, serializer] of jsTypes) {
|
137 | serializers.set(type, {
|
138 | request: "",
|
139 | name: i++,
|
140 | serializer
|
141 | });
|
142 | }
|
143 | }
|
144 |
|
145 | for (const { request, name, serializer } of serializers.values()) {
|
146 | serializerInversed.set(`${request}/${name}`, serializer);
|
147 | }
|
148 |
|
149 |
|
150 | const loaders = new Map();
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 | class ObjectMiddleware extends SerializerMiddleware {
|
158 | |
159 |
|
160 |
|
161 |
|
162 | constructor(extendContext, hashFunction = "md4") {
|
163 | super();
|
164 | this.extendContext = extendContext;
|
165 | this._hashFunction = hashFunction;
|
166 | }
|
167 | |
168 |
|
169 |
|
170 |
|
171 |
|
172 | static registerLoader(regExp, loader) {
|
173 | loaders.set(regExp, loader);
|
174 | }
|
175 |
|
176 | |
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 | static register(Constructor, request, name, serializer) {
|
184 | const key = request + "/" + name;
|
185 |
|
186 | if (serializers.has(Constructor)) {
|
187 | throw new Error(
|
188 | `ObjectMiddleware.register: serializer for ${Constructor.name} is already registered`
|
189 | );
|
190 | }
|
191 |
|
192 | if (serializerInversed.has(key)) {
|
193 | throw new Error(
|
194 | `ObjectMiddleware.register: serializer for ${key} is already registered`
|
195 | );
|
196 | }
|
197 |
|
198 | serializers.set(Constructor, {
|
199 | request,
|
200 | name,
|
201 | serializer
|
202 | });
|
203 |
|
204 | serializerInversed.set(key, serializer);
|
205 | }
|
206 |
|
207 | |
208 |
|
209 |
|
210 |
|
211 | static registerNotSerializable(Constructor) {
|
212 | if (serializers.has(Constructor)) {
|
213 | throw new Error(
|
214 | `ObjectMiddleware.registerNotSerializable: serializer for ${Constructor.name} is already registered`
|
215 | );
|
216 | }
|
217 |
|
218 | serializers.set(Constructor, NOT_SERIALIZABLE);
|
219 | }
|
220 |
|
221 | static getSerializerFor(object) {
|
222 | const proto = Object.getPrototypeOf(object);
|
223 | let c;
|
224 | if (proto === null) {
|
225 |
|
226 | c = null;
|
227 | } else {
|
228 | c = proto.constructor;
|
229 | if (!c) {
|
230 | throw new Error(
|
231 | "Serialization of objects with prototype without valid constructor property not possible"
|
232 | );
|
233 | }
|
234 | }
|
235 | const config = serializers.get(c);
|
236 |
|
237 | if (!config) throw new Error(`No serializer registered for ${c.name}`);
|
238 | if (config === NOT_SERIALIZABLE) throw NOT_SERIALIZABLE;
|
239 |
|
240 | return config;
|
241 | }
|
242 |
|
243 | static getDeserializerFor(request, name) {
|
244 | const key = request + "/" + name;
|
245 | const serializer = serializerInversed.get(key);
|
246 |
|
247 | if (serializer === undefined) {
|
248 | throw new Error(`No deserializer registered for ${key}`);
|
249 | }
|
250 |
|
251 | return serializer;
|
252 | }
|
253 |
|
254 | static _getDeserializerForWithoutError(request, name) {
|
255 | const key = request + "/" + name;
|
256 | const serializer = serializerInversed.get(key);
|
257 | return serializer;
|
258 | }
|
259 |
|
260 | |
261 |
|
262 |
|
263 |
|
264 |
|
265 | serialize(data, context) {
|
266 |
|
267 | let result = [CURRENT_VERSION];
|
268 | let currentPos = 0;
|
269 | let referenceable = new Map();
|
270 | const addReferenceable = item => {
|
271 | referenceable.set(item, currentPos++);
|
272 | };
|
273 | let bufferDedupeMap = new Map();
|
274 | const dedupeBuffer = buf => {
|
275 | const len = buf.length;
|
276 | const entry = bufferDedupeMap.get(len);
|
277 | if (entry === undefined) {
|
278 | bufferDedupeMap.set(len, buf);
|
279 | return buf;
|
280 | }
|
281 | if (Buffer.isBuffer(entry)) {
|
282 | if (len < 32) {
|
283 | if (buf.equals(entry)) {
|
284 | return entry;
|
285 | }
|
286 | bufferDedupeMap.set(len, [entry, buf]);
|
287 | return buf;
|
288 | } else {
|
289 | const hash = toHash(entry, this._hashFunction);
|
290 | const newMap = new Map();
|
291 | newMap.set(hash, entry);
|
292 | bufferDedupeMap.set(len, newMap);
|
293 | const hashBuf = toHash(buf, this._hashFunction);
|
294 | if (hash === hashBuf) {
|
295 | return entry;
|
296 | }
|
297 | return buf;
|
298 | }
|
299 | } else if (Array.isArray(entry)) {
|
300 | if (entry.length < 16) {
|
301 | for (const item of entry) {
|
302 | if (buf.equals(item)) {
|
303 | return item;
|
304 | }
|
305 | }
|
306 | entry.push(buf);
|
307 | return buf;
|
308 | } else {
|
309 | const newMap = new Map();
|
310 | const hash = toHash(buf, this._hashFunction);
|
311 | let found;
|
312 | for (const item of entry) {
|
313 | const itemHash = toHash(item, this._hashFunction);
|
314 | newMap.set(itemHash, item);
|
315 | if (found === undefined && itemHash === hash) found = item;
|
316 | }
|
317 | bufferDedupeMap.set(len, newMap);
|
318 | if (found === undefined) {
|
319 | newMap.set(hash, buf);
|
320 | return buf;
|
321 | } else {
|
322 | return found;
|
323 | }
|
324 | }
|
325 | } else {
|
326 | const hash = toHash(buf, this._hashFunction);
|
327 | const item = entry.get(hash);
|
328 | if (item !== undefined) {
|
329 | return item;
|
330 | }
|
331 | entry.set(hash, buf);
|
332 | return buf;
|
333 | }
|
334 | };
|
335 | let currentPosTypeLookup = 0;
|
336 | let objectTypeLookup = new Map();
|
337 | const cycleStack = new Set();
|
338 | const stackToString = item => {
|
339 | const arr = Array.from(cycleStack);
|
340 | arr.push(item);
|
341 | return arr
|
342 | .map(item => {
|
343 | if (typeof item === "string") {
|
344 | if (item.length > 100) {
|
345 | return `String ${JSON.stringify(item.slice(0, 100)).slice(
|
346 | 0,
|
347 | -1
|
348 | )}..."`;
|
349 | }
|
350 | return `String ${JSON.stringify(item)}`;
|
351 | }
|
352 | try {
|
353 | const { request, name } = ObjectMiddleware.getSerializerFor(item);
|
354 | if (request) {
|
355 | return `${request}${name ? `.${name}` : ""}`;
|
356 | }
|
357 | } catch (e) {
|
358 |
|
359 | }
|
360 | if (typeof item === "object" && item !== null) {
|
361 | if (item.constructor) {
|
362 | if (item.constructor === Object)
|
363 | return `Object { ${Object.keys(item).join(", ")} }`;
|
364 | if (item.constructor === Map) return `Map { ${item.size} items }`;
|
365 | if (item.constructor === Array)
|
366 | return `Array { ${item.length} items }`;
|
367 | if (item.constructor === Set) return `Set { ${item.size} items }`;
|
368 | if (item.constructor === RegExp) return item.toString();
|
369 | return `${item.constructor.name}`;
|
370 | }
|
371 | return `Object [null prototype] { ${Object.keys(item).join(
|
372 | ", "
|
373 | )} }`;
|
374 | }
|
375 | try {
|
376 | return `${item}`;
|
377 | } catch (e) {
|
378 | return `(${e.message})`;
|
379 | }
|
380 | })
|
381 | .join(" -> ");
|
382 | };
|
383 | let hasDebugInfoAttached;
|
384 | let ctx = {
|
385 | write(value, key) {
|
386 | try {
|
387 | process(value);
|
388 | } catch (e) {
|
389 | if (e !== NOT_SERIALIZABLE) {
|
390 | if (hasDebugInfoAttached === undefined)
|
391 | hasDebugInfoAttached = new WeakSet();
|
392 | if (!hasDebugInfoAttached.has(e)) {
|
393 | e.message += `\nwhile serializing ${stackToString(value)}`;
|
394 | hasDebugInfoAttached.add(e);
|
395 | }
|
396 | }
|
397 | throw e;
|
398 | }
|
399 | },
|
400 | setCircularReference(ref) {
|
401 | addReferenceable(ref);
|
402 | },
|
403 | snapshot() {
|
404 | return {
|
405 | length: result.length,
|
406 | cycleStackSize: cycleStack.size,
|
407 | referenceableSize: referenceable.size,
|
408 | currentPos,
|
409 | objectTypeLookupSize: objectTypeLookup.size,
|
410 | currentPosTypeLookup
|
411 | };
|
412 | },
|
413 | rollback(snapshot) {
|
414 | result.length = snapshot.length;
|
415 | setSetSize(cycleStack, snapshot.cycleStackSize);
|
416 | setMapSize(referenceable, snapshot.referenceableSize);
|
417 | currentPos = snapshot.currentPos;
|
418 | setMapSize(objectTypeLookup, snapshot.objectTypeLookupSize);
|
419 | currentPosTypeLookup = snapshot.currentPosTypeLookup;
|
420 | },
|
421 | ...context
|
422 | };
|
423 | this.extendContext(ctx);
|
424 | const process = item => {
|
425 | if (Buffer.isBuffer(item)) {
|
426 |
|
427 | const ref = referenceable.get(item);
|
428 | if (ref !== undefined) {
|
429 | result.push(ESCAPE, ref - currentPos);
|
430 | return;
|
431 | }
|
432 | const alreadyUsedBuffer = dedupeBuffer(item);
|
433 | if (alreadyUsedBuffer !== item) {
|
434 | const ref = referenceable.get(alreadyUsedBuffer);
|
435 | if (ref !== undefined) {
|
436 | referenceable.set(item, ref);
|
437 | result.push(ESCAPE, ref - currentPos);
|
438 | return;
|
439 | }
|
440 | item = alreadyUsedBuffer;
|
441 | }
|
442 | addReferenceable(item);
|
443 |
|
444 | result.push(item);
|
445 | } else if (item === ESCAPE) {
|
446 | result.push(ESCAPE, ESCAPE_ESCAPE_VALUE);
|
447 | } else if (
|
448 | typeof item === "object"
|
449 |
|
450 | ) {
|
451 |
|
452 | const ref = referenceable.get(item);
|
453 | if (ref !== undefined) {
|
454 | result.push(ESCAPE, ref - currentPos);
|
455 | return;
|
456 | }
|
457 |
|
458 | if (cycleStack.has(item)) {
|
459 | throw new Error(
|
460 | `This is a circular references. To serialize circular references use 'setCircularReference' somewhere in the circle during serialize and deserialize.`
|
461 | );
|
462 | }
|
463 |
|
464 | const { request, name, serializer } =
|
465 | ObjectMiddleware.getSerializerFor(item);
|
466 | const key = `${request}/${name}`;
|
467 | const lastIndex = objectTypeLookup.get(key);
|
468 |
|
469 | if (lastIndex === undefined) {
|
470 | objectTypeLookup.set(key, currentPosTypeLookup++);
|
471 |
|
472 | result.push(ESCAPE, request, name);
|
473 | } else {
|
474 | result.push(ESCAPE, currentPosTypeLookup - lastIndex);
|
475 | }
|
476 |
|
477 | cycleStack.add(item);
|
478 |
|
479 | try {
|
480 | serializer.serialize(item, ctx);
|
481 | } finally {
|
482 | cycleStack.delete(item);
|
483 | }
|
484 |
|
485 | result.push(ESCAPE, ESCAPE_END_OBJECT);
|
486 |
|
487 | addReferenceable(item);
|
488 | } else if (typeof item === "string") {
|
489 | if (item.length > 1) {
|
490 |
|
491 |
|
492 | const ref = referenceable.get(item);
|
493 | if (ref !== undefined) {
|
494 | result.push(ESCAPE, ref - currentPos);
|
495 | return;
|
496 | }
|
497 | addReferenceable(item);
|
498 | }
|
499 |
|
500 | if (item.length > 102400 && context.logger) {
|
501 | context.logger.warn(
|
502 | `Serializing big strings (${Math.round(
|
503 | item.length / 1024
|
504 | )}kiB) impacts deserialization performance (consider using Buffer instead and decode when needed)`
|
505 | );
|
506 | }
|
507 |
|
508 | result.push(item);
|
509 | } else if (typeof item === "function") {
|
510 | if (!SerializerMiddleware.isLazy(item))
|
511 | throw new Error("Unexpected function " + item);
|
512 | /** @type {SerializedType} */
|
513 | const serializedData =
|
514 | SerializerMiddleware.getLazySerializedValue(item);
|
515 | if (serializedData !== undefined) {
|
516 | if (typeof serializedData === "function") {
|
517 | result.push(serializedData);
|
518 | } else {
|
519 | throw new Error("Not implemented");
|
520 | }
|
521 | } else if (SerializerMiddleware.isLazy(item, this)) {
|
522 | throw new Error("Not implemented");
|
523 | } else {
|
524 | const data = SerializerMiddleware.serializeLazy(item, data =>
|
525 | this.serialize([data], context)
|
526 | );
|
527 | SerializerMiddleware.setLazySerializedValue(item, data);
|
528 | result.push(data);
|
529 | }
|
530 | } else if (item === undefined) {
|
531 | result.push(ESCAPE, ESCAPE_UNDEFINED);
|
532 | } else {
|
533 | result.push(item);
|
534 | }
|
535 | };
|
536 |
|
537 | try {
|
538 | for (const item of data) {
|
539 | process(item);
|
540 | }
|
541 | return result;
|
542 | } catch (e) {
|
543 | if (e === NOT_SERIALIZABLE) return null;
|
544 |
|
545 | throw e;
|
546 | } finally {
|
547 | // Get rid of these references to avoid leaking memory
|
548 | // This happens because the optimized code v8 generates
|
549 | // is optimized for our "ctx.write" method so it will reference
|
550 | // it from e. g. Dependency.prototype.serialize -(IC)-> ctx.write
|
551 | data =
|
552 | result =
|
553 | referenceable =
|
554 | bufferDedupeMap =
|
555 | objectTypeLookup =
|
556 | ctx =
|
557 | undefined;
|
558 | }
|
559 | }
|
560 |
|
561 | /**
|
562 | * @param {SerializedType} data data
|
563 | * @param {Object} context context object
|
564 | * @returns {DeserializedType|Promise<DeserializedType>} deserialized data
|
565 | */
|
566 | deserialize(data, context) {
|
567 | let currentDataPos = 0;
|
568 | const read = () => {
|
569 | if (currentDataPos >= data.length)
|
570 | throw new Error("Unexpected end of stream");
|
571 |
|
572 | return data[currentDataPos++];
|
573 | };
|
574 |
|
575 | if (read() !== CURRENT_VERSION)
|
576 | throw new Error("Version mismatch, serializer changed");
|
577 |
|
578 | let currentPos = 0;
|
579 | let referenceable = [];
|
580 | const addReferenceable = item => {
|
581 | referenceable.push(item);
|
582 | currentPos++;
|
583 | };
|
584 | let currentPosTypeLookup = 0;
|
585 | let objectTypeLookup = [];
|
586 | let result = [];
|
587 | let ctx = {
|
588 | read() {
|
589 | return decodeValue();
|
590 | },
|
591 | setCircularReference(ref) {
|
592 | addReferenceable(ref);
|
593 | },
|
594 | ...context
|
595 | };
|
596 | this.extendContext(ctx);
|
597 | const decodeValue = () => {
|
598 | const item = read();
|
599 |
|
600 | if (item === ESCAPE) {
|
601 | const nextItem = read();
|
602 |
|
603 | if (nextItem === ESCAPE_ESCAPE_VALUE) {
|
604 | return ESCAPE;
|
605 | } else if (nextItem === ESCAPE_UNDEFINED) {
|
606 | return undefined;
|
607 | } else if (nextItem === ESCAPE_END_OBJECT) {
|
608 | throw new Error(
|
609 | `Unexpected end of object at position ${currentDataPos - 1}`
|
610 | );
|
611 | } else {
|
612 | const request = nextItem;
|
613 | let serializer;
|
614 |
|
615 | if (typeof request === "number") {
|
616 | if (request < 0) {
|
617 | // relative reference
|
618 | return referenceable[currentPos + request];
|
619 | }
|
620 | serializer = objectTypeLookup[currentPosTypeLookup - request];
|
621 | } else {
|
622 | if (typeof request !== "string") {
|
623 | throw new Error(
|
624 | `Unexpected type (${typeof request}) of request ` +
|
625 | `at position ${currentDataPos - 1}`
|
626 | );
|
627 | }
|
628 | const name = read();
|
629 |
|
630 | serializer = ObjectMiddleware._getDeserializerForWithoutError(
|
631 | request,
|
632 | name
|
633 | );
|
634 |
|
635 | if (serializer === undefined) {
|
636 | if (request && !loadedRequests.has(request)) {
|
637 | let loaded = false;
|
638 | for (const [regExp, loader] of loaders) {
|
639 | if (regExp.test(request)) {
|
640 | if (loader(request)) {
|
641 | loaded = true;
|
642 | break;
|
643 | }
|
644 | }
|
645 | }
|
646 | if (!loaded) {
|
647 | require(request);
|
648 | }
|
649 |
|
650 | loadedRequests.add(request);
|
651 | }
|
652 |
|
653 | serializer = ObjectMiddleware.getDeserializerFor(request, name);
|
654 | }
|
655 |
|
656 | objectTypeLookup.push(serializer);
|
657 | currentPosTypeLookup++;
|
658 | }
|
659 | try {
|
660 | const item = serializer.deserialize(ctx);
|
661 | const end1 = read();
|
662 |
|
663 | if (end1 !== ESCAPE) {
|
664 | throw new Error("Expected end of object");
|
665 | }
|
666 |
|
667 | const end2 = read();
|
668 |
|
669 | if (end2 !== ESCAPE_END_OBJECT) {
|
670 | throw new Error("Expected end of object");
|
671 | }
|
672 |
|
673 | addReferenceable(item);
|
674 |
|
675 | return item;
|
676 | } catch (err) {
|
677 | // As this is only for error handling, we omit creating a Map for
|
678 | // faster access to this information, as this would affect performance
|
679 | // in the good case
|
680 | let serializerEntry;
|
681 | for (const entry of serializers) {
|
682 | if (entry[1].serializer === serializer) {
|
683 | serializerEntry = entry;
|
684 | break;
|
685 | }
|
686 | }
|
687 | const name = !serializerEntry
|
688 | ? "unknown"
|
689 | : !serializerEntry[1].request
|
690 | ? serializerEntry[0].name
|
691 | : serializerEntry[1].name
|
692 | ? `${serializerEntry[1].request} ${serializerEntry[1].name}`
|
693 | : serializerEntry[1].request;
|
694 | err.message += `\n(during deserialization of ${name})`;
|
695 | throw err;
|
696 | }
|
697 | }
|
698 | } else if (typeof item === "string") {
|
699 | if (item.length > 1) {
|
700 | addReferenceable(item);
|
701 | }
|
702 |
|
703 | return item;
|
704 | } else if (Buffer.isBuffer(item)) {
|
705 | addReferenceable(item);
|
706 |
|
707 | return item;
|
708 | } else if (typeof item === "function") {
|
709 | return SerializerMiddleware.deserializeLazy(
|
710 | item,
|
711 | data => this.deserialize(data, context)[0]
|
712 | );
|
713 | } else {
|
714 | return item;
|
715 | }
|
716 | };
|
717 |
|
718 | try {
|
719 | while (currentDataPos < data.length) {
|
720 | result.push(decodeValue());
|
721 | }
|
722 | return result;
|
723 | } finally {
|
724 | // Get rid of these references to avoid leaking memory
|
725 | // This happens because the optimized code v8 generates
|
726 | // is optimized for our "ctx.read" method so it will reference
|
727 | // it from e. g. Dependency.prototype.deserialize -(IC)-> ctx.read
|
728 | result = referenceable = data = objectTypeLookup = ctx = undefined;
|
729 | }
|
730 | }
|
731 | }
|
732 |
|
733 | module.exports = ObjectMiddleware;
|
734 | module.exports.NOT_SERIALIZABLE = NOT_SERIALIZABLE;
|
735 |
|
\ | No newline at end of file |