import { Id , State , AnyAction , DerivedAction , ActionTypes , AttachAction , DetachAction , Selectors , ActionCreators , DeleteAction , InvalidAction , EntityTreeNode , } from './interfaces' ; import { Cardinalities } from './enums' ; import { ModelSchemaReader } from './schema' ; export default class Derivator < S extends State > { actionTypes : ActionTypes ; actionCreators : ActionCreators < S > ; schema : ModelSchemaReader ; selectors : Selectors < S > ; constructor ( actionTypes , actionCreators , schema , selectors ) { this . actionTypes = actionTypes ; this . actionCreators = actionCreators ; this . schema = schema ; this . selectors = selectors ; } public deriveAction ( state , action ) { if ( action . type === this . actionTypes . DETACH ) { const detachAction = action as DetachAction ; const derivedActions = this . deriveDetachActions ( detachAction ) ; return { type : action . type , original : action , derived : derivedActions } as DerivedAction < DetachAction > ; } if ( action . type === this . actionTypes . ATTACH ) { const attachAction = action as AttachAction ; const derivedActions = this . deriveAttachActions ( state , attachAction ) ; return { type : action . type , original : action , derived : derivedActions } as DerivedAction < AttachAction > ; } if ( action . type === this . actionTypes . DELETE ) { const deleteAction = action as DeleteAction ; let derivedActions = [ ] ; if ( ! deleteAction . cascade ) { derivedActions = this . deriveDeleteActions ( state , deleteAction ) ; } if ( deleteAction . cascade ) { const cascadeNodes = this . selectors . getEntityTree ( state , { type : deleteAction . entityType , id : deleteAction . id , schema : deleteAction . cascade } ) ; const cascadeActions = [ ] ; cascadeNodes . forEach ( ( { id , type } ) => { const cascadeAction = this . actionCreators . delete ( type , id ) ; if ( cascadeAction . type === this . actionTypes . DELETE ) { const cascadeDeleteAction = cascadeAction as DeleteAction ; cascadeActions . push ( ... this . deriveDeleteActions ( state , cascadeDeleteAction ) ) ; } } ) ; derivedActions . push ( ... cascadeActions ) ; } return { type : action . type , original : action , derived : derivedActions } as DerivedAction < DeleteAction > ; } return action ; } private deriveDetachActions ( action ) { const { entityType , id , relation , detachableId } = action ; const schema = this . schema . type ( entityType ) ; const relationType = schema . resolveRelationType ( relation ) ; const reciprocalKey = schema . resolveRelationReciprocalKey ( relation ) ; if ( ! relationType || ! reciprocalKey ) { return [ action ] ; } const reciprocalAction = this . actionCreators . detach ( relationType , detachableId , reciprocalKey , id ) ; return [ action , reciprocalAction ] ; } private deriveAttachActions ( state , action ) { const { entityType , id , relation , attachableId } = action ; const schema = this . schema . type ( entityType ) ; const relationType = schema . resolveRelationType ( relation ) ; if ( ! relationType ) { return [ ] ; } const entity = this . selectors . getEntity ( state , { type : entityType , id } ) ; if ( ! entity ) { return [ ] ; } const attachableEntity = this . selectors . getEntity ( state , { type : relationType , id : attachableId } ) ; const reciprocalKey = schema . resolveRelationReciprocalKey ( relation ) ; if ( ! attachableEntity || ! reciprocalKey ) { return [ ] ; } const relAttachAction = this . actionCreators . attach ( relationType , attachableId , reciprocalKey , id , { index : action . reciprocalIndex , reciprocalIndex : action . index } ) ; const entityDetachments = this . detachOccupant ( state , entityType , id , relation ) ; const relEntityDetachments = this . detachOccupant ( state , relationType , attachableId , reciprocalKey ) ; return [ action , relAttachAction , ... entityDetachments , ... relEntityDetachments ] ; } private deriveDeleteActions ( state , action ) { const { entityType , id } = action ; const entitySchema = this . schema . type ( entityType ) ; if ( ! entitySchema ) { return [ ] ; } const attachedIdsByRelationKey = this . selectors . getAllAttachedIds ( state , { type : entityType , id } ) ; const detachActions = Object . entries < Id [ ] > ( attachedIdsByRelationKey ) . reduce ( ( allDetachActions , [ relationKey , attachedIds ] ) => { const relationType = entitySchema . resolveRelationType ( relationKey ) ; if ( ! relationType ) { return allDetachActions ; } const reciprocalKey = entitySchema . resolveRelationReciprocalKey ( relationKey ) ; if ( ! reciprocalKey ) { return allDetachActions ; } const detachActions = attachedIds . map ( attachedId => this . actionCreators . detach ( relationType , attachedId , reciprocalKey , id ) ) ; allDetachActions . push ( ... detachActions ) ; return allDetachActions ; } , [ ] as ( DetachAction | InvalidAction ) [ ] ) ; return [ action , ... detachActions ] ; } private detachOccupant ( state , entityType , id , relation ) { const schema = this . schema . type ( entityType ) ; const relationType = schema . resolveRelationType ( relation ) ; const relationReciprocalKey = schema . resolveRelationReciprocalKey ( relation ) ; const cardinality = schema . resolveRelationCardinality ( relation ) ; if ( ! relationType || ! relationReciprocalKey || cardinality === Cardinalities . MANY ) { return [ ] ; } let occupantId = this . selectors . getAttached < Id > ( state , { type : entityType , id , relation } ) ; if ( ! occupantId ) { return [ ] ; } return [ this . actionCreators . detach ( entityType , id , relationType , occupantId ) , this . actionCreators . detach ( relationType , occupantId , relationReciprocalKey , id ) , ] ; } }