import Validatable from './Validatable' ; import Controller from '../control/Controller' ; import Router from '../control/Router' ; import Schema from '../Schema' ; import { mergePaths , parseEndpoint , invokeChain } from '../utility' ; const toController = ( target , props = { } ) => { return Object . assign ( target instanceof Controller ? target : new Controller ( target ) , props ) ; } ; const applyEndpoint = ( Class , pattern , target ) => { const custom = [ 'read' , 'write' ] ; const { method , path , flags } = parseEndpoint ( pattern , custom , Class . root ( ) ) ; if ( ! method || ! path ) { throw new Error ( `Invalid pattern '${ pattern }'.` ) ; } if ( custom . includes ( method ) ) { return toController ( target , { pattern : path , isRead : method === 'read' , isCacheable : ! flags . includes ( 'nocache' ) , } ) ; } const controller = toController ( target , { pattern : path , isRead : method === 'get' , isCacheable : ! flags . includes ( 'nocache' ) , } ) ; if ( ! Class . router ) { Class . router = new Router ( ) ; } Class . router . declare ( method , controller . pattern , controller . try ) ; return controller ; } ; const applyAuthorizer = ( Class , ... chain ) => { const target = chain . pop ( ) ; return toController ( target , { authorizer : async ( args ) => { const result = await invokeChain ( chain , args ) ; return Array . isArray ( result ) ? result [ 0 ] : result ; } , } ) ; } ; const applySchema = ( Class , from , target ) => { return toController ( target , { validator : from instanceof Schema || typeof from === 'function' ? from : new Schema ( from ) , } ) ; } ; const applyInstance = ( Class , from , target ) => { return toController ( target , { instance : from } ) ; } ; const applyUses = ( Class , paths , target ) => { const root = Class . root ( ) ; return toController ( target , { dependencies : paths . map ( ( path ) => mergePaths ( root , path ) ) , } ) ; } ; const applyAffects = ( Class , paths , target ) => { const root = Class . root ( ) ; return toController ( target , { dependents : paths . map ( ( path ) => mergePaths ( root , path ) ) , } ) ; } ; export interface ControllerOptions { endpoint : string ; authorizer : Array < Function > ; schema : Schema | object ; instance : Function ; uses : Array < string > ; affects : Array < string > ; } /** Represents a type which can be exposed by a Synapse API. Defines the functionality necessary to create {@linkcode Controller|Controllers} and add them to a static {@linkcode Controllable.router|router} property on the derived class. */ export default class Controllable extends Validatable { static router : Router ; /** _**(abstract)**_ Returns the _path_ from which all endpoints on the derived class originate. */ static root ( ) { throw new Error ( "Classes that extend Controllable must implement the 'root' method." ) ; } param options param method protected static controller ( options , method ) { const { endpoint , authorizer , schema , instance , uses , affects } = options ; const controller = new Controller ( method ) ; if ( endpoint ) { applyEndpoint ( this , endpoint , controller ) ; } if ( authorizer ) { applyAuthorizer ( this , ... ( Array . isArray ( authorizer ) ? authorizer : [ authorizer ] ) , controller ) ; } if ( schema ) { applySchema ( this , schema , controller ) ; } if ( instance ) { applyInstance ( this , instance , controller ) ; } if ( uses ) { applyUses ( this , uses , controller ) ; } if ( affects ) { applyAffects ( this , affects , controller ) ; } return controller ; } } category param endpoint param authorizers export const endpoint = ( value ) : Function => { return ( Class , methodName , descriptor ) => { if ( ! ( Class . prototype instanceof Controllable ) ) { throw new Error ( "The '@endpoint' decorator can only be used within 'Controllable' types." ) ; } const method = descriptor . value ; descriptor . value = applyEndpoint ( Class , value , method ) ; } ; } ; category param endpoint param authorizers export const authorizer = ( ... authorizers ) : Function => { return ( Class , methodName , descriptor ) => { if ( ! ( Class . prototype instanceof Controllable ) ) { throw new Error ( "The '@authorizer' decorator can only be used within 'Controllable' types." ) ; } const method = descriptor . value ; descriptor . value = applyAuthorizer ( Class , ... authorizers , method ) ; } ; } ; category param source export const schema = ( source ) : Function => { return ( Class , methodName , descriptor ) => { if ( ! ( Class . prototype instanceof Controllable ) ) { throw new Error ( "The '@schema' decorator can only be used within 'Controllable' types." ) ; } const method = descriptor . value ; descriptor . value = applySchema ( Class , source , method ) ; } ; } ; category param source export const instance = ( source ) : Function => { return ( Class , methodName , descriptor ) => { if ( ! ( Class . prototype instanceof Controllable ) ) { throw new Error ( "The '@instance' decorator can only be used within 'Controllable' types." ) ; } const method = descriptor . value ; descriptor . value = applyInstance ( Class , source , method ) ; } ; } ; category param paths export const uses = ( ... paths ) : Function => { return ( Class , methodName , descriptor ) => { if ( ! ( Class . prototype instanceof Controllable ) ) { throw new Error ( "The '@uses' decorator can only be used within 'Controllable' types." ) ; } const method = descriptor . value ; descriptor . value = applyUses ( Class , paths , method ) ; } ; } ; category param paths export const affects = ( ... paths ) : Function => { return ( Class , methodName , descriptor ) => { if ( ! ( Class . prototype instanceof Controllable ) ) { throw new Error ( "The '@affects' decorator can only be used within 'Controllable' types." ) ; } const method = descriptor . value ; descriptor . value = applyAffects ( Class , paths , method ) ; } ; } ;