import { Field , PolymorphicField , SerdeField } from "./EntityManager" ; import { EntityMetadata , getConstructorFromTaggedId , keyToNumber , keyToString , maybeResolveReferenceToId , } from "./index" ; export function hasSerde ( field ) { return ! ! field . serde ; } /**
 * The database/column serialization / deserialization details of a given field.
 *
 * Most implementations will have just a single column in `columns`, but some logical
 * domain fields can be mapped to multiple physical database columns, i.e. polymorphic
 * references.
 */ export interface FieldSerde { /** A single field might persist to multiple columns, i.e. polymorphic references. */ columns : Column [ ] ; /**
     * Accepts a database `row` and sets the field's value(s) into the `__orm.data`.
     *
     * Used in EntityManager.hydrate to set row value on the entity
     */ setOnEntity ( data , row ) : void ; } /** A specific physical column of a logical field. */ export interface Column { columnName : string ; dbType : string ; dbValue ( data ) : any ; mapToDb ( value ) : any ; isArray : boolean ; } export class PrimitiveSerde implements FieldSerde { isArray = false ; columns = [ this ] ; constructor ( private fieldName , public columnName , public dbType ) { } setOnEntity ( data , row ) { data [ this . fieldName ] = maybeNullToUndefined ( row [ this . columnName ] ) ; } dbValue ( data ) { return data [ this . fieldName ] ; } mapToDb ( value ) { return value ; } } /**
 * Maps `decimal(...)` database types to the JS `number`.
 *
 * Note that we assume the db values are within the range of the JS `number`;
 * we should eventually sanity check that.
 *
 * Also note that knex/pg accept `number`s as input, so we only need
 * to handle from-database -> to JS translation.
 */ export class DecimalToNumberSerde implements FieldSerde { dbType = "decimal" ; isArray = false ; columns = [ this ] ; constructor ( private fieldName , public columnName ) { } setOnEntity ( data , row ) { const value = maybeNullToUndefined ( row [ this . columnName ] ) ; data [ this . fieldName ] = value !== undefined ? Number ( value ) : value ; } dbValue ( data ) { return data [ this . fieldName ] ; } mapToDb ( value ) { return value ; } } /** Maps physical integer keys to logical string IDs "because GraphQL". */ export class KeySerde implements FieldSerde { isArray = false ; columns = [ this ] ; private meta : { tagName : string ; idType : "int" | "uuid" ; } ; constructor ( tagName , private fieldName , public columnName , public dbType ) { this . meta = { tagName , idType : dbType } ; } setOnEntity ( data , row ) { data [ this . fieldName ] = keyToString ( this . meta , row [ this . columnName ] ) ; } dbValue ( data ) { return keyToNumber ( this . meta , maybeResolveReferenceToId ( data [ this . fieldName ] ) ) ; } mapToDb ( value ) { return keyToNumber ( this . meta , maybeResolveReferenceToId ( value ) ) ; } } export class PolymorphicKeySerde implements FieldSerde { constructor ( private meta , private fieldName ) { } setOnEntity ( data , row ) { this . columns . filter ( ( column ) => ! ! row [ column . columnName ] ) . forEach ( ( column ) => { data [ this . fieldName ] ??= keyToString ( column . otherMetadata ( ) , row [ column . columnName ] ) ; } ) ; } get columns ( ) : Array < Column & { otherMetadata : ( ) => EntityMetadata < any > ; } > { const { fieldName } = this ; return this . field . components . map ( ( comp ) => ( { columnName : comp . columnName , dbType : "int" , isArray : false , otherMetadata : comp . otherMetadata , dbValue ( data ) { const id = maybeResolveReferenceToId ( data [ fieldName ] ) ; const cstr = id ? getConstructorFromTaggedId ( id ) : undefined ; return cstr === comp . otherMetadata ( ) . cstr ? keyToNumber ( comp . otherMetadata ( ) , id ) : undefined ; } , mapToDb ( value ) { return keyToNumber ( comp . otherMetadata ( ) , maybeResolveReferenceToId ( value ) ) ; } , } ) ) ; } get columnName ( ) : string { throw new Error ( "Unsupported" ) ; } private get field ( ) : PolymorphicField { return this . meta ( ) . fields [ this . fieldName ] as PolymorphicField ; } } export class EnumFieldSerde implements FieldSerde { dbType = "int" ; isArray = false ; columns = [ this ] ; constructor ( private fieldName , public columnName , private enumObject ) { } setOnEntity ( data , row ) { data [ this . fieldName ] = this . enumObject . findById ( row [ this . columnName ] ) ?. code ; } dbValue ( data ) { return this . enumObject . findByCode ( data [ this . fieldName ] ) ?. id ; } mapToDb ( value ) { return this . enumObject . findByCode ( value ) ?. id ; } } export class EnumArrayFieldSerde implements FieldSerde { dbType = "int[]" ; isArray = true ; columns = [ this ] ; constructor ( private fieldName , public columnName , private enumObject ) { } setOnEntity ( data , row ) { data [ this . fieldName ] = row [ this . columnName ] ?. map ( ( id ) => this . enumObject . findById ( id ) . code ) || [ ] ; } dbValue ( data ) { return data [ this . fieldName ] ?. map ( ( code ) => this . enumObject . getByCode ( code ) . id ) || [ ] ; } mapToDb ( value ) { return ! value ? [ ] : value . map ( ( code ) => this . enumObject . getByCode ( code ) . id ) ; } } function maybeNullToUndefined ( value ) { return value === null ? undefined : value ; } /** Similar to SimpleSerde, but applies the superstruct `assert` function when reading values from the db. */ export class SuperstructSerde implements FieldSerde { dbType = "jsonb" ; isArray = false ; columns = [ this ] ; private assert = require ( "superstruct" ) . assert ; constructor ( private fieldName , public columnName , private superstruct ) { } setOnEntity ( data , row ) { const value = maybeNullToUndefined ( row [ this . columnName ] ) ; if ( value ) { this . assert ( value , this . superstruct ) ; } data [ this . fieldName ] = value ; } dbValue ( data ) { return data [ this . fieldName ] ; } mapToDb ( value ) { return value ; } }