@knxcloud/lowcode-data-source
Version:
284 lines (283 loc) • 8.71 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const vue = require("vue");
const lowcodeUtils = require("@knxcloud/lowcode-utils");
function isFormData(o) {
return lowcodeUtils.toString(o) === "[object FormData]";
}
function serializeParams(obj) {
const result = [];
const applyItem = (key, val) => {
if (val === null || val === void 0 || val === "") {
return;
}
if (typeof val === "object") {
result.push(`${key}=${encodeURIComponent(JSON.stringify(val))}`);
} else {
result.push(`${key}=${encodeURIComponent(String(val))}`);
}
};
if (isFormData(obj)) {
obj.forEach((val, key) => applyItem(key, val));
} else {
Object.keys(obj).forEach((key) => applyItem(key, obj[key]));
}
return result.join("&");
}
function buildUrl(dataAPI, params) {
if (!params)
return dataAPI;
const paramStr = serializeParams(params);
if (paramStr) {
return dataAPI.indexOf("?") > 0 ? `${dataAPI}&${paramStr}` : `${dataAPI}?${paramStr}`;
}
return dataAPI;
}
function find(o, k) {
for (const key in o) {
if (key.toLowerCase() === k) {
return [o[key], key];
}
}
return [];
}
function isValidResponseType(type) {
return lowcodeUtils.isString(type) && ["arrayBuffer", "blob", "formData", "json", "text"].includes(type);
}
function createFormData(data) {
const formData = new FormData();
for (const key in data) {
const value = data[key];
if (value instanceof Blob) {
formData.append(key, value);
} else {
formData.append(key, String(value));
}
}
return formData;
}
const bodyParseStrategies = {
"application/json": (data) => JSON.stringify(data),
"multipart/form-data": (data) => lowcodeUtils.isPlainObject(data) ? createFormData(data) : data,
"application/x-www-form-urlencoded": (data) => serializeParams(data)
};
function parseRequestBody(contentType, data) {
const parser = Object.keys(bodyParseStrategies).find(
(key) => contentType.includes(key)
);
return parser ? bodyParseStrategies[parser](data) : data;
}
class RequestError extends Error {
constructor(message, code, data) {
super(message);
this.code = code;
this.data = data;
}
}
class Response {
constructor(code, data) {
this.code = code;
this.data = data;
}
}
async function request(options) {
const {
uri,
method,
timeout,
params = {},
headers = {},
isCors,
responseType = "json",
...restOptions
} = options;
let url;
const requestHeaders = {
Accept: "application/json",
...headers
};
const fetchOptions = {
method,
headers: requestHeaders,
credentials: "include",
...restOptions
};
isCors && (fetchOptions.mode = "cors");
if (method === "GET" || method === "DELETE" || method === "OPTIONS") {
url = buildUrl(uri, params);
} else {
url = uri;
const [contentType, key] = find(requestHeaders, "content-type");
fetchOptions.body = parseRequestBody(contentType != null ? contentType : "application/json", params);
if (contentType === "multipart/form-data") {
key && delete requestHeaders[key];
}
}
if (timeout) {
const controller = new AbortController();
fetchOptions.signal = controller.signal;
setTimeout(() => controller.abort(), timeout);
}
const res = await fetch(url, fetchOptions);
const code = res.status;
if (code >= 200 && code < 300) {
if (code === 204) {
if (method === "DELETE") {
return new Response(code, null);
} else {
throw new RequestError(res.statusText, code);
}
} else {
if (!isValidResponseType(responseType)) {
throw new RequestError(`invalid response type: ${responseType}`, -1);
}
return new Response(code, await res[responseType]());
}
} else if (code >= 400) {
try {
const data = await res.json();
throw new RequestError(res.statusText, code, data);
} catch (e) {
throw new RequestError(res.statusText, code);
}
}
throw new RequestError(res.statusText, code);
}
function createDataSource(config, { state, setState }, requestHandlersMap) {
const data = vue.shallowRef();
const error = vue.shallowRef();
const status = vue.ref("init");
const loading = vue.computed(() => status.value === "loading");
const isInit = vue.computed(
() => config.isInit ? exec(config.isInit, state) : false
);
const isSync = vue.computed(
() => config.isSync ? exec(config.isSync, state) : false
);
const {
willFetch = same,
shouldFetch = alwaysTrue,
dataHandler = (res) => res && Reflect.get(res, "data"),
errorHandler = alwaysThrow
} = config;
const load = async (inputParams, otherOptions = {}) => {
try {
const { type, options, id } = config;
const request2 = getRequestHandler(config, requestHandlersMap);
if (!request2) {
throw new Error("unsupport fetch type: " + type);
}
if (!shouldFetch()) {
throw new Error(`the ${id} request should not fetch, please check the condition`);
}
const { inputHeaders = {}, assignToScope = true, ...inputOptions } = otherOptions;
status.value = "loading";
const { params, headers, ...restOptions } = exec(options, state);
const parsedOptions = await willFetch({
...restOptions,
...inputOptions,
params: lowcodeUtils.isPlainObject(params) && lowcodeUtils.isPlainObject(inputParams) ? {
...params,
...inputParams
} : inputParams != null ? inputParams : params,
headers: {
...lowcodeUtils.isPlainObject(headers) ? headers : {},
...lowcodeUtils.isPlainObject(inputHeaders) ? inputHeaders : {}
}
});
const res = await request2(parsedOptions, { state, setState });
const _data = data.value = dataHandler(res);
if (!lowcodeUtils.isUndefined(_data) && assignToScope) {
setState({ [id]: _data });
}
status.value = "loading";
return _data;
} catch (err) {
status.value = "error";
error.value = err;
errorHandler(err);
}
};
return vue.reactive({
data,
error,
loading,
status,
isInit,
isSync,
load
});
}
const same = (v) => v;
const alwaysTrue = () => true;
const alwaysThrow = (e) => {
throw e;
};
function exec(val, state) {
if (lowcodeUtils.isFunction(val)) {
return val.call(state, state);
} else if (lowcodeUtils.isPlainObject(val)) {
return Object.keys(val).reduce((res, next) => {
Reflect.set(res, next, exec(val[next], state));
return res;
}, {});
}
return val;
}
function getRequestHandler(config, requestHandlersMap) {
var _a, _b;
const { type, requestHandler } = config;
if (type) {
if (type === "custom" && requestHandler) {
return requestHandler;
} else {
return type === "fetch" ? (_a = requestHandlersMap[type]) != null ? _a : request : (_b = requestHandlersMap[type]) != null ? _b : null;
}
}
return request;
}
function createDataSourceEngine(config, context, requestHandlersMap = {}) {
const dataSource = {};
const dataSourceMap = {};
const { list, dataHandler } = config;
for (const config2 of list) {
const mergedConfig = { dataHandler, ...config2 };
const _dataSource = createDataSource(mergedConfig, context, requestHandlersMap);
const func = (params, otherOptions) => {
const mergedOptions = {
assignToScope: false,
...otherOptions
};
return _dataSource.load(params, mergedOptions);
};
dataSource[config2.id] = func;
dataSourceMap[config2.id] = _dataSource;
}
const reloadDataSource = (id, params, otherOptions) => {
if (id) {
const dataSource2 = dataSourceMap[id];
if (!dataSource2) {
throw new Error("dataSource not found, id: " + id);
}
return dataSource2.load(params, otherOptions);
}
const syncItems = [];
const asyncItems = [];
Object.keys(dataSourceMap).map((id2) => dataSourceMap[id2]).filter((ds) => ds.isInit).forEach((ds) => {
ds.isSync ? syncItems.push(ds) : asyncItems.push(ds);
});
const promises = [
...asyncItems.map((ds) => ds.load()),
syncItems.reduce(
(res, next) => res.then(() => next.load()),
Promise.resolve(null)
)
];
return Promise.all(promises);
};
const needInit = () => Object.keys(dataSourceMap).some((id) => dataSourceMap[id].isInit);
return { dataSource, dataSourceMap, reloadDataSource, shouldInit: needInit };
}
exports.createDataSourceEngine = createDataSourceEngine;
exports.fetchRequest = request;
//# sourceMappingURL=lowcode-data-source.js.map
;