import { ClassType , CompilerContext , isArray , isObject , toFastProperties } from '@deepkit/core' ; import { ClassSchema , getClassSchema , getGlobalStore , getSortedUnionTypes , JitStack , jsonTypeGuards , PropertySchema , UnpopulatedCheck , unpopulatedSymbol } from '@deepkit/type' ; import { seekElementSize } from './continuation' ; import { isObjectId , isUUID , ObjectId , ObjectIdSymbol , UUID , UUIDSymbol } from './model' ; import { BSON_BINARY_SUBTYPE_BIGINT , BSON_BINARY_SUBTYPE_DEFAULT , BSON_BINARY_SUBTYPE_UUID , BSONType , digitByteSize , TWO_PWR_32_DBL_N } from './utils' ; export function createBuffer ( size ) { return 'undefined' !== typeof Buffer && 'function' === typeof Buffer . allocUnsafe ? Buffer . allocUnsafe ( size ) : new Uint8Array ( size ) ; } const BSON_INT32_MAX = 0x7fffffff ; const BSON_INT32_MIN = - 0x80000000 ; export const JS_INT_MAX = 0x20000000000000 ; export const JS_INT_MIN = - 0x20000000000000 ; const LONG_MAX = 'undefined' !== typeof BigInt ? BigInt ( '9223372036854775807' ) : 9223372036854775807 ; const LONG_MIN = 'undefined' !== typeof BigInt ? BigInt ( '-9223372036854775807' ) : - 9223372036854775807 ; export function hexToByte ( hex , index = 0 , offset = 0 ) { let code1 = hex . charCodeAt ( index * 2 + offset ) - 48 ; if ( code1 > 9 ) code1 -= 39 ; let code2 = hex . charCodeAt ( ( index * 2 ) + offset + 1 ) - 48 ; if ( code2 > 9 ) code2 -= 39 ; return code1 * 16 + code2 ; } export function uuidStringToByte ( hex , index = 0 ) { let offset = 0 ; if ( index > 3 ) offset += 1 ; if ( index > 5 ) offset += 1 ; if ( index > 7 ) offset += 1 ; if ( index > 9 ) offset += 1 ; return hexToByte ( hex , index , offset ) ; } export function stringByteLength ( str ) { if ( ! str ) return 0 ; let size = 0 ; for ( let i = 0 ; i < str . length ; i ++ ) { const c = str . charCodeAt ( i ) ; if ( c < 128 ) size += 1 ; else if ( c > 127 && c < 2048 ) size += 2 ; else size += 3 ; } return size ; } function getBigIntSizeBinary ( value ) { let hex = value . toString ( 16 ) ; let signum = hex === '0' ? 0 : 1 ; if ( hex [ 0 ] === '-' ) { signum = - 1 ; hex = hex . slice ( 1 ) ; } if ( hex . length % 2 ) hex = '0' + hex ; let size = 4 + 1 + Math . ceil ( hex . length / 2 ) ; if ( signum !== 0 ) size ++ ; return size ; } export function getValueSize ( value ) { if ( 'boolean' === typeof value ) { return 1 ; } else if ( 'string' === typeof value ) { return 4 + stringByteLength ( value ) + 1 ; } else if ( 'bigint' === typeof value ) { return getBigIntSizeBinary ( value ) ; } else if ( 'number' === typeof value ) { if ( Math . floor ( value ) === value ) { if ( value >= BSON_INT32_MIN && value <= BSON_INT32_MAX ) { return 4 ; } else if ( value >= JS_INT_MIN && value <= JS_INT_MAX ) { return 8 ; } else { return 8 ; } } else { return 8 ; } } else if ( value instanceof Date ) { return 8 ; } else if ( value instanceof ArrayBuffer || ArrayBuffer . isView ( value ) ) { let size = 4 ; size += 1 ; size += value . byteLength ; return size ; } else if ( isArray ( value ) ) { let size = 4 ; for ( let i = 0 ; i < value . length ; i ++ ) { size += 1 ; size += digitByteSize ( i ) ; size += getValueSize ( value [ i ] ) ; } size += 1 ; return size ; } else if ( isUUID ( value ) ) { return 4 + 1 + 16 ; } else if ( isObjectId ( value ) ) { return 12 ; } else if ( value && value [ '_bsontype' ] === 'Binary' ) { let size = 4 ; size += 1 ; size += value . buffer . byteLength ; return size ; } else if ( value instanceof RegExp ) { return stringByteLength ( value . source ) + 1 + ( value . global ? 1 : 0 ) + ( value . ignoreCase ? 1 : 0 ) + ( value . multiline ? 1 : 0 ) + 1 ; } else if ( isObject ( value ) ) { let size = 4 ; for ( let i in value ) { if ( ! value . hasOwnProperty ( i ) ) continue ; size += 1 ; size += stringByteLength ( i ) + 1 ; size += getValueSize ( value [ i ] ) ; } size += 1 ; return size ; } return 0 ; } function getPropertySizer ( schema , compiler , property , accessor , jitStack ) { if ( property . type === 'class' && property . getResolvedClassSchema ( ) . decorator ) { property = property . getResolvedClassSchema ( ) . getDecoratedPropertySchema ( ) ; accessor = `(${ accessor } && ${ accessor }.${ property . name })` ; } else if ( property . type === 'promise' ) { property = property . templateArgs [ 0 ] || new PropertySchema ( property . name ) ; } compiler . context . set ( 'getValueSize' , getValueSize ) ; let code = `size += getValueSize(${ accessor });` ; if ( property . type === 'array' ) { compiler . context . set ( 'digitByteSize' , digitByteSize ) ; const isArrayVar = compiler . reserveVariable ( 'isArray' , isArray ) ; const unpopulatedSymbolVar = compiler . reserveVariable ( 'unpopulatedSymbol' , unpopulatedSymbol ) ; const i = compiler . reserveName ( 'i' ) ; code = `
        if (${ accessor } && ${ accessor } !== ${ unpopulatedSymbolVar } && ${ isArrayVar }(${ accessor })) {
            size += 4; //array size
            for (let ${ i } = 0; ${ i } < ${ accessor }.length; ${ i }++) {
                size += 1; //element type
                size += digitByteSize(${ i }); //element name
                ${ getPropertySizer ( schema , compiler , property . getSubType ( ) , `${ accessor }[${ i }]` , jitStack ) }
            }
            size += 1; //null
        }
        ` ; } else if ( property . type === 'bigint' ) { compiler . context . set ( 'getBigIntSizeBinary' , getBigIntSizeBinary ) ; code = `
        if (typeof ${ accessor } === 'bigint') {
            size += getBigIntSizeBinary(${ accessor });
        }
        ` ; } else if ( property . type === 'number' ) { code = `
        if (typeof ${ accessor } === 'number') {
            if (Math.floor(${ accessor }) === ${ accessor }) {
                //it's an int
                if (${ accessor } >= ${ BSON_INT32_MIN } && ${ accessor } <= ${ BSON_INT32_MAX }) {
                    //32bit
                    size += 4;
                } else if (${ accessor } >= ${ JS_INT_MIN } && ${ accessor } <= ${ JS_INT_MAX }) {
                    //double, 64bit
                    size += 8;
                } else {
                    //long
                    size += 8;
                }
            } else {
                //double
                size += 8;
            }
        } else if (typeof ${ accessor } === 'bigint') {
            size += 8;
        }
        ` ; } else if ( property . type === 'string' ) { code = `
        if (typeof ${ accessor } === 'string') {
            size += getValueSize(${ accessor });
        }
        ` ; } else if ( property . type === 'literal' ) { code = `
        if (typeof ${ accessor } === 'string' || typeof ${ accessor } === 'number' || typeof ${ accessor } === 'boolean') {
            size += getValueSize(${ accessor });
        } else if (!${ property . isOptional } && !${ property . isOptional }) {
            size += getValueSize(${ JSON . stringify ( property . literalValue ) });
        }
        ` ; } else if ( property . type === 'boolean' ) { code = `
        if (typeof ${ accessor } === 'boolean') {
            size += 1;
        }
        ` ; } else if ( property . type === 'map' ) { compiler . context . set ( 'stringByteLength' , stringByteLength ) ; const i = compiler . reserveName ( 'i' ) ; code = `
        size += 4; //object size
        for (let ${ i } in ${ accessor }) {
            if (!${ accessor }.hasOwnProperty(${ i })) continue;
            size += 1; //element type
            size += stringByteLength(${ i }) + 1; //element name + null;
            ${ getPropertySizer ( schema , compiler , property . getSubType ( ) , `${ accessor }[${ i }]` , jitStack ) }
        }
        size += 1; //null
        ` ; } else if ( property . type === 'class' ) { const forwardSchema = property . getResolvedClassSchema ( ) ; const sizerFn = jitStack . getOrCreate ( forwardSchema , ( ) => createBSONSizer ( property . getResolvedClassSchema ( ) , jitStack ) ) ; const sizer = compiler . reserveVariable ( '_sizer' + property . name , sizerFn ) ; const unpopulatedSymbolVar = compiler . reserveVariable ( 'unpopulatedSymbol' , unpopulatedSymbol ) ; compiler . context . set ( 'isObject' , isObject ) ; compiler . context . set ( 'UUIDSymbol' , UUIDSymbol ) ; compiler . context . set ( 'ObjectIdSymbol' , ObjectIdSymbol ) ; let primarKeyHandling = '' ; const isReferenceCheck = property . isReference || ( property . parent && property . parent . isReference ) ; if ( isReferenceCheck ) { primarKeyHandling = getPropertySizer ( schema , compiler , forwardSchema . getPrimaryField ( ) , accessor , jitStack ) ; } let circularCheck = 'true' ; if ( schema . hasCircularReference ( ) ) { circularCheck = `!_stack.includes(${ accessor })` ; } code = `
            if (${ accessor } !== ${ unpopulatedSymbolVar }) {
                if (isObject(${ accessor }) && !${ accessor }.hasOwnProperty(UUIDSymbol) && !${ accessor }.hasOwnProperty(ObjectIdSymbol) && ${ circularCheck }) {
                    size += ${ sizer }.fn(${ accessor }, _stack);
                } else if (${ isReferenceCheck })  {
                    ${ primarKeyHandling }
                }
            }
        ` ; } else if ( property . type === 'date' ) { code = `if (${ accessor } instanceof Date) size += 8;` ; } else if ( property . type === 'objectId' ) { compiler . context . set ( 'isObjectId' , isObjectId ) ; code = `if ('string' === typeof ${ accessor }|| isObjectId(${ accessor })) size += 12;` ; } else if ( property . type === 'uuid' ) { compiler . context . set ( 'isUUID' , isUUID ) ; code = `if ('string' === typeof ${ accessor } || isUUID(${ accessor })) size += 4 + 1 + 16;` ; } else if ( property . type === 'arrayBuffer' || property . isTypedArray ) { code = `
            size += 4; //size
            size += 1; //sub type
            if (${ accessor }['_bsontype'] === 'Binary') {
                size += ${ accessor }.buffer.byteLength
            } else {
                size += ${ accessor }.byteLength;
            }
        ` ; } else if ( property . type === 'union' ) { let discriminator = [ `if (false) {\n}` ] ; const discriminants = [ ] ; for ( const unionType of getSortedUnionTypes ( property , jsonTypeGuards ) ) { discriminants . push ( unionType . property . type ) ; } const elseBranch = `throw new Error('No valid discriminant was found for ${ property . name }, so could not determine class type. Guard tried: [${ discriminants . join ( ',' ) }]. Got: ' + ${ accessor });` ; for ( const unionType of getSortedUnionTypes ( property , jsonTypeGuards ) ) { const guardVar = compiler . reserveVariable ( 'guard_' + unionType . property . type , unionType . guard ) ; discriminator . push ( `
                //guard:${ unionType . property . type }
                else if (${ guardVar }(${ accessor })) {
                    ${ getPropertySizer ( schema , compiler , unionType . property , `${ accessor }` , jitStack ) }
                }
            ` ) ; } code = `
            ${ discriminator . join ( '\n' ) }
            else {
                ${ elseBranch }
            }
        ` ; } let writeDefaultValue = `
        // size += 0; //null
    ` ; if ( ! property . hasDefaultValue && property . defaultValue !== undefined ) { const propertyVar = compiler . reserveVariable ( 'property' , property ) ; const cloned = property . clone ( ) ; cloned . defaultValue = undefined ; writeDefaultValue = `
            ${ propertyVar }.lastGeneratedDefaultValue = ${ propertyVar }.defaultValue();
            ${ getPropertySizer ( schema , compiler , cloned , `${ propertyVar }.lastGeneratedDefaultValue` , jitStack ) }
        ` ; } else if ( ! property . isOptional && property . type === 'literal' ) { writeDefaultValue = `size += getValueSize(${ JSON . stringify ( property . literalValue ) });` ; } return `
    if (${ accessor } === undefined || ${ accessor } === unpopulatedSymbol) {
        ${ writeDefaultValue }
    } else if (${ accessor } === null) {
        if (${ property . isNullable }) {
            // size += 0; //null
        } else {
            ${ writeDefaultValue }
        }
    } else {
        ${ code }
    }
    ` ; } /**
 * Creates a JIT compiled function that allows to get the BSON buffer size of a certain object.
 */ export function createBSONSizer ( schema , jitStack = new JitStack ( ) ) { const compiler = new CompilerContext ; let getSizeCode = [ ] ; const prepared = jitStack . prepare ( schema ) ; for ( const property of schema . getProperties ( ) ) { let setDefault = '' ; if ( property . hasManualDefaultValue ( ) || property . type === 'literal' ) { if ( property . defaultValue !== undefined ) { const propertyVar = compiler . reserveVariable ( 'property' , property ) ; setDefault = `
                    size += 1; //type
                    size += ${ property . name . length } + 1; //property name
                    ${ propertyVar }.lastGeneratedDefaultValue = ${ propertyVar }.defaultValue();
                    ${ getPropertySizer ( schema , compiler , property , `${ propertyVar }.lastGeneratedDefaultValue` , jitStack ) }
                ` ; } else if ( property . type === 'literal' && ! property . isOptional ) { setDefault = `
                size += 1; //type
                size += ${ property . name . length } + 1; //property name
                ${ getPropertySizer ( schema , compiler , property , JSON . stringify ( property . literalValue ) , jitStack ) }` ; } } else if ( property . isNullable ) { setDefault = `
                size += 1; //type null
                size += ${ property . name . length } + 1; //property name
            ` ; } getSizeCode . push ( `
            //${ property . name }
            if (${ JSON . stringify ( property . name ) } in obj) {
                size += 1; //type
                size += ${ property . name . length } + 1; //property name
                ${ getPropertySizer ( schema , compiler , property , `obj.${ property . name }` , jitStack ) }
            } else {
                ${ setDefault }
            }
        ` ) ; } compiler . context . set ( '_global' , getGlobalStore ( ) ) ; compiler . context . set ( 'unpopulatedSymbol' , unpopulatedSymbol ) ; compiler . context . set ( 'UnpopulatedCheck' , UnpopulatedCheck ) ; compiler . context . set ( 'seekElementSize' , seekElementSize ) ; let circularCheckBeginning = '' ; let circularCheckEnd = '' ; if ( schema . hasCircularReference ( ) ) { circularCheckBeginning = `
        if (!_stack) _stack = [];
        _stack.push(obj);
        ` ; circularCheckEnd = `_stack.pop();` ; } const functionCode = `
        ${ circularCheckBeginning }
        let size = 4; //object size

        const unpopulatedCheck = _global.unpopulatedCheck;
        _global.unpopulatedCheck = UnpopulatedCheck.ReturnSymbol;

        ${ getSizeCode . join ( '\n' ) }

        size += 1; //null

        _global.unpopulatedCheck = unpopulatedCheck;

        ${ circularCheckEnd }
        return size;
    ` ; try { const fn = compiler . build ( functionCode , 'obj' , '_stack' ) ; prepared ( fn ) ; return fn ; } catch ( error ) { console . log ( 'Error compiling BSON sizer' , functionCode ) ; throw error ; } } export class Writer { public dataView : DataView ; constructor ( public buffer , public offset = 0 ) { this . dataView = new DataView ( buffer . buffer , buffer . byteOffset , buffer . byteLength ) ; } writeUint32 ( v ) { this . dataView . setUint32 ( this . offset , v , true ) ; this . offset += 4 ; } writeInt32 ( v ) { this . dataView . setInt32 ( this . offset , v , true ) ; this . offset += 4 ; } writeDouble ( v ) { this . dataView . setFloat64 ( this . offset , v , true ) ; this . offset += 8 ; } writeDelayedSize ( v , position ) { this . dataView . setUint32 ( position , v , true ) ; } writeByte ( v ) { this . buffer [ this . offset ++ ] = v ; } writeBuffer ( buffer , offset = 0 ) { for ( let i = offset ; i < buffer . byteLength ; i ++ ) { this . buffer [ this . offset ++ ] = buffer [ i ] ; } } writeNull ( ) { this . writeByte ( 0 ) ; } writeAsciiString ( str ) { for ( let i = 0 ; i < str . length ; i ++ ) { this . buffer [ this . offset ++ ] = str . charCodeAt ( i ) ; } } writeString ( str ) { if ( ! str ) return ; if ( typeof str !== 'string' ) return ; for ( let i = 0 ; i < str . length ; i ++ ) { const c = str . charCodeAt ( i ) ; if ( c < 128 ) { this . buffer [ this . offset ++ ] = c ; } else if ( c > 127 && c < 2048 ) { this . buffer [ this . offset ++ ] = ( c >> 6 ) | 192 ; this . buffer [ this . offset ++ ] = ( ( c & 63 ) | 128 ) ; } else { this . buffer [ this . offset ++ ] = ( c >> 12 ) | 224 ; this . buffer [ this . offset ++ ] = ( ( c >> 6 ) & 63 ) | 128 ; this . buffer [ this . offset ++ ] = ( c & 63 ) | 128 ; } } } getBigIntBSONType ( value ) { if ( BSON_INT32_MIN <= value && value <= BSON_INT32_MAX ) { return BSONType . INT ; } else if ( LONG_MIN <= value && value <= LONG_MAX ) { return BSONType . LONG ; } else { return BSONType . BINARY ; } } writeBigIntLong ( value ) { if ( value < 0 ) { this . writeInt32 ( ~ Number ( - value % BigInt ( TWO_PWR_32_DBL_N ) ) + 1 | 0 ) ; this . writeInt32 ( ~ ( Number ( - value / BigInt ( TWO_PWR_32_DBL_N ) ) ) | 0 ) ; } else { this . writeInt32 ( Number ( value % BigInt ( TWO_PWR_32_DBL_N ) ) | 0 ) ; this . writeInt32 ( Number ( value / BigInt ( TWO_PWR_32_DBL_N ) ) | 0 ) ; } } writeBigIntBinary ( value ) { let hex = value . toString ( 16 ) ; let signum = hex === '0' ? 0 : 1 ; if ( hex [ 0 ] === '-' ) { signum = - 1 ; hex = hex . slice ( 1 ) ; } if ( hex . length % 2 ) hex = '0' + hex ; if ( signum === 0 ) { this . writeUint32 ( 1 ) ; this . writeByte ( BSON_BINARY_SUBTYPE_BIGINT ) ; this . buffer [ this . offset ++ ] = 0 ; return ; } let size = Math . ceil ( hex . length / 2 ) ; this . writeUint32 ( size + 1 ) ; this . writeByte ( BSON_BINARY_SUBTYPE_BIGINT ) ; this . buffer [ this . offset ++ ] = signum === 1 ? 1 : 255 ; for ( let i = 0 ; i < size ; i ++ ) { this . buffer [ this . offset ++ ] = hexToByte ( hex , i ) ; } } writeLong ( value ) { if ( value > 9223372036854775807 ) value = 9223372036854775807 ; if ( value < - 9223372036854775807 ) value = - 9223372036854775807 ; if ( value < 0 ) { this . writeInt32 ( ~ ( - value % TWO_PWR_32_DBL_N ) + 1 | 0 ) ; this . writeInt32 ( ~ ( - value / TWO_PWR_32_DBL_N ) | 0 ) ; } else { this . writeInt32 ( ( value % TWO_PWR_32_DBL_N ) | 0 ) ; this . writeInt32 ( ( value / TWO_PWR_32_DBL_N ) | 0 ) ; } } writeUUID ( value ) { value = value instanceof UUID ? value . id : value ; this . writeUint32 ( 16 ) ; this . writeByte ( BSON_BINARY_SUBTYPE_UUID ) ; this . buffer [ this . offset + 0 ] = uuidStringToByte ( value , 0 ) ; this . buffer [ this . offset + 1 ] = uuidStringToByte ( value , 1 ) ; this . buffer [ this . offset + 2 ] = uuidStringToByte ( value , 2 ) ; this . buffer [ this . offset + 3 ] = uuidStringToByte ( value , 3 ) ; this . buffer [ this . offset + 4 ] = uuidStringToByte ( value , 4 ) ; this . buffer [ this . offset + 5 ] = uuidStringToByte ( value , 5 ) ; this . buffer [ this . offset + 6 ] = uuidStringToByte ( value , 6 ) ; this . buffer [ this . offset + 7 ] = uuidStringToByte ( value , 7 ) ; this . buffer [ this . offset + 8 ] = uuidStringToByte ( value , 8 ) ; this . buffer [ this . offset + 9 ] = uuidStringToByte ( value , 9 ) ; this . buffer [ this . offset + 10 ] = uuidStringToByte ( value , 10 ) ; this . buffer [ this . offset + 11 ] = uuidStringToByte ( value , 11 ) ; this . buffer [ this . offset + 12 ] = uuidStringToByte ( value , 12 ) ; this . buffer [ this . offset + 13 ] = uuidStringToByte ( value , 13 ) ; this . buffer [ this . offset + 14 ] = uuidStringToByte ( value , 14 ) ; this . buffer [ this . offset + 15 ] = uuidStringToByte ( value , 15 ) ; this . offset += 16 ; } writeObjectId ( value ) { value = 'string' === typeof value ? value : value . id ; this . buffer [ this . offset + 0 ] = hexToByte ( value , 0 ) ; this . buffer [ this . offset + 1 ] = hexToByte ( value , 1 ) ; this . buffer [ this . offset + 2 ] = hexToByte ( value , 2 ) ; this . buffer [ this . offset + 3 ] = hexToByte ( value , 3 ) ; this . buffer [ this . offset + 4 ] = hexToByte ( value , 4 ) ; this . buffer [ this . offset + 5 ] = hexToByte ( value , 5 ) ; this . buffer [ this . offset + 6 ] = hexToByte ( value , 6 ) ; this . buffer [ this . offset + 7 ] = hexToByte ( value , 7 ) ; this . buffer [ this . offset + 8 ] = hexToByte ( value , 8 ) ; this . buffer [ this . offset + 9 ] = hexToByte ( value , 9 ) ; this . buffer [ this . offset + 10 ] = hexToByte ( value , 10 ) ; this . buffer [ this . offset + 11 ] = hexToByte ( value , 11 ) ; this . offset += 12 ; } write ( value , nameWriter ? ) { if ( 'boolean' === typeof value ) { if ( nameWriter ) { this . writeByte ( BSONType . BOOLEAN ) ; nameWriter ( ) ; } this . writeByte ( value ? 1 : 0 ) ; } else if ( value instanceof RegExp ) { if ( nameWriter ) { this . writeByte ( BSONType . REGEXP ) ; nameWriter ( ) ; } this . writeString ( value . source ) ; this . writeNull ( ) ; if ( value . ignoreCase ) this . writeString ( 'i' ) ; if ( value . global ) this . writeString ( 's' ) ; if ( value . multiline ) this . writeString ( 'm' ) ; this . writeNull ( ) ; } else if ( 'string' === typeof value ) { if ( nameWriter ) { this . writeByte ( BSONType . STRING ) ; nameWriter ( ) ; } const start = this . offset ; this . offset += 4 ; this . writeString ( value ) ; this . writeByte ( 0 ) ; this . writeDelayedSize ( this . offset - start - 4 , start ) ; } else if ( 'number' === typeof value ) { if ( Math . floor ( value ) === value ) { if ( value >= BSON_INT32_MIN && value <= BSON_INT32_MAX ) { if ( nameWriter ) { this . writeByte ( BSONType . INT ) ; nameWriter ( ) ; } this . writeInt32 ( value ) ; } else if ( value >= JS_INT_MIN && value <= JS_INT_MAX ) { if ( nameWriter ) { this . writeByte ( BSONType . NUMBER ) ; nameWriter ( ) ; } this . writeDouble ( value ) ; } else { if ( nameWriter ) { this . writeByte ( BSONType . NUMBER ) ; nameWriter ( ) ; } this . writeDouble ( value ) ; } } else { if ( nameWriter ) { this . writeByte ( BSONType . NUMBER ) ; nameWriter ( ) ; } this . writeDouble ( value ) ; } } else if ( value instanceof Date ) { if ( nameWriter ) { this . writeByte ( BSONType . DATE ) ; nameWriter ( ) ; } this . writeLong ( value . valueOf ( ) ) ; } else if ( isUUID ( value ) ) { if ( nameWriter ) { this . writeByte ( BSONType . BINARY ) ; nameWriter ( ) ; } this . writeUUID ( value ) ; } else if ( 'bigint' === typeof value ) { if ( nameWriter ) { this . writeByte ( BSONType . BINARY ) ; nameWriter ( ) ; } this . writeBigIntBinary ( value ) ; } else if ( isObjectId ( value ) ) { if ( nameWriter ) { this . writeByte ( BSONType . OID ) ; nameWriter ( ) ; } this . writeObjectId ( value ) ; } else if ( value instanceof ArrayBuffer || ArrayBuffer . isView ( value ) ) { if ( nameWriter ) { this . writeByte ( BSONType . BINARY ) ; nameWriter ( ) ; } let view = value instanceof ArrayBuffer ? new Uint8Array ( value ) : new Uint8Array ( value . buffer , value . byteOffset , value . byteLength ) ; if ( ( value as any ) [ '_bsontype' ] === 'Binary' ) { view = ( value as any ) . buffer ; } this . writeUint32 ( value . byteLength ) ; this . writeByte ( BSON_BINARY_SUBTYPE_DEFAULT ) ; for ( let i = 0 ; i < value . byteLength ; i ++ ) { this . buffer [ this . offset ++ ] = view [ i ] ; } } else if ( isArray ( value ) ) { if ( nameWriter ) { this . writeByte ( BSONType . ARRAY ) ; nameWriter ( ) ; } const start = this . offset ; this . offset += 4 ; for ( let i = 0 ; i < value . length ; i ++ ) { this . write ( value [ i ] , ( ) => { this . writeAsciiString ( '' + i ) ; this . writeByte ( 0 ) ; } ) ; } this . writeNull ( ) ; this . writeDelayedSize ( this . offset - start , start ) ; } else if ( value === undefined ) { if ( nameWriter ) { this . writeByte ( BSONType . UNDEFINED ) ; nameWriter ( ) ; } } else if ( value === null ) { if ( nameWriter ) { this . writeByte ( BSONType . NULL ) ; nameWriter ( ) ; } } else if ( isObject ( value ) ) { if ( nameWriter ) { this . writeByte ( BSONType . OBJECT ) ; nameWriter ( ) ; } const start = this . offset ; this . offset += 4 ; for ( let i in value ) { if ( ! value . hasOwnProperty ( i ) ) continue ; this . write ( value [ i ] , ( ) => { this . writeString ( i ) ; this . writeByte ( 0 ) ; } ) ; } this . writeNull ( ) ; this . writeDelayedSize ( this . offset - start , start ) ; } else { if ( nameWriter ) { this . writeByte ( BSONType . UNDEFINED ) ; nameWriter ( ) ; } } } } function getNameWriterCode ( property ) { const nameSetter = [ ] ; for ( let i = 0 ; i < property . name . length ; i ++ ) { nameSetter . push ( `writer.buffer[writer.offset++] = ${ property . name . charCodeAt ( i ) };` ) ; } return `
        //write name: '${ property . name }'
        ${ nameSetter . join ( '\n' ) }
        writer.writeByte(0); //null
    ` ; } function getPropertySerializerCode ( schema , compiler , property , accessor , jitStack , nameAccessor ? ) { if ( property . isParentReference ) return '' ; let nameWriter = `
        writer.writeAsciiString(${ nameAccessor });
        writer.writeByte(0);
    ` ; if ( ! nameAccessor ) { nameWriter = getNameWriterCode ( property ) ; } if ( property . type === 'promise' ) { property = property . templateArgs [ 0 ] || new PropertySchema ( property . name ) ; } let undefinedWriter = `
    writer.writeByte(${ BSONType . UNDEFINED });
    ${ nameWriter }` ; let code = `writer.write(${ accessor }, () => {
        ${ nameWriter }
    });` ; if ( property . type === 'class' && property . getResolvedClassSchema ( ) . decorator ) { property = property . getResolvedClassSchema ( ) . getDecoratedPropertySchema ( ) ; accessor = `(${ accessor } && ${ accessor }.${ property . name })` ; } if ( property . type === 'class' ) { const propertySerializer = `_serializer_${ property . name }` ; const forwardSchema = property . getResolvedClassSchema ( ) ; const serializerFn = jitStack . getOrCreate ( property . getResolvedClassSchema ( ) , ( ) => createBSONSerialize ( property . getResolvedClassSchema ( ) , jitStack ) ) ; compiler . context . set ( propertySerializer , serializerFn ) ; const unpopulatedSymbolVar = compiler . reserveVariable ( 'unpopulatedSymbol' , unpopulatedSymbol ) ; compiler . context . set ( 'isObject' , isObject ) ; compiler . context . set ( 'UUIDSymbol' , UUIDSymbol ) ; compiler . context . set ( 'ObjectIdSymbol' , ObjectIdSymbol ) ; let primarKeyHandling = '' ; const isReference = property . isReference || ( property . parent && property . parent . isReference ) ; if ( isReference ) { primarKeyHandling = getPropertySerializerCode ( schema , compiler , forwardSchema . getPrimaryField ( ) , accessor , jitStack , nameAccessor || JSON . stringify ( property . name ) ) ; } let circularCheck = 'true' ; if ( schema . hasCircularReference ( ) ) { circularCheck = `!_stack.includes(${ accessor })` ; } code = `
        if (${ accessor } !== ${ unpopulatedSymbolVar }) {
            if (isObject(${ accessor }) && !${ accessor }.hasOwnProperty(UUIDSymbol) && !${ accessor }.hasOwnProperty(ObjectIdSymbol) && ${ circularCheck }) {
                writer.writeByte(${ BSONType . OBJECT });
                ${ nameWriter }
                ${ propertySerializer }.fn(${ accessor }, writer, _stack);
            } else if (${ isReference })  {
                ${ primarKeyHandling }
            } else {
                ${ undefinedWriter }
            }
        } else {
            ${ undefinedWriter }
        }
        ` ; } else if ( property . type === 'string' ) { code = `
        if (typeof ${ accessor } === 'string') {
            writer.writeByte(${ BSONType . STRING });
            ${ nameWriter }
            const start = writer.offset;
            writer.offset += 4; //size placeholder
            writer.writeString(${ accessor });
            writer.writeByte(0); //null
            writer.writeDelayedSize(writer.offset - start - 4, start);
        } else {
            ${ undefinedWriter }
        }
        ` ; } else if ( property . type === 'literal' ) { code = `
        if (typeof ${ accessor } === 'string' || typeof ${ accessor } === 'number' || typeof ${ accessor } === 'boolean') {
            ${ code }
        } else if (!${ property . isOptional } && !${ property . isOptional }) {
            writer.write(${ JSON . stringify ( property . literalValue ) }, () => {
                ${ nameWriter }
            });
        } else {
            ${ undefinedWriter }
        }
        ` ; } else if ( property . type === 'boolean' ) { code = `
        if (typeof ${ accessor } === 'boolean') {
            writer.writeByte(${ BSONType . BOOLEAN });
            ${ nameWriter }
            writer.writeByte(${ accessor } ? 1 : 0);
        } else {
            ${ undefinedWriter }
        }
        ` ; } else if ( property . type === 'date' ) { code = `
        if (${ accessor } instanceof Date) {
            writer.writeByte(${ BSONType . DATE });
            ${ nameWriter }
            if (!(${ accessor } instanceof Date)) {
                throw new Error(${ JSON . stringify ( accessor ) } + " not a Date object");
            }

            writer.writeLong(${ accessor }.valueOf());
        } else {
            ${ undefinedWriter }
        }
        ` ; } else if ( property . type === 'objectId' ) { compiler . context . set ( 'isObjectId' , isObjectId ) ; compiler . context . set ( 'hexToByte' , hexToByte ) ; code = `
            if ('string' === typeof ${ accessor } || isObjectId(${ accessor })) {
                writer.writeByte(${ BSONType . OID });
                ${ nameWriter }
                writer.writeObjectId(${ accessor });
            } else {
                ${ undefinedWriter }
            }
        ` ; } else if ( property . type === 'uuid' ) { compiler . context . set ( 'isUUID' , isUUID ) ; compiler . context . set ( 'UUID' , UUID ) ; code = `
        if ('string' === typeof ${ accessor } || isUUID(${ accessor })) {
            writer.writeByte(${ BSONType . BINARY });
            ${ nameWriter }
            writer.writeUUID(${ accessor });
        } else {
            ${ undefinedWriter }
        }
        ` ; } else if ( property . type === 'bigint' ) { code = `
            if ('bigint' === typeof ${ accessor }) {
                writer.writeByte(${ BSONType . BINARY });
                ${ nameWriter }
                writer.writeBigIntBinary(${ accessor });
            } else {
                ${ undefinedWriter }
            }
        ` ; } else if ( property . type === 'number' ) { code = `
            if ('bigint' === typeof ${ accessor }) {
                //long
                writer.writeByte(${ BSONType . LONG });
                ${ nameWriter }
                writer.writeBigIntLong(${ accessor });
            } else if ('number' === typeof ${ accessor }) {
                if (Math.floor(${ accessor }) === ${ accessor }) {
                    //it's an int
                    if (${ accessor } >= ${ BSON_INT32_MIN } && ${ accessor } <= ${ BSON_INT32_MAX }) {
                        //32bit
                        writer.writeByte(${ BSONType . INT });
                        ${ nameWriter }
                        writer.writeInt32(${ accessor });
                    } else if (${ accessor } >= ${ JS_INT_MIN } && ${ accessor } <= ${ JS_INT_MAX }) {
                        //double, 64bit
                        writer.writeByte(${ BSONType . NUMBER });
                        ${ nameWriter }
                        writer.writeDouble(${ accessor });
                    } else {
                        //long, but we serialize as Double, because deserialize will be BigInt
                        writer.writeByte(${ BSONType . NUMBER });
                        ${ nameWriter }
                        writer.writeDouble(${ accessor });
                    }
                } else {
                    //double, 64bit
                    writer.writeByte(${ BSONType . NUMBER });
                    ${ nameWriter }
                    writer.writeDouble(${ accessor });
                }
            } else {
                ${ undefinedWriter }
            }
        ` ; } else if ( property . type === 'array' ) { const i = compiler . reserveName ( 'i' ) ; const isArrayVar = compiler . reserveVariable ( 'isArray' , isArray ) ; const unpopulatedSymbolVar = compiler . reserveVariable ( 'unpopulatedSymbol' , unpopulatedSymbol ) ; code = `
        if (${ accessor } && ${ accessor } !== ${ unpopulatedSymbolVar } && ${ isArrayVar }(${ accessor })) {
            writer.writeByte(${ BSONType . ARRAY });
            ${ nameWriter }
            const start = writer.offset;
            writer.offset += 4; //size

            for (let ${ i } = 0; ${ i } < ${ accessor }.length; ${ i }++) {
                //${ property . getSubType ( ) . name } (${ property . getSubType ( ) . type })
                ${ getPropertySerializerCode ( schema , compiler , property . getSubType ( ) , `${ accessor }[${ i }]` , jitStack , `''+${ i }` ) }
            }
            writer.writeNull();
            writer.writeDelayedSize(writer.offset - start, start);
        } else {
            ${ undefinedWriter }
        }
        ` ; } else if ( property . type === 'map' ) { const i = compiler . reserveName ( 'i' ) ; code = `
            writer.writeByte(${ BSONType . OBJECT });
            ${ nameWriter }
            const start = writer.offset;
            writer.offset += 4; //size

            for (let ${ i } in ${ accessor }) {
                if (!${ accessor }.hasOwnProperty(${ i })) continue;
                //${ property . getSubType ( ) . name } (${ property . getSubType ( ) . type })
                ${ getPropertySerializerCode ( schema , compiler , property . getSubType ( ) , `${ accessor }[${ i }]` , jitStack , `${ i }` ) }
            }
            writer.writeNull();
            writer.writeDelayedSize(writer.offset - start, start);
        ` ; } else if ( property . type === 'union' ) { let discriminator = [ `if (false) {\n}` ] ; const discriminants = [ ] ; for ( const unionType of getSortedUnionTypes ( property , jsonTypeGuards ) ) { discriminants . push ( unionType . property . type ) ; } const elseBranch = `throw new Error('No valid discriminant was found for ${ property . name }, so could not determine class type. Guard tried: [${ discriminants . join ( ',' ) }]. Got: ' + ${ accessor });` ; for ( const unionType of getSortedUnionTypes ( property , jsonTypeGuards ) ) { const guardVar = compiler . reserveVariable ( 'guard_' + unionType . property . type , unionType . guard ) ; discriminator . push ( `
                //guard
                else if (${ guardVar }(${ accessor })) {
                    //${ unionType . property . name } (${ unionType . property . type })
                    ${ getPropertySerializerCode ( schema , compiler , unionType . property , `${ accessor }` , jitStack , nameAccessor || JSON . stringify ( property . name ) ) }
                }
            ` ) ; } code = `
            ${ discriminator . join ( '\n' ) }
            else {
                ${ elseBranch }
            }
        ` ; } let writeDefaultValue = `
        writer.writeByte(${ BSONType . NULL });
        ${ nameWriter }
    ` ; if ( ! property . hasDefaultValue && property . defaultValue !== undefined ) { const propertyVar = compiler . reserveVariable ( 'property' , property ) ; const cloned = property . clone ( ) ; cloned . defaultValue = undefined ; writeDefaultValue = getPropertySerializerCode ( schema , compiler , cloned , `${ propertyVar }.lastGeneratedDefaultValue` , jitStack ) ; } else if ( ! property . isOptional && property . type === 'literal' ) { writeDefaultValue = `writer.write(${ JSON . stringify ( property . literalValue ) }, () => {${ nameWriter }});` ; } return `
    if (${ accessor } === undefined || ${ accessor } === unpopulatedSymbol) {
        ${ writeDefaultValue }
    } else if (${ accessor } === null) {
        if (${ property . isNullable }) {
            writer.writeByte(${ BSONType . NULL });
            ${ nameWriter }
        } else {
            ${ writeDefaultValue }
        }
    } else {
        //serialization code
        ${ code }
    }
    ` ; } function createBSONSerialize ( schema , jitStack = new JitStack ( ) ) { const compiler = new CompilerContext ( ) ; const prepared = jitStack . prepare ( schema ) ; compiler . context . set ( '_global' , getGlobalStore ( ) ) ; compiler . context . set ( 'UnpopulatedCheck' , UnpopulatedCheck ) ; compiler . context . set ( 'unpopulatedSymbol' , unpopulatedSymbol ) ; compiler . context . set ( '_sizer' , getBSONSizer ( schema ) ) ; compiler . context . set ( 'Writer' , Writer ) ; compiler . context . set ( 'seekElementSize' , seekElementSize ) ; compiler . context . set ( 'createBuffer' , createBuffer ) ; compiler . context . set ( 'schema' , schema ) ; let functionCode = '' ; let getPropertyCode = [ ] ; for ( const property of schema . getProperties ( ) ) { let setDefault = '' ; if ( property . hasManualDefaultValue ( ) || property . type === 'literal' ) { if ( property . defaultValue !== undefined ) { const propertyVar = compiler . reserveVariable ( 'property' , property ) ; setDefault = getPropertySerializerCode ( schema , compiler , property , `${ propertyVar }.lastGeneratedDefaultValue` , jitStack ) ; } else if ( property . type === 'literal' && ! property . isOptional ) { setDefault = getPropertySerializerCode ( schema , compiler , property , JSON . stringify ( property . literalValue ) , jitStack ) ; } } else if ( property . isNullable ) { setDefault = getPropertySerializerCode ( schema , compiler , property , 'null' , jitStack ) ; } getPropertyCode . push ( `
            //${ property . name }:${ property . type }
            if (${ JSON . stringify ( property . name ) } in obj) {
                ${ getPropertySerializerCode ( schema , compiler , property , `obj.${ property . name }` , jitStack ) }
            } else {
                ${ setDefault }
            }
        ` ) ; } let circularCheckBeginning = '' ; let circularCheckEnd = '' ; if ( schema . hasCircularReference ( ) ) { circularCheckBeginning = `
        if (!_stack) _stack = [];
        _stack.push(obj);
        ` ; circularCheckEnd = `_stack.pop();` ; } functionCode = `
        ${ circularCheckBeginning }
        const size = _sizer(obj, _stack);
        writer = writer || new Writer(createBuffer(size));
        const started = writer.offset;
        writer.writeUint32(size);
        const unpopulatedCheck = _global.unpopulatedCheck;
        _global.unpopulatedCheck = UnpopulatedCheck.ReturnSymbol;

        ${ getPropertyCode . join ( '\n' ) }
        writer.writeNull();

        _global.unpopulatedCheck = unpopulatedCheck;
        if (size !== writer.offset - started) {
            console.error('Wrong size calculated. Calculated=' + size + ', but serializer wrote ' + (writer.offset - started) + ' bytes. Object: ', JSON.stringify(obj), Object.getOwnPropertyNames(obj), schema.toString());
            throw new Error('Wrong size calculated. Calculated=' + size + ', but serializer wrote ' + (writer.offset - started) + ' bytes');
        }

        ${ circularCheckEnd }
        return writer.buffer;
    ` ; const fn = compiler . build ( functionCode , 'obj' , 'writer' , '_stack' ) ; prepared ( fn ) ; return fn ; } export function serialize ( data ) { const size = getValueSize ( data ) ; const writer = new Writer ( createBuffer ( size ) ) ; writer . write ( data ) ; return writer . buffer ; } export type BSONSerializer = ( data : any , writer ? : Writer ) => Uint8Array ; export type BSONSizer = ( data : any ) => number ; /**
 * Serializes an schema instance to BSON.
 *
 * Note: The instances needs to be in the mongo format already since it does not resolve decorated properties.
 *       So call it with the result of classToMongo(Schema, item).
 */ export function getBSONSerializer ( schema ) { schema = getClassSchema ( schema ) ; const jit = schema . jit ; if ( jit . bsonSerializer ) return jit . bsonSerializer ; jit . bsonSerializer = createBSONSerialize ( schema ) ; toFastProperties ( jit ) ; return jit . bsonSerializer ; } export function getBSONSizer ( schema ) { schema = getClassSchema ( schema ) ; const jit = schema . jit ; if ( jit . bsonSizer ) return jit . bsonSizer ; jit . bsonSizer = createBSONSizer ( schema ) ; toFastProperties ( jit ) ; return jit . bsonSizer ; }