UNPKG

ra-core

Version:

Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React

591 lines (549 loc) 21.2 kB
import { CreateParams, CreateResult, DataProvider, DeleteManyParams, DeleteManyResult, DeleteParams, DeleteResult, GetListParams, GetListResult, GetManyParams, GetManyReferenceParams, GetManyReferenceResult, GetManyResult, GetOneParams, GetOneResult, RaRecord, UpdateManyParams, UpdateManyResult, UpdateParams, UpdateResult, } from '../types'; /** * Extend a dataProvider to execute callbacks before and after read and write calls. * * @param {DataProvider} dataProvider The dataProvider to wrap * @param {ResourceCallbacks[]} handlers An array of ResourceCallbacks * * @typedef {Object} ResourceCallbacks * @property {string} resource The resource name * @property {AfterCreate} [afterCreate] A callback executed after create * @property {AfterDelete} [afterDelete] A callback executed after delete * @property {AfterDeleteMany} [afterDeleteMany] A callback executed after deleteMany * @property {AfterGetList} [afterGetList] A callback executed after getList * @property {AfterGetMany} [afterGetMany] A callback executed after getMany * @property {AfterGetManyReference} [afterGetManyReference] A callback executed after getManyReference * @property {AfterGetOne} [afterGetOne] A callback executed after getOne * @property {AfterRead} [afterRead] A callback executed after read (getList, getMany, getManyReference, getOne) * @property {AfterSave} [afterSave] A callback executed after save (create, update, updateMany) * @property {AfterUpdate} [afterUpdate] A callback executed after update * @property {AfterUpdateMany} [afterUpdateMany] A callback executed after updateMany * @property {BeforeCreate} [beforeCreate] A callback executed before create * @property {BeforeDelete} [beforeDelete] A callback executed before delete * @property {BeforeDeleteMany} [beforeDeleteMany] A callback executed before deleteMany * @property {BeforeGetList} [beforeGetList] A callback executed before getList * @property {BeforeGetMany} [beforeGetMany] A callback executed before getMany * @property {BeforeGetManyReference} [beforeGetManyReference] A callback executed before getManyReference * @property {BeforeGetOne} [beforeGetOne] A callback executed before getOne * @property {BeforeSave} [beforeSave] A callback executed before save (create, update, updateMany) * @property {BeforeUpdate} [beforeUpdate] A callback executed before update * @property {BeforeUpdateMany} [beforeUpdateMany] A callback executed before updateMany * * Warnings: * - As queries issued in the callbacks are not done through react-query, * any change in the data will not be automatically reflected in the UI. * - The callbacks are not executed in a transaction. In case of error, * the backend may be left in an inconsistent state. * - When calling the API directly using fetch or another client, * the callbacks will not be executed, leaving the backend in a possibly inconsistent state. * - If a callback triggers the query it's listening to, this will lead to a infinite loop. * * @example * * const dataProvider = withLifecycleCallbacks( * jsonServerProvider("http://localhost:3000"), * [ * { * resource: "posts", * afterRead: async (data, dataProvider) => { * // rename field to the record * data.user_id = data.userId; * return data; * }, * // executed after create, update and updateMany * afterSave: async (record, dataProvider) => { * // update the author's nb_posts * const { total } = await dataProvider.getList("users", { * filter: { id: record.user_id }, * pagination: { page: 1, perPage: 1 }, * }); * await dataProvider.update("users", { * id: user.id, * data: { nb_posts: total }, * previousData: user, * }); * return record; * }, * beforeDelete: async (params, dataProvider) => { * // delete all comments linked to the post * const { data: comments } = await dataProvider.getManyReference( * "comments", * { * target: "post_id", * id: params.id, * } * ); * if (comments.length > 0) { * await dataProvider.deleteMany("comments", { * ids: comments.map((comment) => comment.id), * }); * } * // update the author's nb_posts * const { data: post } = await dataProvider.getOne("posts", { * id: params.id, * }); * const { total } = await dataProvider.getList("users", { * filter: { id: post.user_id }, * pagination: { page: 1, perPage: 1 }, * }); * await dataProvider.update("users", { * id: user.id, * data: { nb_posts: total - 1 }, * previousData: user, * }); * return params; * }, * }, * ] * ); */ export const withLifecycleCallbacks = ( dataProvider: DataProvider, handlers: ResourceCallbacks[] ): DataProvider => { return { ...dataProvider, getList: async function <RecordType extends RaRecord = any>( resource: string, params: GetListParams ) { let newParams = params; const beforeGetListHandlers = handlers.filter( h => h.resource === resource && h.beforeGetList ); for (let handler of beforeGetListHandlers) { newParams = await handler.beforeGetList( newParams, dataProvider ); } let result = await dataProvider.getList<RecordType>( resource, newParams ); const afterGetListHandlers = handlers.filter( h => h.resource === resource && h.afterGetList ); for (let handler of afterGetListHandlers) { result = await handler.afterGetList(result, dataProvider); } const afterReadHandlers = handlers.filter( h => h.resource === resource && h.afterRead ); for (let handler of afterReadHandlers) { result.data = await Promise.all( result.data.map(record => handler.afterRead(record, dataProvider) ) ); } return result; }, getOne: async function <RecordType extends RaRecord = any>( resource: string, params: GetOneParams<RecordType> ) { let newParams = params; const beforeGetOneHandlers = handlers.filter( h => h.resource === resource && h.beforeGetOne ); for (let handler of beforeGetOneHandlers) { newParams = await handler.beforeGetOne(newParams, dataProvider); } let result = await dataProvider.getOne<RecordType>( resource, newParams ); const afterGetOneHandlers = handlers.filter( h => h.resource === resource && h.afterGetOne ); for (let handler of afterGetOneHandlers) { result = await handler.afterGetOne(result, dataProvider); } const afterReadHandlers = handlers.filter( h => h.resource === resource && h.afterRead ); for (let handler of afterReadHandlers) { result.data = await handler.afterRead( result.data, dataProvider ); } return result; }, getMany: async function <RecordType extends RaRecord = any>( resource: string, params: GetManyParams ) { let newParams = params; const beforeGetManyHandlers = handlers.filter( h => h.resource === resource && h.beforeGetMany ); for (let handler of beforeGetManyHandlers) { newParams = await handler.beforeGetMany( newParams, dataProvider ); } let result = await dataProvider.getMany<RecordType>( resource, newParams ); const afterGetManyHandlers = handlers.filter( h => h.resource === resource && h.afterGetMany ); for (let handler of afterGetManyHandlers) { result = await handler.afterGetMany(result, dataProvider); } const afterReadHandlers = handlers.filter( h => h.resource === resource && h.afterRead ); for (let handler of afterReadHandlers) { result.data = await Promise.all( result.data.map(record => handler.afterRead(record, dataProvider) ) ); } return result; }, getManyReference: async function <RecordType extends RaRecord = any>( resource: string, params: GetManyReferenceParams ) { let newParams = params; const beforeGetManyReferenceHandlers = handlers.filter( h => h.resource === resource && h.beforeGetManyReference ); for (let handler of beforeGetManyReferenceHandlers) { newParams = await handler.beforeGetManyReference( newParams, dataProvider ); } let result = await dataProvider.getManyReference<RecordType>( resource, newParams ); const afterGetManyReferenceHandlers = handlers.filter( h => h.resource === resource && h.afterGetManyReference ); for (let handler of afterGetManyReferenceHandlers) { result = await handler.afterGetManyReference( result, dataProvider ); } const afterReadHandlers = handlers.filter( h => h.resource === resource && h.afterRead ); for (let handler of afterReadHandlers) { result.data = await Promise.all( result.data.map(record => handler.afterRead(record, dataProvider) ) ); } return result; }, update: async function <RecordType extends RaRecord = any>( resource: string, params: UpdateParams<RecordType> ) { let newParams = params; const beforeUpdateHandlers = handlers.filter( h => h.resource === resource && h.beforeUpdate ); for (let handler of beforeUpdateHandlers) { newParams = await handler.beforeUpdate(newParams, dataProvider); } const beforeSaveHandlers = handlers.filter( h => h.resource === resource && h.beforeSave ); for (let handler of beforeSaveHandlers) { newParams.data = await handler.beforeSave( newParams.data, dataProvider ); } let result = await dataProvider.update<RecordType>( resource, newParams ); const afterUpdateHandlers = handlers.filter( h => h.resource === resource && h.afterUpdate ); for (let handler of afterUpdateHandlers) { result = await handler.afterUpdate(result, dataProvider); } const afterSaveHandlers = handlers.filter( h => h.resource === resource && h.afterSave ); for (let handler of afterSaveHandlers) { result.data = await handler.afterSave( result.data, dataProvider ); } return result; }, create: async function <RecordType extends RaRecord = any>( resource: string, params: CreateParams<RecordType> ) { let newParams = params; const beforeCreateHandlers = handlers.filter( h => h.resource === resource && h.beforeCreate ); for (let handler of beforeCreateHandlers) { newParams = await handler.beforeCreate(newParams, dataProvider); } const beforeSaveHandlers = handlers.filter( h => h.resource === resource && h.beforeSave ); for (let handler of beforeSaveHandlers) { newParams.data = await handler.beforeSave( newParams.data, dataProvider ); } let result = await dataProvider.create<RecordType>( resource, newParams ); const afterCreateHandlers = handlers.filter( h => h.resource === resource && h.afterCreate ); for (let handler of afterCreateHandlers) { result = await handler.afterCreate(result, dataProvider); } const afterSaveHandlers = handlers.filter( h => h.resource === resource && h.afterSave ); for (let handler of afterSaveHandlers) { result.data = await handler.afterSave( result.data, dataProvider ); } return result; }, delete: async function <RecordType extends RaRecord = any>( resource: string, params: DeleteParams<RecordType> ) { let newParams = params; const beforeDeleteHandlers = handlers.filter( h => h.resource === resource && h.beforeDelete ); for (let handler of beforeDeleteHandlers) { newParams = await handler.beforeDelete(newParams, dataProvider); } let result = await dataProvider.delete<RecordType>( resource, newParams ); const afterDeleteHandlers = handlers.filter( h => h.resource === resource && h.afterDelete ); for (let handler of afterDeleteHandlers) { result = await handler.afterDelete(result, dataProvider); } return result; }, updateMany: async function <RecordType extends RaRecord = any>( resource: string, params: UpdateManyParams<RecordType> ) { let newParams = params; const beforeUpdateManyHandlers = handlers.filter( h => h.resource === resource && h.beforeUpdateMany ); for (let handler of beforeUpdateManyHandlers) { newParams = await handler.beforeUpdateMany( newParams, dataProvider ); } const beforeSaveHandlers = handlers.filter( h => h.resource === resource && h.beforeSave ); for (let handler of beforeSaveHandlers) { newParams.data = await handler.beforeSave( newParams.data, dataProvider ); } let result = await dataProvider.updateMany<RecordType>( resource, newParams ); const afterUpdateManyHandlers = handlers.filter( h => h.resource === resource && h.afterUpdateMany ); for (let handler of afterUpdateManyHandlers) { result = await handler.afterUpdateMany(result, dataProvider); } const afterSaveHandlers = handlers.filter( h => h.resource === resource && h.afterSave ); if (afterSaveHandlers.length > 0) { const { data: records } = await dataProvider.getMany(resource, { ids: result.data, }); for (let handler of afterSaveHandlers) { await Promise.all( records.map(record => handler.afterSave(record, dataProvider) ) ); } } return result; }, deleteMany: async function <RecordType extends RaRecord = any>( resource: string, params: DeleteManyParams<RecordType> ) { let newParams = params; const beforeDeleteManyHandlers = handlers.filter( h => h.resource === resource && h.beforeDeleteMany ); for (let handler of beforeDeleteManyHandlers) { newParams = await handler.beforeDeleteMany( newParams, dataProvider ); } let result = await dataProvider.deleteMany<RecordType>( resource, newParams ); const afterDeleteManyHandlers = handlers.filter( h => h.resource === resource && h.afterDeleteMany ); for (let handler of afterDeleteManyHandlers) { result = await handler.afterDeleteMany(result, dataProvider); } return result; }, }; }; export type ResourceCallbacks<T extends RaRecord = any> = { resource: string; afterCreate?: ( result: CreateResult<T>, dataProvider: DataProvider ) => Promise<CreateResult<T>>; afterDelete?: ( result: DeleteResult<T>, dataProvider: DataProvider ) => Promise<DeleteResult<T>>; afterDeleteMany?: ( result: DeleteManyResult<T>, dataProvider: DataProvider ) => Promise<DeleteManyResult<T>>; afterGetList?: ( result: GetListResult<T>, dataProvider: DataProvider ) => Promise<GetListResult<T>>; afterGetMany?: ( result: GetManyResult<T>, dataProvider: DataProvider ) => Promise<GetManyResult<T>>; afterGetManyReference?: ( result: GetManyReferenceResult<T>, dataProvider: DataProvider ) => Promise<GetManyReferenceResult<T>>; afterGetOne?: ( result: GetOneResult<T>, dataProvider: DataProvider ) => Promise<GetOneResult<T>>; afterUpdate?: ( result: UpdateResult<T>, dataProvider: DataProvider ) => Promise<UpdateResult<T>>; afterUpdateMany?: ( result: UpdateManyResult<T>, dataProvider: DataProvider ) => Promise<UpdateManyResult<T>>; beforeCreate?: ( params: CreateParams<T>, dataProvider: DataProvider ) => Promise<CreateParams<T>>; beforeDelete?: ( params: DeleteParams<T>, dataProvider: DataProvider ) => Promise<DeleteParams<T>>; beforeDeleteMany?: ( params: DeleteManyParams<T>, dataProvider: DataProvider ) => Promise<DeleteManyParams<T>>; beforeGetList?: ( params: GetListParams, dataProvider: DataProvider ) => Promise<GetListParams>; beforeGetMany?: ( params: GetManyParams, dataProvider: DataProvider ) => Promise<GetManyParams>; beforeGetManyReference?: ( params: GetManyReferenceParams, dataProvider: DataProvider ) => Promise<GetManyReferenceParams>; beforeGetOne?: ( params: GetOneParams<T>, dataProvider: DataProvider ) => Promise<GetOneParams<T>>; beforeUpdate?: ( params: UpdateParams<T>, dataProvider: DataProvider ) => Promise<UpdateParams<T>>; beforeUpdateMany?: ( params: UpdateManyParams<T>, dataProvider: DataProvider ) => Promise<UpdateManyParams<T>>; // The following hooks don't match a dataProvider method /** * Modify the data before it is sent to the dataProvider. * * Used in create, update, and updateMany * * Note: This callback doesn't modify the record itself, but the data argument * (which may be a diff, especially when called with updateMany). */ beforeSave?: (data: Partial<T>, dataProvider: DataProvider) => Promise<T>; /** * Update a record after it has been read from the dataProvider * * Used in getOne, getList, getMany, and getManyReference */ afterRead?: (record: T, dataProvider: DataProvider) => Promise<T>; /** * Use the record after it is returned by the dataProvider. * * Used in create, update, and updateMany */ afterSave?: (record: T, dataProvider: DataProvider) => Promise<T>; };