UNPKG

@fox-hound-tools/refine-library

Version:

Refine sdk library

400 lines 13.5 kB
import camelcase from 'camelcase'; /** * Create SDK data provider */ export function createDataProvider(client, baseUrl = '') { /** * Get list */ async function getList({ resource, pagination, sorters, filters, meta, }) { const queries = {}; const { currentPage = 1, pageSize = 10, mode = 'server' } = pagination ?? {}; if (mode === 'server') { queries['page'] = currentPage; queries['pageSize'] = pageSize; } const orders = sorters?.map((sorter) => `${sorter.field}.${sorter.order}`); if (orders?.length) { queries['orders'] = orders; } const filtersQueryList = filters ?.filter((f) => f.operator !== 'and' && f.operator !== 'or') .map((filter) => { if (arrayOperators.includes(filter.operator)) { return `${filter.field}.${filter.operator}.(${filter.value})`; } return `${filter.field}.${filter.operator}.${filter.value}`; }); if (filtersQueryList?.length) { queries['filters'] = filtersQueryList; } const { version, query, headers, parentKeys } = parseMeta(meta); const func = getSdkFunction(client, 'get', version, resource, parentKeys); if (!func) { throw new Error(notFoundFunction('get', version, resource)); } try { const result = (await func({ headers, queryParameters: { ...queries, ...query, }, })); return { data: result.value, total: result.total ?? result.value.length, }; } catch (error) { return handleErrorResponse(error); } } /** * Get many */ async function getMany({ resource, ids, meta, }) { const { version, query, headers, idColumnName, parentKeys } = parseMeta(meta); const queries = {}; const func = getSdkFunction(client, 'get', version, resource, parentKeys); if (!func) { throw new Error(notFoundFunction('get', version, resource)); } if (ids.length) { queries['filters'] = [`${idColumnName}.in.(${ids.join(',')})`]; } try { const result = (await func({ headers, queryParameters: { ...queries, ...query, }, })); return { data: result.value, }; } catch (error) { return handleErrorResponse(error); } } /** * Get one */ async function getOne({ resource, id, meta, }) { const { version, query, headers, idColumnName, parentKeys } = parseMeta(meta); const func = getSdkFunction(client, 'get', version, resource, { [idColumnName]: id, ...parentKeys }); if (!func) { throw new Error(notFoundFunction('get', version, resource, idColumnName)); } try { const result = (await func({ headers, queryParameters: query })); return { data: result }; } catch (error) { return handleErrorResponse(error); } } /** * Create */ async function create({ resource, variables, meta, }) { const { version, query, headers, parentKeys } = parseMeta(meta); const func = getSdkFunction(client, 'post', version, resource, parentKeys); if (!func) { throw new Error(notFoundFunction('post', version, resource)); } try { const result = (await func(variables, { headers, queryParameters: query })); return { data: result }; } catch (error) { return handleErrorResponse(error); } } /** * Create many */ async function createMany({ resource, variables, meta, }) { const { version, query, headers, parentKeys } = parseMeta(meta); const func = getSdkFunction(client, 'put', version, resource, parentKeys); if (!func) { throw new Error(notFoundFunction('put', version, resource)); } try { const result = (await func({ value: variables }, { headers, queryParameters: query })); return { data: result.value }; } catch (error) { return handleErrorResponse(error); } } /** * Update */ async function update({ resource, variables, id, meta, }) { const { version, query, headers, idColumnName, parentKeys } = parseMeta(meta); const func = getSdkFunction(client, 'patch', version, resource, { [idColumnName]: id, ...parentKeys }); if (!func) { throw new Error(notFoundFunction('patch', version, resource, idColumnName)); } try { const result = (await func(variables, { headers, queryParameters: query })); return { data: result }; } catch (error) { return handleErrorResponse(error); } } /** * Update many * * MEMO: この関数は通常の想定されている実装と反転されている。 * 通常は、複数のレコードに対して一括で同一の更新を行うが、 * この関数では、複数のレコードを特定して、それぞれのレコードを更新する方式を取っている。 * そのため、idsが空以外の場合はエラーを返す。 */ async function updateMany({ resource, meta, variables, ids, }) { if (ids.length !== 0) { throw new Error('Update many does not support filtering by ids'); } if (!Array.isArray(variables)) { throw new Error('Update many does not support updating single record'); } const { version, query, headers, parentKeys } = parseMeta(meta); const func = getSdkFunction(client, 'patch', version, resource, parentKeys); if (!func) { throw new Error(notFoundFunction('patch', version, resource)); } try { const result = (await func({ value: variables }, { headers, queryParameters: query })); return { data: result.value }; } catch (error) { return handleErrorResponse(error); } } /** * Delete */ async function deleteOne({ resource, id, meta, }) { const { version, query, headers, idColumnName, parentKeys } = parseMeta(meta); const func = getSdkFunction(client, 'delete', version, resource, { [idColumnName]: id, ...parentKeys }); if (!func) { throw new Error(notFoundFunction('delete', version, resource, idColumnName)); } try { const result = (await func({ headers, queryParameters: query })); return { data: result }; } catch (error) { return handleErrorResponse(error); } } /** * Delete many */ async function deleteMany({ resource, meta, ids, variables, }) { const { version, query, headers, parentKeys } = parseMeta(meta); const func = getSdkFunction(client, 'delete', version, resource, parentKeys); if (!func) { throw new Error(notFoundFunction('delete', version, resource)); } try { const values = variables ?? {}; const result = (await func({ ids, ...values }, { headers, queryParameters: query })); return { data: result.value }; } catch (error) { return handleErrorResponse(error); } } /** * Custom */ async function custom({ url, method, sorters, filters, payload, query, headers, meta, }) { // methodがheadとoptionsの場合はエラーを返す if (method === 'head' || method === 'options') { throw new Error('Method not allowed'); } // urlからベースURLを除いてリソースを取得する const resource = url.replace(baseUrl, ''); const queries = {}; const orders = sorters?.map((sorter) => `${sorter.field}.${sorter.order}`); if (orders?.length) { queries['orders'] = orders; } const filtersQueryList = filters ?.filter((f) => f.operator !== 'and' && f.operator !== 'or') .map((filter) => { if (arrayOperators.includes(filter.operator)) { return `${filter.field}.${filter.operator}.(${filter.value})`; } return `${filter.field}.${filter.operator}.${filter.value}`; }); if (filtersQueryList?.length) { queries['filters'] = filtersQueryList; } const { version, query: metaQuery, headers: metaHeaders, parentKeys } = parseMeta(meta); const func = getSdkFunction(client, method, version, resource, { ...parentKeys }); if (!func) { throw new Error(notFoundFunction(method, version, resource)); } const requestConfiguration = { headers: { ...headers, ...metaHeaders, }, queryParameters: { ...query, ...queries, ...metaQuery, }, }; try { const result = (payload ? await func(payload, requestConfiguration) : await func(requestConfiguration)); return { data: result }; } catch (error) { return handleErrorResponse(error); } } return { getList, getMany, getOne, create, createMany, updateMany, update, deleteOne, deleteMany, getApiUrl() { return baseUrl; }, custom, }; } /** * SDKに定義されているか確認 */ function getSdkFunction( /** * クライアント */ client, /** * メソッド */ method, /** * バージョン * * @example * v1など */ version, /** * リソース * * @example * user,blogなど */ resource, /** * パラメータ * * @example * {id: 1}など */ parameters) { const keys = [version, ...resource.split('/')].map((str) => camelcase(str)); for (const p of Object.keys(parameters)) { // pと同じものがあれば、入れ替える const index = keys.findIndex((k) => typeof k === 'string' && (k === p || k === `{${p}}`)); if (index !== -1) { keys[index] = { [camelcase(`by_${p}`)]: parameters[p] }; continue; } keys.push({ [camelcase(`by_${p}`)]: parameters[p] }); } const obj = getObjectFunction(client, keys); if (obj) { return obj[method]; } } /** * オブジェクトに対して、キーの関数を取得する * keyがオブジェクトの場合はvalueを引数とする */ function getObjectFunction(obj, keys) { if (keys.length === 0) { return obj; } const [first, ...rest] = keys; const funcOrObj = typeof first === 'string' ? getFunctionOrObject(obj, first) : getFunctionOrObject(obj, Object.keys(first)[0]); if (funcOrObj) { const ret = getObjectFunction(typeof funcOrObj === 'function' ? funcOrObj(...Object.values(first)) : funcOrObj, rest); return ret; } return undefined; } /** * オブジェクトにキーの関数が存在するか確認 */ function getFunctionOrObject(obj, key) { if (typeof obj[key] !== 'undefined') { return obj[key]; } return undefined; } /** * Meta情報の取得 */ function parseMeta(meta) { const version = meta?.version ?? 'v1'; const query = meta?.query ?? {}; const headers = meta?.headers ?? {}; const idColumnName = meta?.idColumnName ?? 'id'; const parentKeys = meta?.parentKeys ?? {}; return { version, query, headers, idColumnName, parentKeys }; } /** * Not Found Function のエラーメッセージ */ function notFoundFunction(method, version, resource, idColumnName) { const parameter = idColumnName ? `/{${idColumnName}}` : ''; return `Function not found for ${method.toUpperCase()} ${version}/${resource}${parameter}`; } /** * 配列のオペレーター */ const arrayOperators = ['in', 'nin', 'ina', 'nina', 'between', 'nbetween']; const isMainError = (error) => { return (!!error && typeof error === 'object' && Object.prototype.hasOwnProperty.call(error, 'code') && Object.prototype.hasOwnProperty.call(error, 'message') && Object.prototype.hasOwnProperty.call(error, 'responseHeaders') && Object.prototype.hasOwnProperty.call(error, 'responseStatusCode')); }; const handleErrorResponse = (error) => { if (isMainError(error)) { const customError = { code: error.code, message: error.message, // @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html statusCode: error.code ?? 2, responseHeaders: error.responseHeaders, responseStatusCode: error.responseStatusCode, additionalData: error.additionalData, }; return Promise.reject(customError); } return Promise.reject({ message: 'Unknown error', statusCode: 2, }); }; //# sourceMappingURL=data-provider.js.map