import Controllable from './abstract/Controllable' ; import Collection from './Collection' ; import Schema from './Schema' ; import Field from './Field' ; import Id from './fields/Id' ; import { mergePaths } from './utility' ; const { PRV } = Field . Flags ; /** Abstract class representing a RESTful resource. As the {@linkcode Resource} class inherits from {@linkcode Controllable}, its derived classes can be exposed by a Synapse API. As it inherits from {@linkcode State}, its instances also represent valid request responses. */ export default class Resource extends Controllable { /** Returns the _path_ that uniquely locates an instance (i.e. the path to which a ```GET``` request would return the instance). By default, this is the {@linkcode Resource.root|root} path followed by the value on the instance corresponding to the first field on the derived class's schema that extends type {@linkcode Id} (e.g. '/user/123'); however, derived classes may override this behavior. */ path ( ) { const Class = < typeof Resource > this . constructor ; const { fields } = Class . schema ; const keys = Object . keys ( fields ) ; for ( let i = 0 ; i < keys . length ; ++ i ) { const key = keys [ i ] ; if ( fields [ key ] instanceof Id ) { return mergePaths ( Class . root ( ) , this [ key ] ) ; } } throw new Error ( `No field of type 'Id' found for class ${ Class . name }.` ) ; } render ( ) { const Class = this . constructor ; const { fields } = Class . schema ; const result = { } ; Object . keys ( fields ) . forEach ( ( key ) => { const field = fields [ key ] ; if ( ! field . hasFlag ( PRV ) ) { result [ key ] = this [ key ] ; } } ) ; return result ; } /** Returns an object containing all properties of the instance, excluding {@linkcode State} _metadata_. */ export ( ) { const result = { ... this } ; Object . keys ( result ) . forEach ( ( key ) => { if ( key [ 0 ] === '$' ) { delete result [ key ] ; } } ) ; return result ; } /** Returns the _path_ from which all endpoints on the derived class originate. */ static root ( ) { const Class = this ; const name = Class . name . split ( /(?=[A-Z])/ ) . join ( '_' ) . toLowerCase ( ) ; return `/${ name }` ; } param Classes static union ( ... Classes ) { const fields = [ ] ; Classes . reverse ( ) . forEach ( ( Class ) => { if ( Class . prototype instanceof Resource ) { fields . push ( Class . schema . fields ) ; } } ) ; const Class = < typeof Resource > this ; return new Schema ( Object . assign ( { } , ... fields , Class . schema . fields ) ) ; } param data static async restore < T extends typeof Resource > ( this , data ) { const Type = < typeof Resource > this ; const result = await Type . schema . validate ( data ) ; if ( ! result ) { console . log ( data , Type . schema . lastError ) ; throw new Error ( `Invalid properties for type '${ Type . name }'.` ) ; } const instance = new Type ( 200 ) ; Object . keys ( result ) . forEach ( ( key ) => { instance [ key ] = result [ key ] ; } ) ; instance . $dependencies . push ( instance . path ( ) ) ; return < InstanceType < T > > instance ; } param data return static async collection < T extends typeof Resource > ( this , data ) { const Type = < typeof Resource > this ; const pending = data . map ( ( obj ) => Type . restore ( obj ) ) ; return new Collection ( await Promise . all ( pending ) ) ; } param data static async create < T extends typeof Resource > ( this , data ) { const instance = await this . restore ( data ) ; instance . $status = 201 ; return instance ; } }