/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint class-methods-use-this: 0 no-unused-vars: 0 */
/* eslint no-useless-constructor: 0 */
import BaseProperty from './base-property'
import BaseRecord, { ParamsType } from './base-record'
import Filter from '../utils/filter'
import ResourceDecorator from '../decorators/resource-decorator'
import NotImplementedError from '../utils/not-implemented-error'
import { ResourceOptions } from '../decorators/resource-options.interface'
import AdminBro from '../../admin-bro'
/**
* Representation of a ORM Resource in AdminBro. Visually resource is a list item in the sidebar.
* Each resource has many records and many properties.
*
* Analogy is REST resource.
*
* It is an __abstract class__ and all database adapters should implement extend it implement
* following methods:
*
* - (static) {@link BaseResource.isAdapterFor isAdapterFor()}
* - {@link BaseResource#databaseName databaseName()}
* - {@link BaseResource#name name()}
* - {@link BaseResource#id id()}
* - {@link BaseResource#properties properties()}
* - {@link BaseResource#property property()}
* - {@link BaseResource#count count()}
* - {@link BaseResource#find find()}
* - {@link BaseResource#findOne findOne()}
* - {@link BaseResource#create create()}
* - {@link BaseResource#update update()}
* - {@link BaseResource#delete delete()}
* @category Base
* @abstract
* @hideconstructor
*/
class BaseResource {
public _decorated: ResourceDecorator | null
/**
* Checks if given adapter supports resource provided by the user
*
* @param {any} rawResource resource provided in AdminBroOptions#resources array
* @return {Boolean} if given adapter supports this resource - returns true
* @abstract
*/
static isAdapterFor(rawResource): boolean {
throw new NotImplementedError('BaseResource.isAdapterFor')
}
/**
* Creates given resource based on the raw resource object
*
* @param {Object} resource
*/
constructor(resource: any) {
this._decorated = null
}
/**
* The name of the database to which resource belongs. When resource is
* a mongoose model it should be database name of the mongo database.
*
* Visually, by default, all resources are nested in sidebar under their database names.
* @return {String} database name
* @abstract
*/
databaseName(): string {
throw new NotImplementedError('BaseResource#databaseName')
}
/**
* Returns type of the database. It is used to compute sidebar icon for
* given resource. Default: 'database'
* @return {String}
*/
databaseType(): string {
return 'database'
}
/**
* Return name of the resource.
* It could be a table name in SQL database, or collection name in mongoDB.
*
* Visually it will be shown as the name of the resource in the UI.
* @return {String}
* @abstract
*/
name(): string {
throw new NotImplementedError('BaseResource#name')
}
/**
* Each resource has to have uniq id which will be put to an URL of AdminBro routes.
* For instance in {@link Router} path for the `new` form is `/resources/{resourceId}/new`
* @return {String} uniq resource id
* @abstract
*/
id(): string {
throw new NotImplementedError('BaseResource#id')
}
/**
* returns array of all properties which belongs to resource
* @return {BaseProperty[]}
* @abstract
*/
properties(): Array<BaseProperty> {
throw new NotImplementedError('BaseResource#properties')
}
/**
* returns property object for given field
* @param {String} path path/name of the property. Take a look at
* {@link BaseProperty} to learn more about
* property paths.
* @return {BaseProperty}
* @abstract
*/
property(path: string): BaseProperty | null {
throw new NotImplementedError('BaseResource#property')
}
/**
* Returns number of elements for given resource by including filters
* @param {Filter} filter represents what data should be included
* @return {Promise<Number>}
* @abstract
*/
async count(filter: Filter): Promise<number> {
throw new NotImplementedError('BaseResource#count')
}
/**
* Returns actual records for given resource
*
* @param {Filter} filters what data should be included
* @param {Object} options
* @param {Number} [options.limit] how many records should be taken
* @param {Number} [options.offset] offset
* @param {Object} [options.sort] sort
* @param {Number} [options.sort.sortBy] sortable field
* @param {Number} [options.sort.direction] either asc or desc
* @return {Promise<BaseRecord[]>} list of records
* @abstract
* @example
* // filters example
* {
* name: 'Tom',
* createdAt: { from: '2019-01-01', to: '2019-01-18' }
* }
*/
async find(filter: Filter, options: {
limit?: number;
offset?: number;
sort?: {
sortBy?: string;
direction?: 'asc' | 'desc';
};
}): Promise<Array<BaseRecord>> {
throw new NotImplementedError('BaseResource#find')
}
/**
* Populates records with references for given property.
*
* Example: Let say resource `Article` has property `user_id` and it is a reference
* to `User` resource. When you call this `User.populate([...articleRecords], userIdProperty)`
* it should populate `articleRecords` with corresponding users.
* So after that invoking `articleRecord.populated['user_id']` will return the user Record
*
* @param {Array<BaseRecord>} records all records which should be populated
* @param {BaseProperty} property property which is a reference to `this` Resource
*
* @return {Promise<Array<BaseRecord>>} populated records
*/
async populate(records: Array<BaseRecord>, property: BaseProperty): Promise<Array<BaseRecord>> {
throw new NotImplementedError('BaseResource#populate')
}
/**
* Finds one Record in the Resource by its id
*
* @param {String} id uniq id of the Resource Record
* @return {Promise<BaseRecord>} record
* @abstract
*/
async findOne(id: string): Promise<BaseRecord | null> {
throw new NotImplementedError('BaseResource#findOne')
}
/**
* Finds many records based on the resource ids
*
* @param {Array<string>} list of ids to find
*
* @return {Promise<Array<BaseRecord>>} records
*/
async findMany(ids: Array<string>): Promise<Array<BaseRecord>> {
throw new NotImplementedError('BaseResource#findMany')
}
/**
* Builds new Record of given Resource.
*
* Each Record is an representation of the resource item. Before it can be saved,
* it has to be instantiated.
*
* @param {Record<string, any>} params
* @return {BaseRecord}
*/
build(params: Record<string, any>): BaseRecord {
return new BaseRecord(params, this)
}
/**
* Creates new record
*
* @param {Record<string, any>} params
* @return {Promise<Object>} created record converted to raw Object which
* can be used to initiate new {@link BaseRecord} instance
* @throws {ValidationError} If there are validation errors it should be thrown
* @abstract
*/
async create(params: Record<string, any>): Promise<ParamsType> {
throw new NotImplementedError('BaseResource#create')
}
/**
* Updates an object
*
* @param {String} id uniq id of the Resource Record
* @param {Record<string, any>} params
* @return {Promise<Object>} created record converted to raw Object which
* can be used to initiate new {@link BaseRecord} instance
* @throws {ValidationError} If there are validation errors it should be thrown
* @abstract
*/
async update(id: string, params: Record<string, any>): Promise<ParamsType> {
throw new NotImplementedError('BaseResource#update')
}
/**
* Delete given record by id
*
* @param {String|Number} id id of the Record
* @throws {ValidationError} If there are validation errors it should be thrown
* @abstract
*/
async delete(id: string): Promise<void> {
throw new NotImplementedError('BaseResource#delete')
}
/**
* Assigns given decorator to the Resource. Than it will be available under
* resource.decorate() method
*
* @param {BaseDecorator} Decorator
* @param {AdminBro} admin current instance of AdminBro
* @param {ResourceOptions} [options]
*/
assignDecorator(admin: AdminBro, options: ResourceOptions = {}): void {
this._decorated = new ResourceDecorator({ resource: this, admin, options })
}
/**
* Gets decorator object for given resource
* @return {BaseDecorator | null}
*/
decorate(): ResourceDecorator {
if (!this._decorated) {
throw new Error('resource don\'t have assigned decorator yet')
}
return this._decorated
}
}
export default BaseResource
Source