bson
Version:
A bson parser for node.js and the browser
749 lines (688 loc) • 25.6 kB
text/typescript
import { Binary, validateBinaryVector } from '../binary';
import type { BSONSymbol, DBRef, Document, MaxKey } from '../bson';
import { bsonType } from '../bson_value';
import type { Code } from '../code';
import * as constants from '../constants';
import type { Decimal128 } from '../decimal128';
import type { Double } from '../double';
import { BSONError, BSONVersionError } from '../error';
import type { Int32 } from '../int_32';
import { Long } from '../long';
import type { MinKey } from '../min_key';
import type { ObjectId } from '../objectid';
import type { BSONRegExp } from '../regexp';
import { ByteUtils } from '../utils/byte_utils';
import { NumberUtils } from '../utils/number_utils';
import { isAnyArrayBuffer, isDate, isMap, isRegExp, isUint8Array } from './utils';
/** @public */
export interface SerializeOptions {
/**
* the serializer will check if keys are valid.
* @defaultValue `false`
*/
checkKeys?: boolean;
/**
* serialize the javascript functions
* @defaultValue `false`
*/
serializeFunctions?: boolean;
/**
* serialize will not emit undefined fields
* note that the driver sets this to `false`
* @defaultValue `true`
*/
ignoreUndefined?: boolean;
/** @internal Resize internal buffer */
minInternalBufferSize?: number;
/**
* the index in the buffer where we wish to start serializing into
* @defaultValue `0`
*/
index?: number;
}
const regexp = /\x00/; // eslint-disable-line no-control-regex
const ignoreKeys = new Set(['$db', '$ref', '$id', '$clusterTime']);
function serializeString(buffer: Uint8Array, key: string, value: string, index: number) {
// Encode String type
buffer[index++] = constants.BSON_DATA_STRING;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Write the string
const size = ByteUtils.encodeUTF8Into(buffer, value, index + 4);
// Write the size of the string to buffer
NumberUtils.setInt32LE(buffer, index, size + 1);
// Update index
index = index + 4 + size;
// Write zero
buffer[index++] = 0;
return index;
}
function serializeNumber(buffer: Uint8Array, key: string, value: number, index: number) {
const isNegativeZero = Object.is(value, -0);
const type =
!isNegativeZero &&
Number.isSafeInteger(value) &&
value <= constants.BSON_INT32_MAX &&
value >= constants.BSON_INT32_MIN
? constants.BSON_DATA_INT
: constants.BSON_DATA_NUMBER;
buffer[index++] = type;
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
index = index + numberOfWrittenBytes;
buffer[index++] = 0x00;
if (type === constants.BSON_DATA_INT) {
index += NumberUtils.setInt32LE(buffer, index, value);
} else {
index += NumberUtils.setFloat64LE(buffer, index, value);
}
return index;
}
function serializeBigInt(buffer: Uint8Array, key: string, value: bigint, index: number) {
buffer[index++] = constants.BSON_DATA_LONG;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index += numberOfWrittenBytes;
buffer[index++] = 0;
index += NumberUtils.setBigInt64LE(buffer, index, value);
return index;
}
function serializeNull(buffer: Uint8Array, key: string, _: unknown, index: number) {
// Set long type
buffer[index++] = constants.BSON_DATA_NULL;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
return index;
}
function serializeBoolean(buffer: Uint8Array, key: string, value: boolean, index: number) {
// Write the type
buffer[index++] = constants.BSON_DATA_BOOLEAN;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
// Encode the boolean value
buffer[index++] = value ? 1 : 0;
return index;
}
function serializeDate(buffer: Uint8Array, key: string, value: Date, index: number) {
// Write the type
buffer[index++] = constants.BSON_DATA_DATE;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
// Write the date
const dateInMilis = Long.fromNumber(value.getTime());
const lowBits = dateInMilis.getLowBits();
const highBits = dateInMilis.getHighBits();
// Encode low bits
index += NumberUtils.setInt32LE(buffer, index, lowBits);
// Encode high bits
index += NumberUtils.setInt32LE(buffer, index, highBits);
return index;
}
function serializeRegExp(buffer: Uint8Array, key: string, value: RegExp, index: number) {
// Write the type
buffer[index++] = constants.BSON_DATA_REGEXP;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
if (value.source && value.source.match(regexp) != null) {
throw new BSONError('value ' + value.source + ' must not contain null bytes');
}
// Adjust the index
index = index + ByteUtils.encodeUTF8Into(buffer, value.source, index);
// Write zero
buffer[index++] = 0x00;
// Write the parameters
if (value.ignoreCase) buffer[index++] = 0x69; // i
if (value.global) buffer[index++] = 0x73; // s
if (value.multiline) buffer[index++] = 0x6d; // m
// Add ending zero
buffer[index++] = 0x00;
return index;
}
function serializeBSONRegExp(buffer: Uint8Array, key: string, value: BSONRegExp, index: number) {
// Write the type
buffer[index++] = constants.BSON_DATA_REGEXP;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
// Check the pattern for 0 bytes
if (value.pattern.match(regexp) != null) {
// The BSON spec doesn't allow keys with null bytes because keys are
// null-terminated.
throw new BSONError('pattern ' + value.pattern + ' must not contain null bytes');
}
// Adjust the index
index = index + ByteUtils.encodeUTF8Into(buffer, value.pattern, index);
// Write zero
buffer[index++] = 0x00;
// Write the options
const sortedOptions = value.options.split('').sort().join('');
index = index + ByteUtils.encodeUTF8Into(buffer, sortedOptions, index);
// Add ending zero
buffer[index++] = 0x00;
return index;
}
function serializeMinMax(buffer: Uint8Array, key: string, value: MinKey | MaxKey, index: number) {
// Write the type of either min or max key
if (value === null) {
buffer[index++] = constants.BSON_DATA_NULL;
} else if (value[bsonType] === 'MinKey') {
buffer[index++] = constants.BSON_DATA_MIN_KEY;
} else {
buffer[index++] = constants.BSON_DATA_MAX_KEY;
}
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
return index;
}
function serializeObjectId(buffer: Uint8Array, key: string, value: ObjectId, index: number) {
// Write the type
buffer[index++] = constants.BSON_DATA_OID;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
index += value.serializeInto(buffer, index);
// Adjust index
return index;
}
function serializeBuffer(buffer: Uint8Array, key: string, value: Uint8Array, index: number) {
// Write the type
buffer[index++] = constants.BSON_DATA_BINARY;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
// Get size of the buffer (current write point)
const size = value.length;
// Write the size of the string to buffer
index += NumberUtils.setInt32LE(buffer, index, size);
// Write the default subtype
buffer[index++] = constants.BSON_BINARY_SUBTYPE_DEFAULT;
// Copy the content form the binary field to the buffer
if (size <= 16) {
for (let i = 0; i < size; i++) buffer[index + i] = value[i];
} else {
buffer.set(value, index);
}
// Adjust the index
index = index + size;
return index;
}
function serializeDecimal128(buffer: Uint8Array, key: string, value: Decimal128, index: number) {
buffer[index++] = constants.BSON_DATA_DECIMAL128;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
// Write the data from the value
for (let i = 0; i < 16; i++) buffer[index + i] = value.bytes[i];
return index + 16;
}
function serializeLong(buffer: Uint8Array, key: string, value: Long, index: number) {
// Write the type
buffer[index++] =
value[bsonType] === 'Long' ? constants.BSON_DATA_LONG : constants.BSON_DATA_TIMESTAMP;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
// Write the date
const lowBits = value.getLowBits();
const highBits = value.getHighBits();
// Encode low bits
index += NumberUtils.setInt32LE(buffer, index, lowBits);
// Encode high bits
index += NumberUtils.setInt32LE(buffer, index, highBits);
return index;
}
function serializeInt32(buffer: Uint8Array, key: string, value: Int32 | number, index: number) {
value = value.valueOf();
// Set int type 32 bits or less
buffer[index++] = constants.BSON_DATA_INT;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
// Write the int value
index += NumberUtils.setInt32LE(buffer, index, value);
return index;
}
function serializeDouble(buffer: Uint8Array, key: string, value: Double, index: number) {
// Encode as double
buffer[index++] = constants.BSON_DATA_NUMBER;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
// Write float
index += NumberUtils.setFloat64LE(buffer, index, value.value);
return index;
}
function serializeFunction(buffer: Uint8Array, key: string, value: Function, index: number) {
buffer[index++] = constants.BSON_DATA_CODE;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
// Function string
const functionString = value.toString();
// Write the string
const size = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
// Write the size of the string to buffer
NumberUtils.setInt32LE(buffer, index, size);
// Update index
index = index + 4 + size - 1;
// Write zero
buffer[index++] = 0;
return index;
}
function serializeBinary(buffer: Uint8Array, key: string, value: Binary, index: number) {
// Write the type
buffer[index++] = constants.BSON_DATA_BINARY;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
// Extract the buffer
const data = value.buffer;
// Calculate size
let size = value.position;
// Add the deprecated 02 type 4 bytes of size to total
if (value.sub_type === Binary.SUBTYPE_BYTE_ARRAY) size = size + 4;
// Write the size of the string to buffer
index += NumberUtils.setInt32LE(buffer, index, size);
// Write the subtype to the buffer
buffer[index++] = value.sub_type;
// If we have binary type 2 the 4 first bytes are the size
if (value.sub_type === Binary.SUBTYPE_BYTE_ARRAY) {
size = size - 4;
index += NumberUtils.setInt32LE(buffer, index, size);
}
if (value.sub_type === Binary.SUBTYPE_VECTOR) {
validateBinaryVector(value);
}
if (size <= 16) {
for (let i = 0; i < size; i++) buffer[index + i] = data[i];
} else {
buffer.set(data, index);
}
// Adjust the index
index = index + value.position;
return index;
}
function serializeSymbol(buffer: Uint8Array, key: string, value: BSONSymbol, index: number) {
// Write the type
buffer[index++] = constants.BSON_DATA_SYMBOL;
// Number of written bytes
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
// Encode the name
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
// Write the string
const size = ByteUtils.encodeUTF8Into(buffer, value.value, index + 4) + 1;
// Write the size of the string to buffer
NumberUtils.setInt32LE(buffer, index, size);
// Update index
index = index + 4 + size - 1;
// Write zero
buffer[index++] = 0;
return index;
}
interface SerializationFrame {
/** Original object passed to this frame — used for cycle-detection (path.delete). */
sourceObject: Document;
/** True when serializing a BSON array; affects key format and type byte. */
isArray: boolean;
/** Buffer offset where this object's 4-byte size field will be written. */
objectSizeIndex: number;
/** Buffer offset for code-with-scope wrapper size, or null if not applicable. */
codeSizeIndex: number | null;
/**
* Object being iterated. For plain objects this may be the toBSON() result of
* sourceObject; for arrays and Maps it equals sourceObject.
*/
iterTarget: Document;
/**
* Pre-computed Object.keys() of iterTarget for plain objects.
* Null for arrays (use keyIndex as numeric index) and Maps (use mapIterator).
*/
keys: string[] | null;
/** Next index into keys[] for plain objects, or next array index for arrays. */
keyIndex: number;
/** Active iterator for Map objects; null for arrays and plain objects. */
mapIterator: IterableIterator<[unknown, unknown]> | null;
/** The enclosing frame, or null at the root. The stack is a linked list via this field. */
prev: SerializationFrame | null;
/** Whether to validate keys for this frame's document (may differ from caller, e.g. DBRef uses false). */
checkKeys: boolean;
/** Whether undefined values are skipped (may differ from caller, e.g. DBRef uses true). */
ignoreUndefined: boolean;
}
function makeFrame(
sourceObject: Document,
objectSizeIndex: number,
codeSizeIndex: number | null,
prev: SerializationFrame | null,
checkKeys: boolean,
ignoreUndefined: boolean
): SerializationFrame {
if (Array.isArray(sourceObject)) {
return {
sourceObject,
isArray: true,
objectSizeIndex,
codeSizeIndex,
iterTarget: sourceObject,
keys: null,
keyIndex: 0,
mapIterator: null,
prev,
checkKeys,
ignoreUndefined
};
}
if (sourceObject instanceof Map || isMap(sourceObject)) {
return {
sourceObject,
isArray: false,
objectSizeIndex,
codeSizeIndex,
iterTarget: sourceObject,
keys: null,
keyIndex: 0,
mapIterator: (sourceObject as Map<unknown, unknown>).entries(),
prev,
checkKeys,
ignoreUndefined
};
}
// Plain object: call toBSON() if defined to obtain the object to iterate.
let target: Document = sourceObject;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (typeof (target as any)?.toBSON === 'function') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
target = (target as any).toBSON() as Document;
if (target != null && typeof target !== 'object') {
throw new BSONError('toBSON function did not return an object');
}
}
return {
sourceObject,
isArray: false,
objectSizeIndex,
codeSizeIndex,
iterTarget: target,
keys: Object.keys(target as object),
keyIndex: 0,
mapIterator: null,
prev,
checkKeys,
ignoreUndefined
};
}
export function serializeInto(
buffer: Uint8Array,
object: Document,
checkKeys: boolean,
startingIndex: number,
serializeFunctions: boolean,
ignoreUndefined: boolean,
path: Set<Document> | null
): number {
if (path == null) {
// We are at the root input
if (object == null) {
// ONLY the root should turn into an empty document
// BSON Empty document has a size of 5 (LE)
buffer[0] = 0x05;
buffer[1] = 0x00;
buffer[2] = 0x00;
buffer[3] = 0x00;
// All documents end with null terminator
buffer[4] = 0x00;
return 5;
}
if (Array.isArray(object)) {
throw new BSONError('serialize does not support an array as the root input');
}
if (typeof object !== 'object') {
throw new BSONError('serialize does not support non-object as the root input');
} else if ('_bsontype' in object && typeof object._bsontype === 'string') {
throw new BSONError(`BSON types cannot be serialized as a document`);
} else if (
isDate(object) ||
isRegExp(object) ||
isUint8Array(object) ||
isAnyArrayBuffer(object)
) {
throw new BSONError(`date, regexp, typedarray, and arraybuffer cannot be BSON documents`);
}
path = new Set();
}
path.add(object);
let currentFrame: SerializationFrame | null = makeFrame(
object,
startingIndex,
null,
null,
checkKeys,
ignoreUndefined
);
let index = startingIndex + 4;
while (currentFrame !== null) {
const frame: SerializationFrame = currentFrame;
// Advance to the next key-value pair, or finalize the frame if exhausted.
let key: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let value: any;
if (frame.mapIterator !== null) {
const next = frame.mapIterator.next();
if (next.done) {
buffer[index++] = 0x00;
NumberUtils.setInt32LE(buffer, frame.objectSizeIndex, index - frame.objectSizeIndex);
if (frame.codeSizeIndex !== null) {
NumberUtils.setInt32LE(buffer, frame.codeSizeIndex, index - frame.codeSizeIndex);
}
path.delete(frame.sourceObject);
currentFrame = frame.prev;
continue;
}
key = next.value[0] as string;
value = next.value[1];
} else if (frame.keys !== null) {
if (frame.keyIndex >= frame.keys.length) {
buffer[index++] = 0x00;
NumberUtils.setInt32LE(buffer, frame.objectSizeIndex, index - frame.objectSizeIndex);
if (frame.codeSizeIndex !== null) {
NumberUtils.setInt32LE(buffer, frame.codeSizeIndex, index - frame.codeSizeIndex);
}
path.delete(frame.sourceObject);
currentFrame = frame.prev;
continue;
}
key = frame.keys[frame.keyIndex++];
value = (frame.iterTarget as Record<string, unknown>)[key];
} else {
// Array: use keyIndex as the numeric index.
const arr = frame.iterTarget as unknown[];
if (frame.keyIndex >= arr.length) {
buffer[index++] = 0x00;
NumberUtils.setInt32LE(buffer, frame.objectSizeIndex, index - frame.objectSizeIndex);
if (frame.codeSizeIndex !== null) {
NumberUtils.setInt32LE(buffer, frame.codeSizeIndex, index - frame.codeSizeIndex);
}
path.delete(frame.sourceObject);
currentFrame = frame.prev;
continue;
}
const i = frame.keyIndex++;
key = String(i);
value = arr[i];
}
if (typeof value?.toBSON === 'function') {
value = value.toBSON();
}
if (!frame.isArray && typeof key === 'string' && !(key[0] === '$' && ignoreKeys.has(key))) {
if (regexp.test(key)) {
throw new BSONError('key ' + key + ' must not contain null bytes');
}
if (frame.checkKeys) {
if ('$' === key[0]) {
throw new BSONError('key ' + key + " must not start with '$'");
} else if (key.includes('.')) {
throw new BSONError('key ' + key + " must not contain '.'");
}
}
}
const type = typeof value;
if (value === undefined) {
if (frame.isArray || frame.ignoreUndefined === false) {
index = serializeNull(buffer, key, value, index);
}
} else if (value === null) {
index = serializeNull(buffer, key, value, index);
} else if (type === 'string') {
index = serializeString(buffer, key, value, index);
} else if (type === 'number') {
index = serializeNumber(buffer, key, value, index);
} else if (type === 'bigint') {
index = serializeBigInt(buffer, key, value, index);
} else if (type === 'boolean') {
index = serializeBoolean(buffer, key, value, index);
} else if (type === 'object' && value._bsontype == null) {
if (value instanceof Date || isDate(value)) {
index = serializeDate(buffer, key, value, index);
} else if (value instanceof Uint8Array || isUint8Array(value)) {
index = serializeBuffer(buffer, key, value, index);
} else if (value instanceof RegExp || isRegExp(value)) {
index = serializeRegExp(buffer, key, value, index);
} else {
if (path.has(value)) {
throw new BSONError('Cannot convert circular structure to BSON');
}
const nestedIsArray = Array.isArray(value);
buffer[index++] = nestedIsArray ? constants.BSON_DATA_ARRAY : constants.BSON_DATA_OBJECT;
index += ByteUtils.encodeUTF8Into(buffer, key, index);
buffer[index++] = 0x00;
const nestedStartIndex = index;
path.add(value);
currentFrame = makeFrame(
value,
nestedStartIndex,
null,
frame,
frame.checkKeys,
frame.ignoreUndefined
);
index += 4;
}
} else if (type === 'object') {
if (value[constants.BSON_VERSION_SYMBOL] !== constants.BSON_MAJOR_VERSION) {
throw new BSONVersionError();
}
const tag = value[bsonType];
if (tag === 'ObjectId') {
index = serializeObjectId(buffer, key, value, index);
} else if (tag === 'Decimal128') {
index = serializeDecimal128(buffer, key, value, index);
} else if (tag === 'Long' || tag === 'Timestamp') {
index = serializeLong(buffer, key, value, index);
} else if (tag === 'Double') {
index = serializeDouble(buffer, key, value, index);
} else if (tag === 'Code') {
const codeValue = value as Code;
if (codeValue.scope && typeof codeValue.scope === 'object') {
buffer[index++] = constants.BSON_DATA_CODE_W_SCOPE;
index += ByteUtils.encodeUTF8Into(buffer, key, index);
buffer[index++] = 0x00;
const codeTotalSizeIndex = index;
index += 4;
const functionString = codeValue.code;
const codeSize = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
NumberUtils.setInt32LE(buffer, index, codeSize);
buffer[index + 4 + codeSize - 1] = 0;
index = index + codeSize + 4;
const scope = codeValue.scope;
if (path.has(scope)) {
throw new BSONError('Cannot convert circular structure to BSON');
}
path.add(scope);
currentFrame = makeFrame(
scope,
index,
codeTotalSizeIndex,
frame,
frame.checkKeys,
frame.ignoreUndefined
);
index += 4;
} else {
buffer[index++] = constants.BSON_DATA_CODE;
index += ByteUtils.encodeUTF8Into(buffer, key, index);
buffer[index++] = 0x00;
const functionString = codeValue.code.toString();
const size = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
NumberUtils.setInt32LE(buffer, index, size);
index = index + 4 + size - 1;
buffer[index++] = 0;
}
} else if (tag === 'Binary') {
index = serializeBinary(buffer, key, value, index);
} else if (tag === 'BSONSymbol') {
index = serializeSymbol(buffer, key, value, index);
} else if (tag === 'DBRef') {
const dbref = value as DBRef;
const orderedValues: Document = Object.assign(
{ $ref: dbref.collection, $id: dbref.oid },
dbref.db != null ? { $db: dbref.db } : null,
dbref.fields
);
buffer[index++] = constants.BSON_DATA_OBJECT;
index += ByteUtils.encodeUTF8Into(buffer, key, index);
buffer[index++] = 0x00;
path.add(orderedValues);
currentFrame = makeFrame(orderedValues, index, null, frame, false, true);
index += 4;
} else if (tag === 'BSONRegExp') {
index = serializeBSONRegExp(buffer, key, value, index);
} else if (tag === 'Int32') {
index = serializeInt32(buffer, key, value, index);
} else if (tag === 'MinKey' || tag === 'MaxKey') {
index = serializeMinMax(buffer, key, value, index);
} else if (typeof value._bsontype !== 'undefined') {
throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
}
} else if (type === 'function' && serializeFunctions) {
index = serializeFunction(buffer, key, value, index);
}
}
return index;
}