ObjectMiddleware.js 28 KB

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