@fox-hound-tools/refine-library
Version:
Refine sdk library
400 lines • 13.5 kB
JavaScript
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