feat: refactoring project

This commit is contained in:
Carlos
2024-11-23 14:56:07 -05:00
parent f0c2a50c18
commit 1c6db5818d
2351 changed files with 39323 additions and 60326 deletions

View File

@@ -135,6 +135,8 @@ const identifyBigInt = n => {
return 2;
};
/** @typedef {TODO} Context */
/**
* @typedef {PrimitiveSerializableType[]} DeserializedType
* @typedef {BufferSerializableType[]} SerializedType
@@ -150,6 +152,11 @@ class BinaryMiddleware extends SerializerMiddleware {
return this._serialize(data, context);
}
/**
* @param {function(): Promise<any> | any} fn lazy function
* @param {TODO} context serialize function
* @returns {function(): Promise<any> | any} new lazy
*/
_serializeLazy(fn, context) {
return SerializerMiddleware.serializeLazy(fn, data =>
this._serialize(data, context)
@@ -158,7 +165,7 @@ class BinaryMiddleware extends SerializerMiddleware {
/**
* @param {DeserializedType} data data
* @param {object} context context object
* @param {TODO} context context object
* @param {{ leftOverBuffer: Buffer | null, allocationSize: number, increaseCounter: number }} allocationScope allocation scope
* @returns {SerializedType} serialized data
*/
@@ -171,17 +178,20 @@ class BinaryMiddleware extends SerializerMiddleware {
leftOverBuffer: null
}
) {
/** @type {Buffer} */
/** @type {Buffer | null} */
let leftOverBuffer = null;
/** @type {BufferSerializableType[]} */
let buffers = [];
/** @type {Buffer} */
/** @type {Buffer | null} */
let currentBuffer = allocationScope ? allocationScope.leftOverBuffer : null;
allocationScope.leftOverBuffer = null;
let currentPosition = 0;
if (currentBuffer === null) {
currentBuffer = Buffer.allocUnsafe(allocationScope.allocationSize);
}
/**
* @param {number} bytesNeeded bytes needed
*/
const allocate = bytesNeeded => {
if (currentBuffer !== null) {
if (currentBuffer.length - currentPosition >= bytesNeeded) return;
@@ -233,13 +243,15 @@ class BinaryMiddleware extends SerializerMiddleware {
* @param {number} byte byte
*/
const writeU8 = byte => {
currentBuffer.writeUInt8(byte, currentPosition++);
/** @type {Buffer} */
(currentBuffer).writeUInt8(byte, currentPosition++);
};
/**
* @param {number} ui32 ui32
*/
const writeU32 = ui32 => {
currentBuffer.writeUInt32LE(ui32, currentPosition);
/** @type {Buffer} */
(currentBuffer).writeUInt32LE(ui32, currentPosition);
currentPosition += 4;
};
/** @type {number[]} */
@@ -251,8 +263,8 @@ class BinaryMiddleware extends SerializerMiddleware {
* @returns {number} size
*/
const measureEnd = () => {
const oldPos = measureStack.pop();
const buffersIndex = measureStack.pop();
const oldPos = /** @type {number} */ (measureStack.pop());
const buffersIndex = /** @type {number} */ (measureStack.pop());
let size = currentPosition - oldPos;
for (let i = buffersIndex; i < buffers.length; i++) {
size += buffers[i].length;
@@ -264,7 +276,7 @@ class BinaryMiddleware extends SerializerMiddleware {
switch (typeof thing) {
case "function": {
if (!SerializerMiddleware.isLazy(thing))
throw new Error("Unexpected function " + thing);
throw new Error(`Unexpected function ${thing}`);
/** @type {SerializedType | (() => SerializedType)} */
let serializedData =
SerializerMiddleware.getLazySerializedValue(thing);
@@ -287,12 +299,10 @@ class BinaryMiddleware extends SerializerMiddleware {
buffers.push(serializedData);
break;
}
} else {
if (typeof serializedData === "function") {
flush();
buffers.push(serializedData);
break;
}
} else if (typeof serializedData === "function") {
flush();
buffers.push(serializedData);
break;
}
/** @type {number[]} */
const lengths = [];
@@ -612,6 +622,11 @@ class BinaryMiddleware extends SerializerMiddleware {
}
break;
}
default: {
throw new Error(
`Unknown typeof "${typeof thing}" in binary middleware`
);
}
}
}
flush();
@@ -621,9 +636,9 @@ class BinaryMiddleware extends SerializerMiddleware {
// avoid leaking memory
currentBuffer = null;
leftOverBuffer = null;
allocationScope = undefined;
allocationScope = /** @type {EXPECTED_ANY} */ (undefined);
const _buffers = buffers;
buffers = undefined;
buffers = /** @type {EXPECTED_ANY} */ (undefined);
return _buffers;
}
@@ -653,19 +668,21 @@ class BinaryMiddleware extends SerializerMiddleware {
/**
* @param {SerializedType} data data
* @param {object} context context object
* @param {TODO} context context object
* @returns {DeserializedType} deserialized data
*/
_deserialize(data, context) {
let currentDataItem = 0;
/** @type {BufferSerializableType | null} */
let currentBuffer = data[0];
let currentIsBuffer = Buffer.isBuffer(currentBuffer);
let currentPosition = 0;
/** @type {(x: Buffer) => Buffer} */
const retainedBuffer = context.retainedBuffer || (x => x);
const checkOverflow = () => {
if (currentPosition >= currentBuffer.length) {
if (currentPosition >= /** @type {Buffer} */ (currentBuffer).length) {
currentPosition = 0;
currentDataItem++;
currentBuffer =
@@ -673,9 +690,13 @@ class BinaryMiddleware extends SerializerMiddleware {
currentIsBuffer = Buffer.isBuffer(currentBuffer);
}
};
const isInCurrentBuffer = n => {
return currentIsBuffer && n + currentPosition <= currentBuffer.length;
};
/**
* @param {number} n n
* @returns {boolean} true when in current buffer, otherwise false
*/
const isInCurrentBuffer = n =>
currentIsBuffer &&
n + currentPosition <= /** @type {Buffer} */ (currentBuffer).length;
const ensureBuffer = () => {
if (!currentIsBuffer) {
throw new Error(
@@ -692,12 +713,13 @@ class BinaryMiddleware extends SerializerMiddleware {
*/
const read = n => {
ensureBuffer();
const rem = currentBuffer.length - currentPosition;
const rem =
/** @type {Buffer} */ (currentBuffer).length - currentPosition;
if (rem < n) {
const buffers = [read(rem)];
n -= rem;
ensureBuffer();
while (currentBuffer.length < n) {
while (/** @type {Buffer} */ (currentBuffer).length < n) {
const b = /** @type {Buffer} */ (currentBuffer);
buffers.push(b);
n -= b.length;
@@ -723,7 +745,9 @@ class BinaryMiddleware extends SerializerMiddleware {
*/
const readUpTo = n => {
ensureBuffer();
const rem = currentBuffer.length - currentPosition;
const rem =
/** @type {Buffer} */
(currentBuffer).length - currentPosition;
if (rem < n) {
n = rem;
}
@@ -742,9 +766,9 @@ class BinaryMiddleware extends SerializerMiddleware {
* There is no need to check remaining buffer size here
* since {@link checkOverflow} guarantees at least one byte remaining
*/
const byte = /** @type {Buffer} */ (currentBuffer).readUInt8(
currentPosition
);
const byte =
/** @type {Buffer} */
(currentBuffer).readUInt8(currentPosition);
currentPosition += I8_SIZE;
checkOverflow();
return byte;
@@ -752,9 +776,11 @@ class BinaryMiddleware extends SerializerMiddleware {
/**
* @returns {number} U32
*/
const readU32 = () => {
return read(I32_SIZE).readUInt32LE(0);
};
const readU32 = () => read(I32_SIZE).readUInt32LE(0);
/**
* @param {number} data data
* @param {number} n n
*/
const readBits = (data, n) => {
let mask = 1;
while (n !== 0) {
@@ -883,7 +909,8 @@ class BinaryMiddleware extends SerializerMiddleware {
const len = readU32();
if (isInCurrentBuffer(len) && currentPosition + len < 0x7fffffff) {
result.push(
currentBuffer.toString(
/** @type {Buffer} */
(currentBuffer).toString(
undefined,
currentPosition,
currentPosition + len
@@ -901,7 +928,8 @@ class BinaryMiddleware extends SerializerMiddleware {
return () => {
if (currentIsBuffer && currentPosition < 0x7ffffffe) {
result.push(
currentBuffer.toString(
/** @type {Buffer} */
(currentBuffer).toString(
"latin1",
currentPosition,
currentPosition + 1
@@ -974,11 +1002,13 @@ class BinaryMiddleware extends SerializerMiddleware {
return () => {
const len = readU32();
if (isInCurrentBuffer(len) && currentPosition + len < 0x7fffffff) {
const value = currentBuffer.toString(
undefined,
currentPosition,
currentPosition + len
);
const value =
/** @type {Buffer} */
(currentBuffer).toString(
undefined,
currentPosition,
currentPosition + len
);
result.push(BigInt(value));
currentPosition += len;
@@ -1000,7 +1030,8 @@ class BinaryMiddleware extends SerializerMiddleware {
currentPosition + len < 0x7fffffff
) {
result.push(
currentBuffer.toString(
/** @type {Buffer} */
(currentBuffer).toString(
"latin1",
currentPosition,
currentPosition + len
@@ -1075,13 +1106,10 @@ class BinaryMiddleware extends SerializerMiddleware {
}
}
};
} else {
return () => {
throw new Error(
`Unexpected header byte 0x${header.toString(16)}`
);
};
}
return () => {
throw new Error(`Unexpected header byte 0x${header.toString(16)}`);
};
}
});
@@ -1101,8 +1129,9 @@ class BinaryMiddleware extends SerializerMiddleware {
}
// avoid leaking memory in context
// eslint-disable-next-line prefer-const
let _result = result;
result = undefined;
result = /** @type {EXPECTED_ANY} */ (undefined);
return _result;
}
}

View File

@@ -15,6 +15,7 @@ class DateObjectSerializer {
serialize(obj, context) {
context.write(obj.getTime());
}
/**
* @param {ObjectDeserializerContext} context context
* @returns {Date} date

View File

@@ -14,6 +14,7 @@ class ErrorObjectSerializer {
constructor(Type) {
this.Type = Type;
}
/**
* @param {Error | EvalError | RangeError | ReferenceError | SyntaxError | TypeError} obj error
* @param {ObjectSerializerContext} context context
@@ -23,6 +24,7 @@ class ErrorObjectSerializer {
context.write(obj.stack);
context.write(/** @type {Error & { cause: "unknown" }} */ (obj).cause);
}
/**
* @param {ObjectDeserializerContext} context context
* @returns {Error | EvalError | RangeError | ReferenceError | SyntaxError | TypeError} error

View File

@@ -19,6 +19,7 @@ const memoize = require("../util/memoize");
const SerializerMiddleware = require("./SerializerMiddleware");
/** @typedef {typeof import("../util/Hash")} Hash */
/** @typedef {import("../util/fs").IStats} IStats */
/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */
/** @typedef {import("./types").BufferSerializableType} BufferSerializableType */
@@ -57,6 +58,7 @@ const hashForName = (buffers, hashFunction) => {
const COMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024;
const DECOMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024;
/** @type {function(Buffer, number, number): void} */
const writeUInt64LE = Buffer.prototype.writeBigUInt64LE
? (buf, value, offset) => {
buf.writeBigUInt64LE(BigInt(value), offset);
@@ -68,10 +70,9 @@ const writeUInt64LE = Buffer.prototype.writeBigUInt64LE
buf.writeUInt32LE(high, offset + 4);
};
/** @type {function(Buffer, number): void} */
const readUInt64LE = Buffer.prototype.readBigUInt64LE
? (buf, offset) => {
return Number(buf.readBigUInt64LE(offset));
}
? (buf, offset) => Number(buf.readBigUInt64LE(offset))
: (buf, offset) => {
const low = buf.readUInt32LE(offset);
const high = buf.readUInt32LE(offset + 4);
@@ -82,7 +83,7 @@ const readUInt64LE = Buffer.prototype.readBigUInt64LE
* @typedef {object} SerializeResult
* @property {string | false} name
* @property {number} size
* @property {Promise=} backgroundJob
* @property {Promise<any>=} backgroundJob
*/
/**
@@ -104,8 +105,8 @@ const serialize = async (
const processedData = [];
/** @type {WeakMap<SerializeResult, function(): any | Promise<any>>} */
const resultToLazy = new WeakMap();
/** @type {Buffer[]} */
let lastBuffers = undefined;
/** @type {Buffer[] | undefined} */
let lastBuffers;
for (const item of await data) {
if (typeof item === "function") {
if (!SerializerMiddleware.isLazy(item))
@@ -163,9 +164,8 @@ const serialize = async (
const backgroundJobs = [];
const resolvedData = (
await Promise.all(
/** @type {Promise<Buffer[] | Buffer | SerializeResult>[]} */ (
processedData
)
/** @type {Promise<Buffer[] | Buffer | SerializeResult>[]} */
(processedData)
)
).map(item => {
if (Array.isArray(item) || Buffer.isBuffer(item)) return item;
@@ -195,7 +195,7 @@ const serialize = async (
} else if (item) {
lengths.push(-item.length);
} else {
throw new Error("Unexpected falsy value in resolved data " + item);
throw new Error(`Unexpected falsy value in resolved data ${item}`);
}
}
const header = Buffer.allocUnsafe(8 + lengths.length * 4);
@@ -237,12 +237,12 @@ const serialize = async (
*/
const deserialize = async (middleware, name, readFile) => {
const contents = await readFile(name);
if (contents.length === 0) throw new Error("Empty file " + name);
if (contents.length === 0) throw new Error(`Empty file ${name}`);
let contentsIndex = 0;
let contentItem = contents[0];
let contentItemLength = contentItem.length;
let contentPosition = 0;
if (contentItemLength === 0) throw new Error("Empty file " + name);
if (contentItemLength === 0) throw new Error(`Empty file ${name}`);
const nextContent = () => {
contentsIndex++;
contentItem = contents[contentsIndex];
@@ -378,18 +378,16 @@ const deserialize = async (middleware, name, readFile) => {
length -= l;
contentPosition = contentItemLength;
}
} else if (length >= contentItemLength) {
result.push(contentItem);
length -= contentItemLength;
contentPosition = contentItemLength;
} else {
if (length >= contentItemLength) {
result.push(contentItem);
length -= contentItemLength;
contentPosition = contentItemLength;
} else {
result.push(
Buffer.from(contentItem.buffer, contentItem.byteOffset, length)
);
contentPosition += length;
length = 0;
}
result.push(
Buffer.from(contentItem.buffer, contentItem.byteOffset, length)
);
contentPosition += length;
length = 0;
}
while (length > 0) {
nextContent();
@@ -410,6 +408,8 @@ const deserialize = async (middleware, name, readFile) => {
return result;
};
/** @typedef {{ filename: string, extension?: string }} FileMiddlewareContext */
/**
* @typedef {BufferSerializableType[]} DeserializedType
* @typedef {true} SerializedType
@@ -425,6 +425,7 @@ class FileMiddleware extends SerializerMiddleware {
this.fs = fs;
this._hashFunction = hashFunction;
}
/**
* @param {DeserializedType} data data
* @param {object} context context object
@@ -439,76 +440,92 @@ class FileMiddleware extends SerializerMiddleware {
// It's important that we don't touch existing files during serialization
// because serialize may read existing files (when deserializing)
const allWrittenFiles = new Set();
/**
* @param {string | false} name name
* @param {Buffer[]} content content
* @param {number} size size
* @returns {Promise<void>}
*/
const writeFile = async (name, content, size) => {
const file = name
? join(this.fs, filename, `../${name}${extension}`)
: filename;
await new Promise((resolve, reject) => {
let stream = this.fs.createWriteStream(file + "_");
let compression;
if (file.endsWith(".gz")) {
compression = createGzip({
chunkSize: COMPRESSION_CHUNK_SIZE,
level: zConstants.Z_BEST_SPEED
});
} else if (file.endsWith(".br")) {
compression = createBrotliCompress({
chunkSize: COMPRESSION_CHUNK_SIZE,
params: {
[zConstants.BROTLI_PARAM_MODE]: zConstants.BROTLI_MODE_TEXT,
[zConstants.BROTLI_PARAM_QUALITY]: 2,
[zConstants.BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING]: true,
[zConstants.BROTLI_PARAM_SIZE_HINT]: size
}
});
}
if (compression) {
pipeline(compression, stream, reject);
stream = compression;
stream.on("finish", () => resolve());
} else {
stream.on("error", err => reject(err));
stream.on("finish", () => resolve());
}
// split into chunks for WRITE_LIMIT_CHUNK size
const chunks = [];
for (const b of content) {
if (b.length < WRITE_LIMIT_CHUNK) {
chunks.push(b);
await new Promise(
/**
* @param {(value?: undefined) => void} resolve resolve
* @param {(reason?: Error | null) => void} reject reject
*/
(resolve, reject) => {
let stream = this.fs.createWriteStream(`${file}_`);
let compression;
if (file.endsWith(".gz")) {
compression = createGzip({
chunkSize: COMPRESSION_CHUNK_SIZE,
level: zConstants.Z_BEST_SPEED
});
} else if (file.endsWith(".br")) {
compression = createBrotliCompress({
chunkSize: COMPRESSION_CHUNK_SIZE,
params: {
[zConstants.BROTLI_PARAM_MODE]: zConstants.BROTLI_MODE_TEXT,
[zConstants.BROTLI_PARAM_QUALITY]: 2,
[zConstants.BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING]: true,
[zConstants.BROTLI_PARAM_SIZE_HINT]: size
}
});
}
if (compression) {
pipeline(compression, stream, reject);
stream = compression;
stream.on("finish", () => resolve());
} else {
for (let i = 0; i < b.length; i += WRITE_LIMIT_CHUNK) {
chunks.push(b.slice(i, i + WRITE_LIMIT_CHUNK));
stream.on("error", err => reject(err));
stream.on("finish", () => resolve());
}
// split into chunks for WRITE_LIMIT_CHUNK size
/** @type {TODO[]} */
const chunks = [];
for (const b of content) {
if (b.length < WRITE_LIMIT_CHUNK) {
chunks.push(b);
} else {
for (let i = 0; i < b.length; i += WRITE_LIMIT_CHUNK) {
chunks.push(b.slice(i, i + WRITE_LIMIT_CHUNK));
}
}
}
const len = chunks.length;
let i = 0;
/**
* @param {(Error | null)=} err err
*/
const batchWrite = err => {
// will be handled in "on" error handler
if (err) return;
if (i === len) {
stream.end();
return;
}
// queue up a batch of chunks up to the write limit
// end is exclusive
let end = i;
let sum = chunks[end++].length;
while (end < len) {
sum += chunks[end].length;
if (sum > WRITE_LIMIT_TOTAL) break;
end++;
}
while (i < end - 1) {
stream.write(chunks[i++]);
}
stream.write(chunks[i++], batchWrite);
};
batchWrite();
}
const len = chunks.length;
let i = 0;
const batchWrite = err => {
// will be handled in "on" error handler
if (err) return;
if (i === len) {
stream.end();
return;
}
// queue up a batch of chunks up to the write limit
// end is exclusive
let end = i;
let sum = chunks[end++].length;
while (end < len) {
sum += chunks[end].length;
if (sum > WRITE_LIMIT_TOTAL) break;
end++;
}
while (i < end - 1) {
stream.write(chunks[i++]);
}
stream.write(chunks[i++], batchWrite);
};
batchWrite();
});
);
if (name) allWrittenFiles.add(file);
};
@@ -518,10 +535,15 @@ class FileMiddleware extends SerializerMiddleware {
await backgroundJob;
// Rename the index file to disallow access during inconsistent file state
await new Promise(resolve =>
this.fs.rename(filename, filename + ".old", err => {
resolve();
})
await new Promise(
/**
* @param {(value?: undefined) => void} resolve resolve
*/
resolve => {
this.fs.rename(filename, `${filename}.old`, err => {
resolve();
});
}
);
// update all written files
@@ -529,22 +551,35 @@ class FileMiddleware extends SerializerMiddleware {
Array.from(
allWrittenFiles,
file =>
new Promise((resolve, reject) => {
this.fs.rename(file + "_", file, err => {
if (err) return reject(err);
resolve();
});
})
new Promise(
/**
* @param {(value?: undefined) => void} resolve resolve
* @param {(reason?: Error | null) => void} reject reject
* @returns {void}
*/
(resolve, reject) => {
this.fs.rename(`${file}_`, file, err => {
if (err) return reject(err);
resolve();
});
}
)
)
);
// As final step automatically update the index file to have a consistent pack again
await new Promise(resolve => {
this.fs.rename(filename + "_", filename, err => {
if (err) return reject(err);
resolve();
});
});
await new Promise(
/**
* @param {(value?: undefined) => void} resolve resolve
* @returns {void}
*/
resolve => {
this.fs.rename(`${filename}_`, filename, err => {
if (err) return reject(err);
resolve();
});
}
);
return /** @type {true} */ (true);
}
)
@@ -560,6 +595,10 @@ class FileMiddleware extends SerializerMiddleware {
*/
deserialize(data, context) {
const { filename, extension = "" } = context;
/**
* @param {string | boolean} name name
* @returns {Promise<TODO>} result
*/
const readFile = name =>
new Promise((resolve, reject) => {
const file = name
@@ -570,11 +609,12 @@ class FileMiddleware extends SerializerMiddleware {
reject(err);
return;
}
let remaining = /** @type {number} */ (stats.size);
let remaining = /** @type {IStats} */ (stats).size;
/** @type {Buffer | undefined} */
let currentBuffer;
/** @type {number | undefined} */
let currentBufferUsed;
/** @type {any[]} */
const buf = [];
/** @type {import("zlib").Zlib & import("stream").Transform | undefined} */
let decompression;
@@ -588,7 +628,8 @@ class FileMiddleware extends SerializerMiddleware {
});
}
if (decompression) {
let newResolve, newReject;
let newResolve;
let newReject;
resolve(
Promise.all([
new Promise((rs, rj) => {
@@ -605,11 +646,12 @@ class FileMiddleware extends SerializerMiddleware {
resolve = newResolve;
reject = newReject;
}
this.fs.open(file, "r", (err, fd) => {
this.fs.open(file, "r", (err, _fd) => {
if (err) {
reject(err);
return;
}
const fd = /** @type {number} */ (_fd);
const read = () => {
if (currentBuffer === undefined) {
currentBuffer = Buffer.allocUnsafeSlow(
@@ -622,8 +664,10 @@ class FileMiddleware extends SerializerMiddleware {
currentBufferUsed = 0;
}
let readBuffer = currentBuffer;
let readOffset = currentBufferUsed;
let readLength = currentBuffer.length - currentBufferUsed;
let readOffset = /** @type {number} */ (currentBufferUsed);
let readLength =
currentBuffer.length -
/** @type {number} */ (currentBufferUsed);
// values passed to fs.read must be valid int32 values
if (readOffset > 0x7fffffff) {
readBuffer = currentBuffer.slice(readOffset);
@@ -645,9 +689,13 @@ class FileMiddleware extends SerializerMiddleware {
});
return;
}
currentBufferUsed += bytesRead;
/** @type {number} */
(currentBufferUsed) += bytesRead;
remaining -= bytesRead;
if (currentBufferUsed === currentBuffer.length) {
if (
currentBufferUsed ===
/** @type {Buffer} */ (currentBuffer).length
) {
if (decompression) {
decompression.write(currentBuffer);
} else {

View File

@@ -22,6 +22,7 @@ class MapObjectSerializer {
context.write(value);
}
}
/**
* @template K, V
* @param {ObjectDeserializerContext} context context
@@ -29,7 +30,7 @@ class MapObjectSerializer {
*/
deserialize(context) {
/** @type {number} */
let size = context.read();
const size = context.read();
/** @type {Map<K, V>} */
const map = new Map();
/** @type {K[]} */

View File

@@ -21,9 +21,10 @@ class NullPrototypeObjectSerializer {
}
context.write(null);
for (const key of keys) {
context.write(obj[key]);
context.write(obj[/** @type {keyof T} */ (key)]);
}
}
/**
* @template {object} T
* @param {ObjectDeserializerContext} context context
@@ -41,7 +42,7 @@ class NullPrototypeObjectSerializer {
key = context.read();
}
for (const key of keys) {
obj[key] = context.read();
obj[/** @type {keyof T} */ (key)] = context.read();
}
return obj;
}

View File

@@ -137,8 +137,9 @@ jsTypes.set(TypeError, new ErrorObjectSerializer(TypeError));
// If in a sandboxed environment (e. g. jest), this escapes the sandbox and registers
// real Object and Array types to. These types may occur in the wild too, e. g. when
// using Structured Clone in postMessage.
// eslint-disable-next-line n/exports-style
if (exports.constructor !== Object) {
// eslint-disable-next-line jsdoc/check-types
// eslint-disable-next-line jsdoc/check-types, n/exports-style
const Obj = /** @type {typeof Object} */ (exports.constructor);
const Fn = /** @type {typeof Function} */ (Obj.constructor);
for (const [type, config] of Array.from(jsTypes)) {
@@ -185,6 +186,7 @@ class ObjectMiddleware extends SerializerMiddleware {
this.extendContext = extendContext;
this._hashFunction = hashFunction;
}
/**
* @param {RegExp} regExp RegExp for which the request is tested
* @param {function(string): boolean} loader loader to load the request, returns true when successful
@@ -202,7 +204,7 @@ class ObjectMiddleware extends SerializerMiddleware {
* @returns {void}
*/
static register(Constructor, request, name, serializer) {
const key = request + "/" + name;
const key = `${request}/${name}`;
if (serializers.has(Constructor)) {
throw new Error(
@@ -267,7 +269,7 @@ class ObjectMiddleware extends SerializerMiddleware {
* @returns {ObjectSerializer} serializer
*/
static getDeserializerFor(request, name) {
const key = request + "/" + name;
const key = `${request}/${name}`;
const serializer = serializerInversed.get(key);
if (serializer === undefined) {
@@ -279,11 +281,11 @@ class ObjectMiddleware extends SerializerMiddleware {
/**
* @param {string} request request
* @param {TODO} name name
* @returns {ObjectSerializer} serializer
* @param {string} name name
* @returns {ObjectSerializer | undefined} serializer
*/
static _getDeserializerForWithoutError(request, name) {
const key = request + "/" + name;
const key = `${request}/${name}`;
const serializer = serializerInversed.get(key);
return serializer;
}
@@ -302,6 +304,10 @@ class ObjectMiddleware extends SerializerMiddleware {
referenceable.set(item, currentPos++);
};
let bufferDedupeMap = new Map();
/**
* @param {Buffer} buf buffer
* @returns {Buffer} deduped buffer
*/
const dedupeBuffer = buf => {
const len = buf.length;
const entry = bufferDedupeMap.get(len);
@@ -316,17 +322,16 @@ class ObjectMiddleware extends SerializerMiddleware {
}
bufferDedupeMap.set(len, [entry, buf]);
return buf;
} else {
const hash = toHash(entry, this._hashFunction);
const newMap = new Map();
newMap.set(hash, entry);
bufferDedupeMap.set(len, newMap);
const hashBuf = toHash(buf, this._hashFunction);
if (hash === hashBuf) {
return entry;
}
return buf;
}
const hash = toHash(entry, this._hashFunction);
const newMap = new Map();
newMap.set(hash, entry);
bufferDedupeMap.set(len, newMap);
const hashBuf = toHash(buf, this._hashFunction);
if (hash === hashBuf) {
return entry;
}
return buf;
} else if (Array.isArray(entry)) {
if (entry.length < 16) {
for (const item of entry) {
@@ -336,32 +341,29 @@ class ObjectMiddleware extends SerializerMiddleware {
}
entry.push(buf);
return buf;
} else {
const newMap = new Map();
const hash = toHash(buf, this._hashFunction);
let found;
for (const item of entry) {
const itemHash = toHash(item, this._hashFunction);
newMap.set(itemHash, item);
if (found === undefined && itemHash === hash) found = item;
}
bufferDedupeMap.set(len, newMap);
if (found === undefined) {
newMap.set(hash, buf);
return buf;
} else {
return found;
}
}
} else {
const newMap = new Map();
const hash = toHash(buf, this._hashFunction);
const item = entry.get(hash);
if (item !== undefined) {
return item;
let found;
for (const item of entry) {
const itemHash = toHash(item, this._hashFunction);
newMap.set(itemHash, item);
if (found === undefined && itemHash === hash) found = item;
}
entry.set(hash, buf);
return buf;
bufferDedupeMap.set(len, newMap);
if (found === undefined) {
newMap.set(hash, buf);
return buf;
}
return found;
}
const hash = toHash(buf, this._hashFunction);
const item = entry.get(hash);
if (item !== undefined) {
return item;
}
entry.set(hash, buf);
return buf;
};
let currentPosTypeLookup = 0;
let objectTypeLookup = new Map();
@@ -385,7 +387,7 @@ class ObjectMiddleware extends SerializerMiddleware {
if (request) {
return `${request}${name ? `.${name}` : ""}`;
}
} catch (e) {
} catch (_err) {
// ignore -> fallback
}
if (typeof item === "object" && item !== null) {
@@ -408,27 +410,29 @@ class ObjectMiddleware extends SerializerMiddleware {
}
try {
return `${item}`;
} catch (e) {
return `(${e.message})`;
} catch (err) {
return `(${err.message})`;
}
})
.join(" -> ");
};
/** @type {WeakSet<Error>} */
let hasDebugInfoAttached;
let ctx = {
write(value, key) {
try {
process(value);
} catch (e) {
if (e !== NOT_SERIALIZABLE) {
} catch (err) {
if (err !== NOT_SERIALIZABLE) {
if (hasDebugInfoAttached === undefined)
hasDebugInfoAttached = new WeakSet();
if (!hasDebugInfoAttached.has(e)) {
e.message += `\nwhile serializing ${stackToString(value)}`;
hasDebugInfoAttached.add(e);
if (!hasDebugInfoAttached.has(/** @type {Error} */ (err))) {
/** @type {Error} */
(err).message += `\nwhile serializing ${stackToString(value)}`;
hasDebugInfoAttached.add(/** @type {Error} */ (err));
}
}
throw e;
throw err;
}
},
setCircularReference(ref) {
@@ -491,7 +495,7 @@ class ObjectMiddleware extends SerializerMiddleware {
if (cycleStack.has(item)) {
throw new Error(
`This is a circular references. To serialize circular references use 'setCircularReference' somewhere in the circle during serialize and deserialize.`
"This is a circular references. To serialize circular references use 'setCircularReference' somewhere in the circle during serialize and deserialize."
);
}
@@ -542,7 +546,7 @@ class ObjectMiddleware extends SerializerMiddleware {
result.push(item);
} else if (typeof item === "function") {
if (!SerializerMiddleware.isLazy(item))
throw new Error("Unexpected function " + item);
throw new Error(`Unexpected function ${item}`);
/** @type {SerializedType} */
const serializedData =
SerializerMiddleware.getLazySerializedValue(item);
@@ -573,10 +577,10 @@ class ObjectMiddleware extends SerializerMiddleware {
process(item);
}
return result;
} catch (e) {
if (e === NOT_SERIALIZABLE) return null;
} catch (err) {
if (err === NOT_SERIALIZABLE) return null;
throw e;
throw err;
} finally {
// Get rid of these references to avoid leaking memory
// This happens because the optimized code v8 generates
@@ -588,7 +592,8 @@ class ObjectMiddleware extends SerializerMiddleware {
bufferDedupeMap =
objectTypeLookup =
ctx =
undefined;
/** @type {EXPECTED_ANY} */
(undefined);
}
}
@@ -637,7 +642,7 @@ class ObjectMiddleware extends SerializerMiddleware {
if (nextItem === ESCAPE_ESCAPE_VALUE) {
return ESCAPE;
} else if (nextItem === ESCAPE_UNDEFINED) {
return undefined;
// Nothing
} else if (nextItem === ESCAPE_END_OBJECT) {
throw new Error(
`Unexpected end of object at position ${currentDataPos - 1}`
@@ -659,7 +664,7 @@ class ObjectMiddleware extends SerializerMiddleware {
`at position ${currentDataPos - 1}`
);
}
const name = read();
const name = /** @type {string} */ (read());
serializer = ObjectMiddleware._getDeserializerForWithoutError(
request,
@@ -670,11 +675,9 @@ class ObjectMiddleware extends SerializerMiddleware {
if (request && !loadedRequests.has(request)) {
let loaded = false;
for (const [regExp, loader] of loaders) {
if (regExp.test(request)) {
if (loader(request)) {
loaded = true;
break;
}
if (regExp.test(request) && loader(request)) {
loaded = true;
break;
}
}
if (!loaded) {
@@ -725,7 +728,8 @@ class ObjectMiddleware extends SerializerMiddleware {
: serializerEntry[1].name
? `${serializerEntry[1].request} ${serializerEntry[1].name}`
: serializerEntry[1].request;
err.message += `\n(during deserialization of ${name})`;
/** @type {Error} */
(err).message += `\n(during deserialization of ${name})`;
throw err;
}
}
@@ -759,7 +763,13 @@ class ObjectMiddleware extends SerializerMiddleware {
// This happens because the optimized code v8 generates
// is optimized for our "ctx.read" method so it will reference
// it from e. g. Dependency.prototype.deserialize -(IC)-> ctx.read
result = referenceable = data = objectTypeLookup = ctx = undefined;
result =
referenceable =
data =
objectTypeLookup =
ctx =
/** @type {EXPECTED_ANY} */
(undefined);
}
}
}

View File

@@ -7,19 +7,36 @@
/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
/** @typedef {(arg0?: any) => void} CacheAssoc */
/**
* @template T
* @typedef {WeakMap<CacheAssoc, ObjectStructure<T>>}
*/
const cache = new WeakMap();
/**
* @template T
*/
class ObjectStructure {
constructor() {
this.keys = undefined;
this.children = undefined;
}
/**
* @param {keyof T[]} keys keys
* @returns {keyof T[]} keys
*/
getKeys(keys) {
if (this.keys === undefined) this.keys = keys;
return this.keys;
}
/**
* @param {keyof T} key key
* @returns {ObjectStructure<T>} object structure
*/
key(key) {
if (this.children === undefined) this.children = new Map();
const child = this.children.get(key);
@@ -30,6 +47,12 @@ class ObjectStructure {
}
}
/**
* @template T
* @param {(keyof T)[]} keys keys
* @param {CacheAssoc} cacheAssoc cache assoc fn
* @returns {(keyof T)[]} keys
*/
const getCachedKeys = (keys, cacheAssoc) => {
let root = cache.get(cacheAssoc);
if (root === undefined) {
@@ -45,11 +68,12 @@ const getCachedKeys = (keys, cacheAssoc) => {
class PlainObjectSerializer {
/**
* @param {object} obj plain object
* @template {object} T
* @param {T} obj plain object
* @param {ObjectSerializerContext} context context
*/
serialize(obj, context) {
const keys = Object.keys(obj);
const keys = /** @type {(keyof T)[]} */ (Object.keys(obj));
if (keys.length > 128) {
// Objects with so many keys are unlikely to share structure
// with other objects
@@ -70,19 +94,21 @@ class PlainObjectSerializer {
context.write(null);
}
}
/**
* @template {object} T
* @param {ObjectDeserializerContext} context context
* @returns {object} plain object
* @returns {T} plain object
*/
deserialize(context) {
const keys = context.read();
const obj = {};
const obj = /** @type {T} */ ({});
if (Array.isArray(keys)) {
for (const key of keys) {
obj[key] = context.read();
obj[/** @type {keyof T} */ (key)] = context.read();
}
} else if (keys !== null) {
obj[keys] = context.read();
obj[/** @type {keyof T} */ (keys)] = context.read();
}
return obj;
}

View File

@@ -16,6 +16,7 @@ class RegExpObjectSerializer {
context.write(obj.source);
context.write(obj.flags);
}
/**
* @param {ObjectDeserializerContext} context context
* @returns {RegExp} regexp

View File

@@ -52,11 +52,10 @@ class Serializer {
/** @type {any} */
let current = value;
for (const middleware of this.deserializeMiddlewares) {
if (current && typeof current.then === "function") {
current = current.then(data => middleware.deserialize(data, ctx));
} else {
current = middleware.deserialize(current, ctx);
}
current =
current && typeof current.then === "function"
? current.then(data => middleware.deserialize(data, ctx))
: middleware.deserialize(current, ctx);
}
return current;
}

View File

@@ -45,7 +45,7 @@ class SerializerMiddleware {
* @param {any=} serializedValue serialized value
* @returns {function(): Promise<any> | any} lazy function
*/
static createLazy(value, target, options = {}, serializedValue) {
static createLazy(value, target, options = {}, serializedValue = undefined) {
if (SerializerMiddleware.isLazy(value, target)) return value;
const fn = typeof value === "function" ? value : () => value;
fn[LAZY_TARGET] = target;
@@ -62,24 +62,24 @@ class SerializerMiddleware {
static isLazy(fn, target) {
if (typeof fn !== "function") return false;
const t = fn[LAZY_TARGET];
return target ? t === target : !!t;
return target ? t === target : Boolean(t);
}
/**
* @param {function(): Promise<any> | any} fn lazy function
* @returns {object} options
* @returns {object | undefined} options
*/
static getLazyOptions(fn) {
if (typeof fn !== "function") return undefined;
if (typeof fn !== "function") return;
return /** @type {any} */ (fn).options;
}
/**
* @param {function(): Promise<any> | any} fn lazy function
* @returns {any} serialized value
* @returns {any | undefined} serialized value
*/
static getLazySerializedValue(fn) {
if (typeof fn !== "function") return undefined;
if (typeof fn !== "function") return;
return fn[LAZY_SERIALIZED_VALUE];
}
@@ -112,9 +112,10 @@ class SerializerMiddleware {
}
/**
* @template T
* @param {function(): Promise<any> | any} lazy lazy function
* @param {function(any): Promise<any> | any} deserialize deserialize function
* @returns {function(): Promise<any> | any} new lazy
* @param {function(T): Promise<T> | T} deserialize deserialize function
* @returns {function(): Promise<T> | T} new lazy
*/
static deserializeLazy(lazy, deserialize) {
const fn = memoize(() => {

View File

@@ -19,6 +19,7 @@ class SetObjectSerializer {
context.write(value);
}
}
/**
* @template T
* @param {ObjectDeserializerContext} context context
@@ -26,7 +27,7 @@ class SetObjectSerializer {
*/
deserialize(context) {
/** @type {number} */
let size = context.read();
const size = context.read();
/** @type {Set<T>} */
const set = new Set();
for (let i = 0; i < size; i++) {

View File

@@ -6,8 +6,8 @@
/** @typedef {undefined | null | number | string | boolean | Buffer | object | (() => ComplexSerializableType[] | Promise<ComplexSerializableType[]>)} ComplexSerializableType */
/** @typedef {undefined|null|number|bigint|string|boolean|Buffer|(() => PrimitiveSerializableType[] | Promise<PrimitiveSerializableType[]>)} PrimitiveSerializableType */
/** @typedef {undefined | null | number | bigint | string | boolean | Buffer | (() => PrimitiveSerializableType[] | Promise<PrimitiveSerializableType[]>)} PrimitiveSerializableType */
/** @typedef {Buffer|(() => BufferSerializableType[] | Promise<BufferSerializableType[]>)} BufferSerializableType */
/** @typedef {Buffer | (() => BufferSerializableType[] | Promise<BufferSerializableType[]>)} BufferSerializableType */
module.exports = {};