source: frontend/node_modules/webpack/lib/serialization/ObjectMiddleware.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 25.7 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3*/
4
5"use strict";
6
7const { DEFAULTS } = require("../config/defaults");
8const createHash = require("../util/createHash");
9const AggregateErrorSerializer = require("./AggregateErrorSerializer");
10const ArraySerializer = require("./ArraySerializer");
11const DateObjectSerializer = require("./DateObjectSerializer");
12const ErrorObjectSerializer = require("./ErrorObjectSerializer");
13const MapObjectSerializer = require("./MapObjectSerializer");
14const NullPrototypeObjectSerializer = require("./NullPrototypeObjectSerializer");
15const PlainObjectSerializer = require("./PlainObjectSerializer");
16const RegExpObjectSerializer = require("./RegExpObjectSerializer");
17const SerializerMiddleware = require("./SerializerMiddleware");
18const SetObjectSerializer = require("./SetObjectSerializer");
19
20/** @typedef {import("../logging/Logger").Logger} Logger */
21/** @typedef {import("../util/Hash").HashFunction} HashFunction */
22/** @typedef {import("./SerializerMiddleware").LazyOptions} LazyOptions */
23/** @typedef {import("./types").ComplexSerializableType} ComplexSerializableType */
24/** @typedef {import("./types").PrimitiveSerializableType} PrimitiveSerializableType */
25
26/** @typedef {new (...params: EXPECTED_ANY[]) => EXPECTED_ANY} Constructor */
27
28/*
29
30Format:
31
32File -> Section*
33Section -> ObjectSection | ReferenceSection | EscapeSection | OtherSection
34
35ObjectSection -> ESCAPE (
36 number:relativeOffset (number > 0) |
37 string:request (string|null):export
38) Section:value* ESCAPE ESCAPE_END_OBJECT
39ReferenceSection -> ESCAPE number:relativeOffset (number < 0)
40EscapeSection -> ESCAPE ESCAPE_ESCAPE_VALUE (escaped value ESCAPE)
41EscapeSection -> ESCAPE ESCAPE_UNDEFINED (escaped value ESCAPE)
42OtherSection -> any (except ESCAPE)
43
44Why using null as escape value?
45Multiple null values can merged by the BinaryMiddleware, which makes it very efficient
46Technically any value can be used.
47
48*/
49
50/**
51 * Defines the object serializer snapshot type used by this module.
52 * @typedef {object} ObjectSerializerSnapshot
53 * @property {number} length
54 * @property {number} cycleStackSize
55 * @property {number} referenceableSize
56 * @property {number} currentPos
57 * @property {number} objectTypeLookupSize
58 * @property {number} currentPosTypeLookup
59 */
60
61/** @typedef {EXPECTED_OBJECT | string} ReferenceableItem */
62
63/**
64 * Defines the object serializer context type used by this module.
65 * @typedef {object} ObjectSerializerContext
66 * @property {(value: EXPECTED_ANY) => void} write
67 * @property {(value: ReferenceableItem) => void} setCircularReference
68 * @property {() => ObjectSerializerSnapshot} snapshot
69 * @property {(snapshot: ObjectSerializerSnapshot) => void} rollback
70 * @property {((item: EXPECTED_ANY | (() => EXPECTED_ANY)) => void)=} writeLazy
71 * @property {((item: (EXPECTED_ANY | (() => EXPECTED_ANY)), obj: LazyOptions | undefined) => import("./SerializerMiddleware").LazyFunction<EXPECTED_ANY, EXPECTED_ANY, EXPECTED_ANY, LazyOptions>)=} writeSeparate
72 */
73
74/**
75 * Defines the object deserializer context type used by this module.
76 * @typedef {object} ObjectDeserializerContext
77 * @property {() => EXPECTED_ANY} read
78 * @property {(value: ReferenceableItem) => void} setCircularReference
79 */
80
81/**
82 * Defines the object serializer type used by this module.
83 * @typedef {object} ObjectSerializer
84 * @property {(value: EXPECTED_ANY, context: ObjectSerializerContext) => void} serialize
85 * @property {(context: ObjectDeserializerContext) => EXPECTED_ANY} deserialize
86 */
87
88/**
89 * Updates set size using the provided set.
90 * @template T
91 * @param {Set<T>} set set
92 * @param {number} size count of items to keep
93 */
94const setSetSize = (set, size) => {
95 let i = 0;
96 for (const item of set) {
97 if (i++ >= size) {
98 set.delete(item);
99 }
100 }
101};
102
103/**
104 * Updates map size using the provided map.
105 * @template K, X
106 * @param {Map<K, X>} map map
107 * @param {number} size count of items to keep
108 */
109const setMapSize = (map, size) => {
110 let i = 0;
111 for (const item of map.keys()) {
112 if (i++ >= size) {
113 map.delete(item);
114 }
115 }
116};
117
118/**
119 * Returns hash.
120 * @param {Buffer} buffer buffer
121 * @param {HashFunction} hashFunction hash function to use
122 * @returns {string} hash
123 */
124const toHash = (buffer, hashFunction) => {
125 const hash = createHash(hashFunction);
126 hash.update(buffer);
127 return hash.digest("latin1");
128};
129
130const ESCAPE = null;
131const ESCAPE_ESCAPE_VALUE = null;
132const ESCAPE_END_OBJECT = true;
133const ESCAPE_UNDEFINED = false;
134
135const CURRENT_VERSION = 2;
136
137/** @typedef {{ request?: string, name?: string | number | null, serializer?: ObjectSerializer }} SerializerConfig */
138/** @typedef {{ request?: string, name?: string | number | null, serializer: ObjectSerializer }} SerializerConfigWithSerializer */
139
140/** @type {Map<Constructor | null, SerializerConfig>} */
141const serializers = new Map();
142/** @type {Map<string | number, ObjectSerializer>} */
143const serializerInversed = new Map();
144
145/** @type {Set<string>} */
146const loadedRequests = new Set();
147
148const NOT_SERIALIZABLE = {};
149
150/** @type {Map<Constructor | null, ObjectSerializer>} */
151const jsTypes = new Map();
152
153jsTypes.set(Object, new PlainObjectSerializer());
154jsTypes.set(Array, new ArraySerializer());
155jsTypes.set(null, new NullPrototypeObjectSerializer());
156jsTypes.set(Map, new MapObjectSerializer());
157jsTypes.set(Set, new SetObjectSerializer());
158jsTypes.set(Date, new DateObjectSerializer());
159jsTypes.set(RegExp, new RegExpObjectSerializer());
160jsTypes.set(Error, new ErrorObjectSerializer(Error));
161jsTypes.set(EvalError, new ErrorObjectSerializer(EvalError));
162jsTypes.set(RangeError, new ErrorObjectSerializer(RangeError));
163jsTypes.set(ReferenceError, new ErrorObjectSerializer(ReferenceError));
164jsTypes.set(SyntaxError, new ErrorObjectSerializer(SyntaxError));
165jsTypes.set(TypeError, new ErrorObjectSerializer(TypeError));
166
167// eslint-disable-next-line n/no-unsupported-features/es-builtins, n/no-unsupported-features/es-syntax
168if (typeof AggregateError !== "undefined") {
169 jsTypes.set(
170 // eslint-disable-next-line n/no-unsupported-features/es-builtins, n/no-unsupported-features/es-syntax
171 AggregateError,
172 new AggregateErrorSerializer()
173 );
174}
175
176// If in a sandboxed environment (e.g. jest), this escapes the sandbox and registers
177// real Object and Array types to. These types may occur in the wild too, e.g. when
178// using Structured Clone in postMessage.
179// eslint-disable-next-line n/exports-style
180if (exports.constructor !== Object) {
181 // eslint-disable-next-line n/exports-style
182 const Obj = /** @type {ObjectConstructor} */ (exports.constructor);
183 const Fn = /** @type {FunctionConstructor} */ (Obj.constructor);
184 for (const [type, config] of jsTypes) {
185 if (type) {
186 const Type = new Fn(`return ${type.name};`)();
187 jsTypes.set(Type, config);
188 }
189 }
190}
191
192{
193 let i = 1;
194 for (const [type, serializer] of jsTypes) {
195 serializers.set(type, {
196 request: "",
197 name: i++,
198 serializer
199 });
200 }
201}
202
203for (const { request, name, serializer } of serializers.values()) {
204 serializerInversed.set(
205 `${request}/${name}`,
206 /** @type {ObjectSerializer} */ (serializer)
207 );
208}
209
210/** @type {Map<RegExp, (request: string) => boolean>} */
211const loaders = new Map();
212
213/** @typedef {ComplexSerializableType[]} DeserializedType */
214/** @typedef {PrimitiveSerializableType[]} SerializedType */
215/** @typedef {{ logger: Logger }} Context */
216
217/** @typedef {(context: ObjectSerializerContext | ObjectDeserializerContext) => void} ExtendContext */
218
219/**
220 * Represents ObjectMiddleware.
221 * @extends {SerializerMiddleware<DeserializedType, SerializedType, Context>}
222 */
223class ObjectMiddleware extends SerializerMiddleware {
224 /**
225 * Creates an instance of ObjectMiddleware.
226 * @param {ExtendContext} extendContext context extensions
227 * @param {HashFunction} hashFunction hash function to use
228 */
229 constructor(extendContext, hashFunction = DEFAULTS.HASH_FUNCTION) {
230 super();
231 /** @type {ExtendContext} */
232 this.extendContext = extendContext;
233 /** @type {HashFunction} */
234 this._hashFunction = hashFunction;
235 }
236
237 /**
238 * Processes the provided reg exp.
239 * @param {RegExp} regExp RegExp for which the request is tested
240 * @param {(request: string) => boolean} loader loader to load the request, returns true when successful
241 * @returns {void}
242 */
243 static registerLoader(regExp, loader) {
244 loaders.set(regExp, loader);
245 }
246
247 /**
248 * Processes the provided constructor.
249 * @param {Constructor} Constructor the constructor
250 * @param {string} request the request which will be required when deserializing
251 * @param {string | null} name the name to make multiple serializer unique when sharing a request
252 * @param {ObjectSerializer} serializer the serializer
253 * @returns {void}
254 */
255 static register(Constructor, request, name, serializer) {
256 const key = `${request}/${name}`;
257
258 if (serializers.has(Constructor)) {
259 throw new Error(
260 `ObjectMiddleware.register: serializer for ${Constructor.name} is already registered`
261 );
262 }
263
264 if (serializerInversed.has(key)) {
265 throw new Error(
266 `ObjectMiddleware.register: serializer for ${key} is already registered`
267 );
268 }
269
270 serializers.set(Constructor, {
271 request,
272 name,
273 serializer
274 });
275
276 serializerInversed.set(key, serializer);
277 }
278
279 /**
280 * Register not serializable.
281 * @param {Constructor} Constructor the constructor
282 * @returns {void}
283 */
284 static registerNotSerializable(Constructor) {
285 if (serializers.has(Constructor)) {
286 throw new Error(
287 `ObjectMiddleware.registerNotSerializable: serializer for ${Constructor.name} is already registered`
288 );
289 }
290
291 serializers.set(Constructor, NOT_SERIALIZABLE);
292 }
293
294 /**
295 * Gets serializer for.
296 * @param {EXPECTED_ANY} object for serialization
297 * @returns {SerializerConfigWithSerializer} Serializer config
298 */
299 static getSerializerFor(object) {
300 const proto = Object.getPrototypeOf(object);
301 /** @type {null | Constructor} */
302 let c;
303 if (proto === null) {
304 // Object created with Object.create(null)
305 c = null;
306 } else {
307 c = proto.constructor;
308 if (!c) {
309 throw new Error(
310 "Serialization of objects with prototype without valid constructor property not possible"
311 );
312 }
313 }
314 const config = serializers.get(c);
315
316 if (!config) {
317 throw new Error(
318 `No serializer registered for ${/** @type {Constructor} */ (c).name}`
319 );
320 }
321 if (config === NOT_SERIALIZABLE) throw NOT_SERIALIZABLE;
322
323 return /** @type {SerializerConfigWithSerializer} */ (config);
324 }
325
326 /**
327 * Gets deserializer for.
328 * @param {string} request request
329 * @param {string} name name
330 * @returns {ObjectSerializer} serializer
331 */
332 static getDeserializerFor(request, name) {
333 const key = `${request}/${name}`;
334 const serializer = serializerInversed.get(key);
335
336 if (serializer === undefined) {
337 throw new Error(`No deserializer registered for ${key}`);
338 }
339
340 return serializer;
341 }
342
343 /**
344 * Get deserializer for without error.
345 * @param {string} request request
346 * @param {string} name name
347 * @returns {ObjectSerializer | undefined} serializer
348 */
349 static _getDeserializerForWithoutError(request, name) {
350 const key = `${request}/${name}`;
351 const serializer = serializerInversed.get(key);
352 return serializer;
353 }
354
355 /**
356 * Serializes this instance into the provided serializer context.
357 * @param {DeserializedType} data data
358 * @param {Context} context context object
359 * @returns {SerializedType | Promise<SerializedType> | null} serialized data
360 */
361 serialize(data, context) {
362 /** @type {PrimitiveSerializableType[]} */
363 let result = [CURRENT_VERSION];
364 let currentPos = 0;
365 /** @type {Map<ReferenceableItem, number>} */
366 let referenceable = new Map();
367 /**
368 * Adds referenceable.
369 * @param {ReferenceableItem} item referenceable item
370 */
371 const addReferenceable = (item) => {
372 referenceable.set(item, currentPos++);
373 };
374 /** @type {Map<number, Buffer | [Buffer, Buffer] | Map<string, Buffer>>} */
375 let bufferDedupeMap = new Map();
376 /**
377 * Returns deduped buffer.
378 * @param {Buffer} buf buffer
379 * @returns {Buffer} deduped buffer
380 */
381 const dedupeBuffer = (buf) => {
382 const len = buf.length;
383 const entry = bufferDedupeMap.get(len);
384 if (entry === undefined) {
385 bufferDedupeMap.set(len, buf);
386 return buf;
387 }
388 if (Buffer.isBuffer(entry)) {
389 if (len < 32) {
390 if (buf.equals(entry)) {
391 return entry;
392 }
393 bufferDedupeMap.set(len, [entry, buf]);
394 return buf;
395 }
396 const hash = toHash(entry, this._hashFunction);
397 /** @type {Map<string, Buffer>} */
398 const newMap = new Map();
399 newMap.set(hash, entry);
400 bufferDedupeMap.set(len, newMap);
401 const hashBuf = toHash(buf, this._hashFunction);
402 if (hash === hashBuf) {
403 return entry;
404 }
405 return buf;
406 } else if (Array.isArray(entry)) {
407 if (entry.length < 16) {
408 for (const item of entry) {
409 if (buf.equals(item)) {
410 return item;
411 }
412 }
413 entry.push(buf);
414 return buf;
415 }
416 /** @type {Map<string, Buffer>} */
417 const newMap = new Map();
418 const hash = toHash(buf, this._hashFunction);
419 /** @type {undefined | Buffer} */
420 let found;
421 for (const item of entry) {
422 const itemHash = toHash(item, this._hashFunction);
423 newMap.set(itemHash, item);
424 if (found === undefined && itemHash === hash) found = item;
425 }
426 bufferDedupeMap.set(len, newMap);
427 if (found === undefined) {
428 newMap.set(hash, buf);
429 return buf;
430 }
431 return found;
432 }
433 const hash = toHash(buf, this._hashFunction);
434 const item = entry.get(hash);
435 if (item !== undefined) {
436 return item;
437 }
438 entry.set(hash, buf);
439 return buf;
440 };
441 let currentPosTypeLookup = 0;
442 /** @type {Map<ComplexSerializableType, number>} */
443 let objectTypeLookup = new Map();
444 /** @type {Set<ComplexSerializableType>} */
445 const cycleStack = new Set();
446 /**
447 * Returns stack.
448 * @param {ComplexSerializableType} item item to stack
449 * @returns {string} stack
450 */
451 const stackToString = (item) => {
452 const arr = [...cycleStack];
453 arr.push(item);
454 return arr
455 .map((item) => {
456 if (typeof item === "string") {
457 if (item.length > 100) {
458 return `String ${JSON.stringify(item.slice(0, 100)).slice(
459 0,
460 -1
461 )}..."`;
462 }
463 return `String ${JSON.stringify(item)}`;
464 }
465 try {
466 const { request, name } = ObjectMiddleware.getSerializerFor(item);
467 if (request) {
468 return `${request}${name ? `.${name}` : ""}`;
469 }
470 } catch (_err) {
471 // ignore -> fallback
472 }
473 if (typeof item === "object" && item !== null) {
474 if (item.constructor) {
475 if (item.constructor === Object) {
476 return `Object { ${Object.keys(item).join(", ")} }`;
477 }
478 if (item.constructor === Map) {
479 return `Map { ${/** @type {Map<EXPECTED_ANY, EXPECTED_ANY>} */ (item).size} items }`;
480 }
481 if (item.constructor === Array) {
482 return `Array { ${/** @type {EXPECTED_ANY[]} */ (item).length} items }`;
483 }
484 if (item.constructor === Set) {
485 return `Set { ${/** @type {Set<EXPECTED_ANY>} */ (item).size} items }`;
486 }
487 if (item.constructor === RegExp) {
488 return /** @type {RegExp} */ (item).toString();
489 }
490 return `${item.constructor.name}`;
491 }
492 return `Object [null prototype] { ${Object.keys(item).join(
493 ", "
494 )} }`;
495 }
496 if (typeof item === "bigint") {
497 return `BigInt ${item}n`;
498 }
499 try {
500 return `${item}`;
501 } catch (err) {
502 return `(${/** @type {Error} */ (err).message})`;
503 }
504 })
505 .join(" -> ");
506 };
507 /** @type {undefined | WeakSet<Error>} */
508 let hasDebugInfoAttached;
509 /** @type {ObjectSerializerContext} */
510 let ctx = {
511 write(value) {
512 try {
513 process(value);
514 } catch (err) {
515 if (err !== NOT_SERIALIZABLE) {
516 if (hasDebugInfoAttached === undefined) {
517 hasDebugInfoAttached = new WeakSet();
518 }
519 if (!hasDebugInfoAttached.has(/** @type {Error} */ (err))) {
520 /** @type {Error} */
521 (err).message += `\nwhile serializing ${stackToString(value)}`;
522 hasDebugInfoAttached.add(/** @type {Error} */ (err));
523 }
524 }
525 throw err;
526 }
527 },
528 setCircularReference(ref) {
529 addReferenceable(ref);
530 },
531 snapshot() {
532 return {
533 length: result.length,
534 cycleStackSize: cycleStack.size,
535 referenceableSize: referenceable.size,
536 currentPos,
537 objectTypeLookupSize: objectTypeLookup.size,
538 currentPosTypeLookup
539 };
540 },
541 rollback(snapshot) {
542 result.length = snapshot.length;
543 setSetSize(cycleStack, snapshot.cycleStackSize);
544 setMapSize(referenceable, snapshot.referenceableSize);
545 currentPos = snapshot.currentPos;
546 setMapSize(objectTypeLookup, snapshot.objectTypeLookupSize);
547 currentPosTypeLookup = snapshot.currentPosTypeLookup;
548 },
549 ...context
550 };
551 this.extendContext(ctx);
552 /**
553 * Processes the provided item.
554 * @param {ComplexSerializableType} item item to serialize
555 */
556 const process = (item) => {
557 if (Buffer.isBuffer(item)) {
558 // check if we can emit a reference
559 const ref = referenceable.get(item);
560 if (ref !== undefined) {
561 result.push(ESCAPE, ref - currentPos);
562 return;
563 }
564 const alreadyUsedBuffer = dedupeBuffer(item);
565 if (alreadyUsedBuffer !== item) {
566 const ref = referenceable.get(alreadyUsedBuffer);
567 if (ref !== undefined) {
568 referenceable.set(item, ref);
569 result.push(ESCAPE, ref - currentPos);
570 return;
571 }
572 item = alreadyUsedBuffer;
573 }
574 addReferenceable(item);
575
576 result.push(/** @type {Buffer} */ (item));
577 } else if (item === ESCAPE) {
578 result.push(ESCAPE, ESCAPE_ESCAPE_VALUE);
579 } else if (
580 typeof item === "object"
581 // We don't have to check for null as ESCAPE is null and this has been checked before
582 ) {
583 // check if we can emit a reference
584 const ref = referenceable.get(item);
585 if (ref !== undefined) {
586 result.push(ESCAPE, ref - currentPos);
587 return;
588 }
589
590 if (cycleStack.has(item)) {
591 throw new Error(
592 "This is a circular references. To serialize circular references use 'setCircularReference' somewhere in the circle during serialize and deserialize."
593 );
594 }
595
596 const { request, name, serializer } = ObjectMiddleware.getSerializerFor(
597 /** @type {Constructor} */
598 (item)
599 );
600 const key = `${request}/${name}`;
601 const lastIndex = objectTypeLookup.get(key);
602
603 if (lastIndex === undefined) {
604 objectTypeLookup.set(key, currentPosTypeLookup++);
605
606 result.push(ESCAPE, request, name);
607 } else {
608 result.push(ESCAPE, currentPosTypeLookup - lastIndex);
609 }
610
611 cycleStack.add(item);
612
613 try {
614 serializer.serialize(item, ctx);
615 } finally {
616 cycleStack.delete(item);
617 }
618
619 result.push(ESCAPE, ESCAPE_END_OBJECT);
620
621 addReferenceable(item);
622 } else if (typeof item === "string") {
623 if (item.length > 1) {
624 // short strings are shorter when not emitting a reference (this saves 1 byte per empty string)
625 // check if we can emit a reference
626 const ref = referenceable.get(item);
627 if (ref !== undefined) {
628 result.push(ESCAPE, ref - currentPos);
629 return;
630 }
631 addReferenceable(item);
632 }
633
634 if (item.length > 102400 && context.logger) {
635 context.logger.warn(
636 `Serializing big strings (${Math.round(
637 item.length / 1024
638 )}kiB) impacts deserialization performance (consider using Buffer instead and decode when needed)`
639 );
640 }
641
642 result.push(item);
643 } else if (typeof item === "function") {
644 if (!SerializerMiddleware.isLazy(item)) {
645 throw new Error(`Unexpected function ${item}`);
646 }
647
648 /** @type {SerializedType | undefined} */
649 const serializedData =
650 SerializerMiddleware.getLazySerializedValue(item);
651
652 if (serializedData !== undefined) {
653 if (typeof serializedData === "function") {
654 result.push(serializedData);
655 } else {
656 throw new Error("Not implemented");
657 }
658 } else if (SerializerMiddleware.isLazy(item, this)) {
659 throw new Error("Not implemented");
660 } else {
661 const data =
662 /** @type {() => PrimitiveSerializableType[] | Promise<PrimitiveSerializableType[]>} */
663 (
664 SerializerMiddleware.serializeLazy(item, (data) =>
665 this.serialize([data], context)
666 )
667 );
668 SerializerMiddleware.setLazySerializedValue(item, data);
669 result.push(data);
670 }
671 } else if (item === undefined) {
672 result.push(ESCAPE, ESCAPE_UNDEFINED);
673 } else {
674 result.push(item);
675 }
676 };
677
678 try {
679 for (const item of data) {
680 process(item);
681 }
682 return result;
683 } catch (err) {
684 if (err === NOT_SERIALIZABLE) return null;
685
686 throw err;
687 } finally {
688 // Get rid of these references to avoid leaking memory
689 // This happens because the optimized code v8 generates
690 // is optimized for our "ctx.write" method so it will reference
691 // it from e. g. Dependency.prototype.serialize -(IC)-> ctx.write
692 data =
693 result =
694 referenceable =
695 bufferDedupeMap =
696 objectTypeLookup =
697 ctx =
698 /** @type {EXPECTED_ANY} */
699 (undefined);
700 }
701 }
702
703 /**
704 * Restores this instance from the provided deserializer context.
705 * @param {SerializedType} data data
706 * @param {Context} context context object
707 * @returns {DeserializedType | Promise<DeserializedType>} deserialized data
708 */
709 deserialize(data, context) {
710 let currentDataPos = 0;
711 const read = () => {
712 if (currentDataPos >= data.length) {
713 throw new Error("Unexpected end of stream");
714 }
715
716 return data[currentDataPos++];
717 };
718
719 if (read() !== CURRENT_VERSION) {
720 throw new Error("Version mismatch, serializer changed");
721 }
722
723 let currentPos = 0;
724 /** @type {ReferenceableItem[]} */
725 let referenceable = [];
726 /**
727 * Adds referenceable.
728 * @param {ReferenceableItem} item referenceable item
729 */
730 const addReferenceable = (item) => {
731 referenceable.push(item);
732 currentPos++;
733 };
734 let currentPosTypeLookup = 0;
735 /** @type {ObjectSerializer[]} */
736 let objectTypeLookup = [];
737 /** @type {ComplexSerializableType[]} */
738 let result = [];
739 /** @type {ObjectDeserializerContext} */
740 let ctx = {
741 read() {
742 return decodeValue();
743 },
744 setCircularReference(ref) {
745 addReferenceable(ref);
746 },
747 ...context
748 };
749 this.extendContext(ctx);
750 /**
751 * Decodes the provided value.
752 * @returns {ComplexSerializableType} deserialize value
753 */
754 const decodeValue = () => {
755 const item = read();
756
757 if (item === ESCAPE) {
758 const nextItem = read();
759
760 if (nextItem === ESCAPE_ESCAPE_VALUE) {
761 return ESCAPE;
762 } else if (nextItem === ESCAPE_UNDEFINED) {
763 // Nothing
764 } else if (nextItem === ESCAPE_END_OBJECT) {
765 throw new Error(
766 `Unexpected end of object at position ${currentDataPos - 1}`
767 );
768 } else {
769 const request = nextItem;
770 /** @type {undefined | ObjectSerializer} */
771 let serializer;
772
773 if (typeof request === "number") {
774 if (request < 0) {
775 // relative reference
776 return referenceable[currentPos + request];
777 }
778 serializer = objectTypeLookup[currentPosTypeLookup - request];
779 } else {
780 if (typeof request !== "string") {
781 throw new Error(
782 `Unexpected type (${typeof request}) of request ` +
783 `at position ${currentDataPos - 1}`
784 );
785 }
786 const name = /** @type {string} */ (read());
787
788 serializer = ObjectMiddleware._getDeserializerForWithoutError(
789 request,
790 name
791 );
792
793 if (serializer === undefined) {
794 if (request && !loadedRequests.has(request)) {
795 let loaded = false;
796 for (const [regExp, loader] of loaders) {
797 if (regExp.test(request) && loader(request)) {
798 loaded = true;
799 break;
800 }
801 }
802 if (!loaded) {
803 require(request);
804 }
805
806 loadedRequests.add(request);
807 }
808
809 serializer = ObjectMiddleware.getDeserializerFor(request, name);
810 }
811
812 objectTypeLookup.push(serializer);
813 currentPosTypeLookup++;
814 }
815 try {
816 const item = serializer.deserialize(ctx);
817 const end1 = read();
818
819 if (end1 !== ESCAPE) {
820 throw new Error("Expected end of object");
821 }
822
823 const end2 = read();
824
825 if (end2 !== ESCAPE_END_OBJECT) {
826 throw new Error("Expected end of object");
827 }
828
829 addReferenceable(item);
830
831 return item;
832 } catch (err) {
833 // As this is only for error handling, we omit creating a Map for
834 // faster access to this information, as this would affect performance
835 // in the good case
836 /** @type {undefined | [Constructor | null, SerializerConfig]} */
837 let serializerEntry;
838 for (const entry of serializers) {
839 if (entry[1].serializer === serializer) {
840 serializerEntry = entry;
841 break;
842 }
843 }
844 const name = !serializerEntry
845 ? "unknown"
846 : !serializerEntry[1].request
847 ? /** @type {Constructor[]} */ (serializerEntry)[0].name
848 : serializerEntry[1].name
849 ? `${serializerEntry[1].request} ${serializerEntry[1].name}`
850 : serializerEntry[1].request;
851 /** @type {Error} */
852 (err).message += `\n(during deserialization of ${name})`;
853 throw err;
854 }
855 }
856 } else if (typeof item === "string") {
857 if (item.length > 1) {
858 addReferenceable(item);
859 }
860
861 return item;
862 } else if (Buffer.isBuffer(item)) {
863 addReferenceable(item);
864
865 return item;
866 } else if (typeof item === "function") {
867 return SerializerMiddleware.deserializeLazy(
868 item,
869 (data) =>
870 /** @type {[DeserializedType]} */
871 (this.deserialize(data, context))[0]
872 );
873 } else {
874 return item;
875 }
876 };
877
878 try {
879 while (currentDataPos < data.length) {
880 result.push(decodeValue());
881 }
882 return result;
883 } finally {
884 // Get rid of these references to avoid leaking memory
885 // This happens because the optimized code v8 generates
886 // is optimized for our "ctx.read" method so it will reference
887 // it from e. g. Dependency.prototype.deserialize -(IC)-> ctx.read
888 result =
889 referenceable =
890 data =
891 objectTypeLookup =
892 ctx =
893 /** @type {EXPECTED_ANY} */
894 (undefined);
895 }
896 }
897}
898
899module.exports = ObjectMiddleware;
900module.exports.NOT_SERIALIZABLE = NOT_SERIALIZABLE;
Note: See TracBrowser for help on using the repository browser.