import { initialState } from "./constants" ; import errors from "./errors" ; import { DatabaseSchema , FieldSchema , ForeignKey , NormalizeContext , TableSchema , TableState } from "./types" ; import * as utils from "./utils" ; export default class SchemaNormalizer { schema : TableSchema ; db : DatabaseSchema ; constructor ( schema ) { this . schema = utils . ensureParamObject ( "schema" , schema , "db" ) ; this . db = schema . db ; } normalize ( data , context ) { if ( ! utils . isPlainObject ( data ) && ! Array . isArray ( data ) ) throw new Error ( errors . normalizeInvalidData ( ) ) ; utils . ensureParamObject ( "context" , context ) ; utils . ensureParamObject ( "context.output" , context . output ) ; context . currentSchema = this . schema ; if ( ! context . output [ this . schema . name ] ) context . output [ this . schema . name ] = initialState ( this . schema . name ) ; return utils . ensureArray ( data ) . map ( obj => { if ( ! utils . isPlainObject ( obj ) ) throw new Error ( errors . normalizeInvalidData ( ) ) ; let subject = obj ; const normalizer = this . db . getRecordNormalizer ( this . schema . name ) ; if ( normalizer ) subject = normalizer ( subject , context ) ; const pk = context . normalizePKs ? this . _normalizePrimaryKey ( subject ) : this . schema . getPrimaryKey ( subject ) ; if ( ! pk ) throw new Error ( errors . normalizePk ( this . schema . name ) ) ; const tbl = context . output [ this . schema . name ] ; if ( ! tbl . byId [ pk ] ) tbl . ids . push ( pk ) ; const record = tbl . byId [ pk ] = { ... subject } ; const fks = this . schema . getForeignKeys ( subject ) ; fks . forEach ( fk => { this . _normalizeForeignKey ( fk , record , context ) ; this . _indexForeignKey ( fk , tbl , pk ) ; } ) ; this . schema . relations . forEach ( rel => this . _normalizeRelations ( rel , record , pk , context ) ) ; return pk ; } ) ; } protected _normalizePrimaryKey ( record ) { const pk = this . schema . getPrimaryKey ( record ) ; if ( pk ) return pk ; const generator = this . db . getMissingPkHandler ( this . schema . name ) ; if ( ! generator ) return undefined ; const generatedPk = generator ( record , this . schema ) ; if ( generatedPk ) if ( this . schema . primaryKeys . length === 1 ) record [ this . schema . primaryKeys [ 0 ] . propName ] = generatedPk ; return generatedPk ; } protected _normalizeRelations ( rel , record , pk , context ) { if ( rel . relationName && record [ rel . relationName ] ) { const normalizedRels = this . schema . inferRelations ( record [ rel . relationName ] , rel , pk ) ; rel . table . normalize ( normalizedRels , context ) ; delete record [ rel . relationName ] ; } } protected _normalizeForeignKey ( fk , record , context ) { if ( typeof fk . value !== "object" ) return ; const fkPks = fk . refTable . normalize ( fk . value , context ) ; if ( fkPks . length > 1 ) throw new Error ( errors . normalizeInvalidFk ( this . schema . name , fk . name , fk . refTable . name ) ) ; record [ fk . name ] = fk . value = fkPks [ 0 ] ; } protected _indexForeignKey ( fk , tbl , pk ) { if ( ! utils . isValidID ( fk . value ) ) return ; const fkId = utils . asID ( fk . value ) ; const idx = tbl . indexes [ fk . name ] || ( tbl . indexes [ fk . name ] = { unique : fk . unique , values : { } } ) ; if ( ! idx . values [ fkId ] ) idx . values [ fkId ] = [ ] ; if ( idx . unique && idx . values . length ) throw new Error ( errors . normalizeFkViolation ( this . schema . name , fk . name ) ) ; idx . values [ fkId ] . push ( pk ) ; } }