|
|
import { Binary } from '../binary'; |
|
|
import type { BSONSymbol, DBRef, Document, MaxKey } from '../bson'; |
|
|
import type { Code } from '../code'; |
|
|
import * as constants from '../constants'; |
|
|
import type { DBRefLike } from '../db_ref'; |
|
|
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 { isAnyArrayBuffer, isDate, isMap, isRegExp, isUint8Array } from './utils'; |
|
|
|
|
|
|
|
|
export interface SerializeOptions { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
checkKeys?: boolean; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
serializeFunctions?: boolean; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ignoreUndefined?: boolean; |
|
|
|
|
|
minInternalBufferSize?: number; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index?: number; |
|
|
} |
|
|
|
|
|
const regexp = /\x00/; |
|
|
const ignoreKeys = new Set(['$db', '$ref', '$id', '$clusterTime']); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function serializeString(buffer: Uint8Array, key: string, value: string, index: number) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_STRING; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes + 1; |
|
|
buffer[index - 1] = 0; |
|
|
|
|
|
const size = ByteUtils.encodeUTF8Into(buffer, value, index + 4); |
|
|
|
|
|
buffer[index + 3] = ((size + 1) >> 24) & 0xff; |
|
|
buffer[index + 2] = ((size + 1) >> 16) & 0xff; |
|
|
buffer[index + 1] = ((size + 1) >> 8) & 0xff; |
|
|
buffer[index] = (size + 1) & 0xff; |
|
|
|
|
|
index = index + 4 + size; |
|
|
|
|
|
buffer[index++] = 0; |
|
|
return index; |
|
|
} |
|
|
|
|
|
const NUMBER_SPACE = new DataView(new ArrayBuffer(8), 0, 8); |
|
|
const FOUR_BYTE_VIEW_ON_NUMBER = new Uint8Array(NUMBER_SPACE.buffer, 0, 4); |
|
|
const EIGHT_BYTE_VIEW_ON_NUMBER = new Uint8Array(NUMBER_SPACE.buffer, 0, 8); |
|
|
|
|
|
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; |
|
|
|
|
|
if (type === constants.BSON_DATA_INT) { |
|
|
NUMBER_SPACE.setInt32(0, value, true); |
|
|
} else { |
|
|
NUMBER_SPACE.setFloat64(0, value, true); |
|
|
} |
|
|
|
|
|
const bytes = |
|
|
type === constants.BSON_DATA_INT ? FOUR_BYTE_VIEW_ON_NUMBER : EIGHT_BYTE_VIEW_ON_NUMBER; |
|
|
|
|
|
buffer[index++] = type; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0x00; |
|
|
|
|
|
buffer.set(bytes, index); |
|
|
index += bytes.byteLength; |
|
|
|
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeBigInt(buffer: Uint8Array, key: string, value: bigint, index: number) { |
|
|
buffer[index++] = constants.BSON_DATA_LONG; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index += numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
NUMBER_SPACE.setBigInt64(0, value, true); |
|
|
|
|
|
buffer.set(EIGHT_BYTE_VIEW_ON_NUMBER, index); |
|
|
index += EIGHT_BYTE_VIEW_ON_NUMBER.byteLength; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeNull(buffer: Uint8Array, key: string, _: unknown, index: number) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_NULL; |
|
|
|
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeBoolean(buffer: Uint8Array, key: string, value: boolean, index: number) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_BOOLEAN; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
buffer[index++] = value ? 1 : 0; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeDate(buffer: Uint8Array, key: string, value: Date, index: number) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_DATE; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
|
|
|
const dateInMilis = Long.fromNumber(value.getTime()); |
|
|
const lowBits = dateInMilis.getLowBits(); |
|
|
const highBits = dateInMilis.getHighBits(); |
|
|
|
|
|
buffer[index++] = lowBits & 0xff; |
|
|
buffer[index++] = (lowBits >> 8) & 0xff; |
|
|
buffer[index++] = (lowBits >> 16) & 0xff; |
|
|
buffer[index++] = (lowBits >> 24) & 0xff; |
|
|
|
|
|
buffer[index++] = highBits & 0xff; |
|
|
buffer[index++] = (highBits >> 8) & 0xff; |
|
|
buffer[index++] = (highBits >> 16) & 0xff; |
|
|
buffer[index++] = (highBits >> 24) & 0xff; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeRegExp(buffer: Uint8Array, key: string, value: RegExp, index: number) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_REGEXP; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
|
|
|
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'); |
|
|
} |
|
|
|
|
|
index = index + ByteUtils.encodeUTF8Into(buffer, value.source, index); |
|
|
|
|
|
buffer[index++] = 0x00; |
|
|
|
|
|
if (value.ignoreCase) buffer[index++] = 0x69; |
|
|
if (value.global) buffer[index++] = 0x73; |
|
|
if (value.multiline) buffer[index++] = 0x6d; |
|
|
|
|
|
|
|
|
buffer[index++] = 0x00; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeBSONRegExp(buffer: Uint8Array, key: string, value: BSONRegExp, index: number) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_REGEXP; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
|
|
|
if (value.pattern.match(regexp) != null) { |
|
|
|
|
|
|
|
|
throw new BSONError('pattern ' + value.pattern + ' must not contain null bytes'); |
|
|
} |
|
|
|
|
|
|
|
|
index = index + ByteUtils.encodeUTF8Into(buffer, value.pattern, index); |
|
|
|
|
|
buffer[index++] = 0x00; |
|
|
|
|
|
const sortedOptions = value.options.split('').sort().join(''); |
|
|
index = index + ByteUtils.encodeUTF8Into(buffer, sortedOptions, index); |
|
|
|
|
|
buffer[index++] = 0x00; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeMinMax(buffer: Uint8Array, key: string, value: MinKey | MaxKey, index: number) { |
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeObjectId(buffer: Uint8Array, key: string, value: ObjectId, index: number) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_OID; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
|
|
|
if (isUint8Array(value.id)) { |
|
|
buffer.set(value.id.subarray(0, 12), index); |
|
|
} else { |
|
|
throw new BSONError('object [' + JSON.stringify(value) + '] is not a valid ObjectId'); |
|
|
} |
|
|
|
|
|
|
|
|
return index + 12; |
|
|
} |
|
|
|
|
|
function serializeBuffer(buffer: Uint8Array, key: string, value: Uint8Array, index: number) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_BINARY; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
const size = value.length; |
|
|
|
|
|
buffer[index++] = size & 0xff; |
|
|
buffer[index++] = (size >> 8) & 0xff; |
|
|
buffer[index++] = (size >> 16) & 0xff; |
|
|
buffer[index++] = (size >> 24) & 0xff; |
|
|
|
|
|
buffer[index++] = constants.BSON_BINARY_SUBTYPE_DEFAULT; |
|
|
|
|
|
buffer.set(value, index); |
|
|
|
|
|
index = index + size; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeObject( |
|
|
buffer: Uint8Array, |
|
|
key: string, |
|
|
value: Document, |
|
|
index: number, |
|
|
checkKeys: boolean, |
|
|
depth: number, |
|
|
serializeFunctions: boolean, |
|
|
ignoreUndefined: boolean, |
|
|
path: Set<Document> |
|
|
) { |
|
|
if (path.has(value)) { |
|
|
throw new BSONError('Cannot convert circular structure to BSON'); |
|
|
} |
|
|
|
|
|
path.add(value); |
|
|
|
|
|
|
|
|
buffer[index++] = Array.isArray(value) ? constants.BSON_DATA_ARRAY : constants.BSON_DATA_OBJECT; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
const endIndex = serializeInto( |
|
|
buffer, |
|
|
value, |
|
|
checkKeys, |
|
|
index, |
|
|
depth + 1, |
|
|
serializeFunctions, |
|
|
ignoreUndefined, |
|
|
path |
|
|
); |
|
|
|
|
|
path.delete(value); |
|
|
|
|
|
return endIndex; |
|
|
} |
|
|
|
|
|
function serializeDecimal128(buffer: Uint8Array, key: string, value: Decimal128, index: number) { |
|
|
buffer[index++] = constants.BSON_DATA_DECIMAL128; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
buffer.set(value.bytes.subarray(0, 16), index); |
|
|
return index + 16; |
|
|
} |
|
|
|
|
|
function serializeLong(buffer: Uint8Array, key: string, value: Long, index: number) { |
|
|
|
|
|
buffer[index++] = |
|
|
value._bsontype === 'Long' ? constants.BSON_DATA_LONG : constants.BSON_DATA_TIMESTAMP; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
const lowBits = value.getLowBits(); |
|
|
const highBits = value.getHighBits(); |
|
|
|
|
|
buffer[index++] = lowBits & 0xff; |
|
|
buffer[index++] = (lowBits >> 8) & 0xff; |
|
|
buffer[index++] = (lowBits >> 16) & 0xff; |
|
|
buffer[index++] = (lowBits >> 24) & 0xff; |
|
|
|
|
|
buffer[index++] = highBits & 0xff; |
|
|
buffer[index++] = (highBits >> 8) & 0xff; |
|
|
buffer[index++] = (highBits >> 16) & 0xff; |
|
|
buffer[index++] = (highBits >> 24) & 0xff; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeInt32(buffer: Uint8Array, key: string, value: Int32 | number, index: number) { |
|
|
value = value.valueOf(); |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_INT; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
buffer[index++] = value & 0xff; |
|
|
buffer[index++] = (value >> 8) & 0xff; |
|
|
buffer[index++] = (value >> 16) & 0xff; |
|
|
buffer[index++] = (value >> 24) & 0xff; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeDouble(buffer: Uint8Array, key: string, value: Double, index: number) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_NUMBER; |
|
|
|
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
|
|
|
NUMBER_SPACE.setFloat64(0, value.value, true); |
|
|
buffer.set(EIGHT_BYTE_VIEW_ON_NUMBER, index); |
|
|
|
|
|
|
|
|
index = index + 8; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeFunction(buffer: Uint8Array, key: string, value: Function, index: number) { |
|
|
buffer[index++] = constants.BSON_DATA_CODE; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
const functionString = value.toString(); |
|
|
|
|
|
|
|
|
const size = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1; |
|
|
|
|
|
buffer[index] = size & 0xff; |
|
|
buffer[index + 1] = (size >> 8) & 0xff; |
|
|
buffer[index + 2] = (size >> 16) & 0xff; |
|
|
buffer[index + 3] = (size >> 24) & 0xff; |
|
|
|
|
|
index = index + 4 + size - 1; |
|
|
|
|
|
buffer[index++] = 0; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeCode( |
|
|
buffer: Uint8Array, |
|
|
key: string, |
|
|
value: Code, |
|
|
index: number, |
|
|
checkKeys = false, |
|
|
depth = 0, |
|
|
serializeFunctions = false, |
|
|
ignoreUndefined = true, |
|
|
path: Set<Document> |
|
|
) { |
|
|
if (value.scope && typeof value.scope === 'object') { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_CODE_W_SCOPE; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
|
|
|
let startIndex = index; |
|
|
|
|
|
|
|
|
|
|
|
const functionString = value.code; |
|
|
|
|
|
index = index + 4; |
|
|
|
|
|
const codeSize = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1; |
|
|
|
|
|
buffer[index] = codeSize & 0xff; |
|
|
buffer[index + 1] = (codeSize >> 8) & 0xff; |
|
|
buffer[index + 2] = (codeSize >> 16) & 0xff; |
|
|
buffer[index + 3] = (codeSize >> 24) & 0xff; |
|
|
|
|
|
buffer[index + 4 + codeSize - 1] = 0; |
|
|
|
|
|
index = index + codeSize + 4; |
|
|
|
|
|
|
|
|
const endIndex = serializeInto( |
|
|
buffer, |
|
|
value.scope, |
|
|
checkKeys, |
|
|
index, |
|
|
depth + 1, |
|
|
serializeFunctions, |
|
|
ignoreUndefined, |
|
|
path |
|
|
); |
|
|
index = endIndex - 1; |
|
|
|
|
|
|
|
|
const totalSize = endIndex - startIndex; |
|
|
|
|
|
|
|
|
buffer[startIndex++] = totalSize & 0xff; |
|
|
buffer[startIndex++] = (totalSize >> 8) & 0xff; |
|
|
buffer[startIndex++] = (totalSize >> 16) & 0xff; |
|
|
buffer[startIndex++] = (totalSize >> 24) & 0xff; |
|
|
|
|
|
buffer[index++] = 0; |
|
|
} else { |
|
|
buffer[index++] = constants.BSON_DATA_CODE; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
const functionString = value.code.toString(); |
|
|
|
|
|
const size = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1; |
|
|
|
|
|
buffer[index] = size & 0xff; |
|
|
buffer[index + 1] = (size >> 8) & 0xff; |
|
|
buffer[index + 2] = (size >> 16) & 0xff; |
|
|
buffer[index + 3] = (size >> 24) & 0xff; |
|
|
|
|
|
index = index + 4 + size - 1; |
|
|
|
|
|
buffer[index++] = 0; |
|
|
} |
|
|
|
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeBinary(buffer: Uint8Array, key: string, value: Binary, index: number) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_BINARY; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
const data = value.buffer; |
|
|
|
|
|
let size = value.position; |
|
|
|
|
|
if (value.sub_type === Binary.SUBTYPE_BYTE_ARRAY) size = size + 4; |
|
|
|
|
|
buffer[index++] = size & 0xff; |
|
|
buffer[index++] = (size >> 8) & 0xff; |
|
|
buffer[index++] = (size >> 16) & 0xff; |
|
|
buffer[index++] = (size >> 24) & 0xff; |
|
|
|
|
|
buffer[index++] = value.sub_type; |
|
|
|
|
|
|
|
|
if (value.sub_type === Binary.SUBTYPE_BYTE_ARRAY) { |
|
|
size = size - 4; |
|
|
buffer[index++] = size & 0xff; |
|
|
buffer[index++] = (size >> 8) & 0xff; |
|
|
buffer[index++] = (size >> 16) & 0xff; |
|
|
buffer[index++] = (size >> 24) & 0xff; |
|
|
} |
|
|
|
|
|
|
|
|
buffer.set(data, index); |
|
|
|
|
|
index = index + value.position; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeSymbol(buffer: Uint8Array, key: string, value: BSONSymbol, index: number) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_SYMBOL; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
const size = ByteUtils.encodeUTF8Into(buffer, value.value, index + 4) + 1; |
|
|
|
|
|
buffer[index] = size & 0xff; |
|
|
buffer[index + 1] = (size >> 8) & 0xff; |
|
|
buffer[index + 2] = (size >> 16) & 0xff; |
|
|
buffer[index + 3] = (size >> 24) & 0xff; |
|
|
|
|
|
index = index + 4 + size - 1; |
|
|
|
|
|
buffer[index++] = 0x00; |
|
|
return index; |
|
|
} |
|
|
|
|
|
function serializeDBRef( |
|
|
buffer: Uint8Array, |
|
|
key: string, |
|
|
value: DBRef, |
|
|
index: number, |
|
|
depth: number, |
|
|
serializeFunctions: boolean, |
|
|
path: Set<Document> |
|
|
) { |
|
|
|
|
|
buffer[index++] = constants.BSON_DATA_OBJECT; |
|
|
|
|
|
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index); |
|
|
|
|
|
|
|
|
index = index + numberOfWrittenBytes; |
|
|
buffer[index++] = 0; |
|
|
|
|
|
let startIndex = index; |
|
|
let output: DBRefLike = { |
|
|
$ref: value.collection || value.namespace, |
|
|
$id: value.oid |
|
|
}; |
|
|
|
|
|
if (value.db != null) { |
|
|
output.$db = value.db; |
|
|
} |
|
|
|
|
|
output = Object.assign(output, value.fields); |
|
|
const endIndex = serializeInto( |
|
|
buffer, |
|
|
output, |
|
|
false, |
|
|
index, |
|
|
depth + 1, |
|
|
serializeFunctions, |
|
|
true, |
|
|
path |
|
|
); |
|
|
|
|
|
|
|
|
const size = endIndex - startIndex; |
|
|
|
|
|
buffer[startIndex++] = size & 0xff; |
|
|
buffer[startIndex++] = (size >> 8) & 0xff; |
|
|
buffer[startIndex++] = (size >> 16) & 0xff; |
|
|
buffer[startIndex++] = (size >> 24) & 0xff; |
|
|
|
|
|
return endIndex; |
|
|
} |
|
|
|
|
|
export function serializeInto( |
|
|
buffer: Uint8Array, |
|
|
object: Document, |
|
|
checkKeys: boolean, |
|
|
startingIndex: number, |
|
|
depth: number, |
|
|
serializeFunctions: boolean, |
|
|
ignoreUndefined: boolean, |
|
|
path: Set<Document> | null |
|
|
): number { |
|
|
if (path == null) { |
|
|
|
|
|
if (object == null) { |
|
|
|
|
|
|
|
|
buffer[0] = 0x05; |
|
|
buffer[1] = 0x00; |
|
|
buffer[2] = 0x00; |
|
|
buffer[3] = 0x00; |
|
|
|
|
|
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 index = startingIndex + 4; |
|
|
|
|
|
|
|
|
if (Array.isArray(object)) { |
|
|
|
|
|
for (let i = 0; i < object.length; i++) { |
|
|
const key = `${i}`; |
|
|
let value = object[i]; |
|
|
|
|
|
|
|
|
if (typeof value?.toBSON === 'function') { |
|
|
value = value.toBSON(); |
|
|
} |
|
|
|
|
|
if (typeof value === 'string') { |
|
|
index = serializeString(buffer, key, value, index); |
|
|
} else if (typeof value === 'number') { |
|
|
index = serializeNumber(buffer, key, value, index); |
|
|
} else if (typeof value === 'bigint') { |
|
|
index = serializeBigInt(buffer, key, value, index); |
|
|
} else if (typeof value === 'boolean') { |
|
|
index = serializeBoolean(buffer, key, value, index); |
|
|
} else if (value instanceof Date || isDate(value)) { |
|
|
index = serializeDate(buffer, key, value, index); |
|
|
} else if (value === undefined) { |
|
|
index = serializeNull(buffer, key, value, index); |
|
|
} else if (value === null) { |
|
|
index = serializeNull(buffer, key, value, index); |
|
|
} else if (isUint8Array(value)) { |
|
|
index = serializeBuffer(buffer, key, value, index); |
|
|
} else if (value instanceof RegExp || isRegExp(value)) { |
|
|
index = serializeRegExp(buffer, key, value, index); |
|
|
} else if (typeof value === 'object' && value._bsontype == null) { |
|
|
index = serializeObject( |
|
|
buffer, |
|
|
key, |
|
|
value, |
|
|
index, |
|
|
checkKeys, |
|
|
depth, |
|
|
serializeFunctions, |
|
|
ignoreUndefined, |
|
|
path |
|
|
); |
|
|
} else if ( |
|
|
typeof value === 'object' && |
|
|
value[Symbol.for('@@mdb.bson.version')] !== constants.BSON_MAJOR_VERSION |
|
|
) { |
|
|
throw new BSONVersionError(); |
|
|
} else if (value._bsontype === 'ObjectId') { |
|
|
index = serializeObjectId(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Decimal128') { |
|
|
index = serializeDecimal128(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { |
|
|
index = serializeLong(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Double') { |
|
|
index = serializeDouble(buffer, key, value, index); |
|
|
} else if (typeof value === 'function' && serializeFunctions) { |
|
|
index = serializeFunction(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Code') { |
|
|
index = serializeCode( |
|
|
buffer, |
|
|
key, |
|
|
value, |
|
|
index, |
|
|
checkKeys, |
|
|
depth, |
|
|
serializeFunctions, |
|
|
ignoreUndefined, |
|
|
path |
|
|
); |
|
|
} else if (value._bsontype === 'Binary') { |
|
|
index = serializeBinary(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'BSONSymbol') { |
|
|
index = serializeSymbol(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'DBRef') { |
|
|
index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); |
|
|
} else if (value._bsontype === 'BSONRegExp') { |
|
|
index = serializeBSONRegExp(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Int32') { |
|
|
index = serializeInt32(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'MinKey' || value._bsontype === '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 (object instanceof Map || isMap(object)) { |
|
|
const iterator = object.entries(); |
|
|
let done = false; |
|
|
|
|
|
while (!done) { |
|
|
|
|
|
const entry = iterator.next(); |
|
|
done = !!entry.done; |
|
|
|
|
|
if (done) continue; |
|
|
|
|
|
|
|
|
const key = entry.value[0]; |
|
|
let value = entry.value[1]; |
|
|
|
|
|
if (typeof value?.toBSON === 'function') { |
|
|
value = value.toBSON(); |
|
|
} |
|
|
|
|
|
|
|
|
const type = typeof value; |
|
|
|
|
|
|
|
|
if (typeof key === 'string' && !ignoreKeys.has(key)) { |
|
|
if (key.match(regexp) != null) { |
|
|
|
|
|
|
|
|
throw new BSONError('key ' + key + ' must not contain null bytes'); |
|
|
} |
|
|
|
|
|
if (checkKeys) { |
|
|
if ('$' === key[0]) { |
|
|
throw new BSONError('key ' + key + " must not start with '$'"); |
|
|
} else if (~key.indexOf('.')) { |
|
|
throw new BSONError('key ' + key + " must not contain '.'"); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
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 (value instanceof Date || isDate(value)) { |
|
|
index = serializeDate(buffer, key, value, index); |
|
|
} else if (value === null || (value === undefined && ignoreUndefined === false)) { |
|
|
index = serializeNull(buffer, key, value, index); |
|
|
} else if (isUint8Array(value)) { |
|
|
index = serializeBuffer(buffer, key, value, index); |
|
|
} else if (value instanceof RegExp || isRegExp(value)) { |
|
|
index = serializeRegExp(buffer, key, value, index); |
|
|
} else if (type === 'object' && value._bsontype == null) { |
|
|
index = serializeObject( |
|
|
buffer, |
|
|
key, |
|
|
value, |
|
|
index, |
|
|
checkKeys, |
|
|
depth, |
|
|
serializeFunctions, |
|
|
ignoreUndefined, |
|
|
path |
|
|
); |
|
|
} else if ( |
|
|
typeof value === 'object' && |
|
|
value[Symbol.for('@@mdb.bson.version')] !== constants.BSON_MAJOR_VERSION |
|
|
) { |
|
|
throw new BSONVersionError(); |
|
|
} else if (value._bsontype === 'ObjectId') { |
|
|
index = serializeObjectId(buffer, key, value, index); |
|
|
} else if (type === 'object' && value._bsontype === 'Decimal128') { |
|
|
index = serializeDecimal128(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { |
|
|
index = serializeLong(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Double') { |
|
|
index = serializeDouble(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Code') { |
|
|
index = serializeCode( |
|
|
buffer, |
|
|
key, |
|
|
value, |
|
|
index, |
|
|
checkKeys, |
|
|
depth, |
|
|
serializeFunctions, |
|
|
ignoreUndefined, |
|
|
path |
|
|
); |
|
|
} else if (typeof value === 'function' && serializeFunctions) { |
|
|
index = serializeFunction(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Binary') { |
|
|
index = serializeBinary(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'BSONSymbol') { |
|
|
index = serializeSymbol(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'DBRef') { |
|
|
index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); |
|
|
} else if (value._bsontype === 'BSONRegExp') { |
|
|
index = serializeBSONRegExp(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Int32') { |
|
|
index = serializeInt32(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'MinKey' || value._bsontype === '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 (typeof object?.toBSON === 'function') { |
|
|
|
|
|
object = object.toBSON(); |
|
|
if (object != null && typeof object !== 'object') { |
|
|
throw new BSONError('toBSON function did not return an object'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (const key of Object.keys(object)) { |
|
|
let value = object[key]; |
|
|
|
|
|
if (typeof value?.toBSON === 'function') { |
|
|
value = value.toBSON(); |
|
|
} |
|
|
|
|
|
|
|
|
const type = typeof value; |
|
|
|
|
|
|
|
|
if (typeof key === 'string' && !ignoreKeys.has(key)) { |
|
|
if (key.match(regexp) != null) { |
|
|
|
|
|
|
|
|
throw new BSONError('key ' + key + ' must not contain null bytes'); |
|
|
} |
|
|
|
|
|
if (checkKeys) { |
|
|
if ('$' === key[0]) { |
|
|
throw new BSONError('key ' + key + " must not start with '$'"); |
|
|
} else if (~key.indexOf('.')) { |
|
|
throw new BSONError('key ' + key + " must not contain '.'"); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
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 (value instanceof Date || isDate(value)) { |
|
|
index = serializeDate(buffer, key, value, index); |
|
|
} else if (value === undefined) { |
|
|
if (ignoreUndefined === false) index = serializeNull(buffer, key, value, index); |
|
|
} else if (value === null) { |
|
|
index = serializeNull(buffer, key, value, index); |
|
|
} else if (isUint8Array(value)) { |
|
|
index = serializeBuffer(buffer, key, value, index); |
|
|
} else if (value instanceof RegExp || isRegExp(value)) { |
|
|
index = serializeRegExp(buffer, key, value, index); |
|
|
} else if (type === 'object' && value._bsontype == null) { |
|
|
index = serializeObject( |
|
|
buffer, |
|
|
key, |
|
|
value, |
|
|
index, |
|
|
checkKeys, |
|
|
depth, |
|
|
serializeFunctions, |
|
|
ignoreUndefined, |
|
|
path |
|
|
); |
|
|
} else if ( |
|
|
typeof value === 'object' && |
|
|
value[Symbol.for('@@mdb.bson.version')] !== constants.BSON_MAJOR_VERSION |
|
|
) { |
|
|
throw new BSONVersionError(); |
|
|
} else if (value._bsontype === 'ObjectId') { |
|
|
index = serializeObjectId(buffer, key, value, index); |
|
|
} else if (type === 'object' && value._bsontype === 'Decimal128') { |
|
|
index = serializeDecimal128(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') { |
|
|
index = serializeLong(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Double') { |
|
|
index = serializeDouble(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Code') { |
|
|
index = serializeCode( |
|
|
buffer, |
|
|
key, |
|
|
value, |
|
|
index, |
|
|
checkKeys, |
|
|
depth, |
|
|
serializeFunctions, |
|
|
ignoreUndefined, |
|
|
path |
|
|
); |
|
|
} else if (typeof value === 'function' && serializeFunctions) { |
|
|
index = serializeFunction(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Binary') { |
|
|
index = serializeBinary(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'BSONSymbol') { |
|
|
index = serializeSymbol(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'DBRef') { |
|
|
index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path); |
|
|
} else if (value._bsontype === 'BSONRegExp') { |
|
|
index = serializeBSONRegExp(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'Int32') { |
|
|
index = serializeInt32(buffer, key, value, index); |
|
|
} else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') { |
|
|
index = serializeMinMax(buffer, key, value, index); |
|
|
} else if (typeof value._bsontype !== 'undefined') { |
|
|
throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
path.delete(object); |
|
|
|
|
|
|
|
|
buffer[index++] = 0x00; |
|
|
|
|
|
|
|
|
const size = index - startingIndex; |
|
|
|
|
|
buffer[startingIndex++] = size & 0xff; |
|
|
buffer[startingIndex++] = (size >> 8) & 0xff; |
|
|
buffer[startingIndex++] = (size >> 16) & 0xff; |
|
|
buffer[startingIndex++] = (size >> 24) & 0xff; |
|
|
return index; |
|
|
} |
|
|
|