ObjectMiddleware.js 26 KB

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