ObjectMiddleware.js 24 KB

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