UNPKG

@kovalson/prevue

Version:

A Vue 3 package based on Pinia that helps to manage resource models.

697 lines (682 loc) 21.4 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/Model.ts import { camelCase } from "lodash"; // src/utils/convertObjectKeys.ts function convertObjectKeys(object, convertFunction) { return Object.fromEntries( Object.entries(object).map((entry) => [ convertFunction(entry[0]), entry[1] ]) ); } // src/defineConfig.ts function defineConfig(options) { if (Object.isFrozen(apiConfig)) { throw new Error("You can configure Prevue only once."); } apiConfig.apiUrl = options.apiUrl; apiConfig.headers = options.headers; apiConfig.convertCase = options.convertCase; apiConfig.pagination = options.pagination; apiConfig.requestOptions = options.requestOptions; apiConfig = Object.freeze(apiConfig); } var apiConfig = {}; // src/Model.ts var Model = class { constructor() { this.$convertCase = false; this.$relations = { // }; } $update(props) { const newModel = new this.constructor(); const relatedModels = {}; if (this.$convertCase || apiConfig.convertCase) { props = convertObjectKeys(props, camelCase); } Object.keys(props).forEach((key) => { if (!(key in this) && !(key in this.$relations)) { delete props[key]; } }); Object.keys(props).filter((key) => key in this.$relations).forEach((key) => { const propsKey = key; const propsModelData = props[propsKey]; const RelatedModelClass = this.$relations[key]; if (Array.isArray(propsModelData)) { relatedModels[key] = []; propsModelData.forEach((propsModel) => { const _relatedModel = new RelatedModelClass(); const relatedModel = _relatedModel.$update(propsModel); relatedModels[key].push(relatedModel); }); } else if (typeof propsModelData === "object") { const _relatedModel = new RelatedModelClass(); relatedModels[key] = _relatedModel.$update(propsModelData); } }); Object.keys(relatedModels).forEach((key) => { if (key in props) { delete props[key]; } }); Object.assign(newModel, __spreadValues(__spreadValues({}, this), props)); Object.assign(newModel, relatedModels); return newModel; } }; Model.primaryKey = "id"; // src/defineApi.ts import { $fetch } from "ofetch"; // src/utils/defineApiUrl.ts function defineApiUrl(setup) { if (setup && setup.apiUrl) { if (typeof setup.apiUrl === "function") { return setup.apiUrl; } else { const apiUrl = setup.apiUrl; return () => apiUrl; } } return () => { var _a; return (_a = apiConfig.apiUrl) != null ? _a : ""; }; } // src/utils/getInferredUri.ts import { kebabCase } from "lodash"; import pluralize from "pluralize"; function getInferredUri(ModelClass) { const modelName = typeof ModelClass === "function" && ModelClass.prototype !== void 0 ? ModelClass.name : ModelClass.constructor.name; const kebabCaseName = kebabCase(modelName); const segments = kebabCaseName.split("-"); if (segments.length > 1) { const lastSegment = segments.pop(); const pluralized = pluralize(lastSegment); return segments.concat(pluralized).join("-"); } return pluralize(kebabCaseName); } // src/utils/defineUri.ts function defineUri(ModelClass, setup) { if (setup && setup.uri) { if (typeof setup.uri === "function") { return setup.uri; } else { const uri = setup.uri; return () => uri; } } return () => getInferredUri(ModelClass); } // src/ApiResponse.ts var ApiResponse = class { constructor(data, pagination) { this._apiResponse = true; this.data = data; this.pagination = pagination; } getData() { return this.data; } getPagination() { return this.pagination; } hasData() { return typeof this.data !== "undefined"; } isDataNull() { return this.data === null; } hasPagination() { return typeof this.pagination !== "undefined"; } }; var SimpleResponse = class extends ApiResponse { isPaginated() { return false; } }; var PaginatedResponse = class extends ApiResponse { isPaginated() { return true; } }; // src/utils/createInstance.ts function createInstance(ModelClass, data) { const instance = new ModelClass(); return typeof data !== "undefined" ? instance.$update(data) : instance; } // src/utils/deepMerge.ts function deepMerge(target, source) { for (const key in source) { if (source.hasOwnProperty(key)) { const targetKey = key; const sourceKey = key; if (target.hasOwnProperty(key) && typeof target[targetKey] === "object" && typeof source[sourceKey] === "object") { deepMerge(target[targetKey], source[sourceKey]); } else { target[targetKey] = source[sourceKey]; } } } } // src/defineApi.ts function getSetupConfig(ModelClass, setup) { const getUri = defineUri(ModelClass, setup); const getApiUrl = defineApiUrl(setup); function buildFullUrl(...segments) { var _a; const apiUrl = getApiUrl(); const uri = getUri(); const concatenated = [apiUrl, uri, ...segments].join("/"); const trimmed = ((_a = setup == null ? void 0 : setup.trimSlashes) != null ? _a : true) ? concatenated.replace(/([^:]\/)\/+/g, "$1") : concatenated; return trimmed.replace(/^\/*/gi, "").replace(/\/*$/gi, ""); } function processPaginatedResponse(response) { var _a, _b, _c, _d, _e; const paginationTemplate = (_a = setup == null ? void 0 : setup.pagination) != null ? _a : apiConfig.pagination; const dataWrapperKey = getPaginationDataKey(); const data = response[dataWrapperKey].map((data2) => createInstance(ModelClass, data2)); if (paginationTemplate) { if (paginationTemplate.mapper) { return { data, pagination: paginationTemplate.mapper(response) }; } const pagination = paginationTemplate.paginationWrapper ? response[paginationTemplate.paginationWrapper] : response.pagination; return { data, pagination: { currentPage: pagination[(_b = paginationTemplate.currentPage) != null ? _b : "currentPage"], lastPage: pagination[(_c = paginationTemplate.lastPage) != null ? _c : "lastPage"], perPage: pagination[(_d = paginationTemplate.perPage) != null ? _d : "perPage"], total: pagination[(_e = paginationTemplate.total) != null ? _e : "total"] } }; } return { data: response.data, pagination: response.pagination }; } function getPaginationDataKey() { var _a, _b, _c, _d; return (_d = (_c = (_a = setup == null ? void 0 : setup.pagination) == null ? void 0 : _a.dataWrapper) != null ? _c : (_b = apiConfig.pagination) == null ? void 0 : _b.dataWrapper) != null ? _d : "data"; } function getPaginationWrapperKey() { var _a, _b, _c, _d; return (_d = (_c = (_a = setup == null ? void 0 : setup.pagination) == null ? void 0 : _a.paginationWrapper) != null ? _c : (_b = apiConfig.pagination) == null ? void 0 : _b.paginationWrapper) != null ? _d : "pagination"; } function getApiOptions() { var _a; return (_a = setup == null ? void 0 : setup.options) != null ? _a : {}; } return { getUri, getApiUrl, buildFullUrl, processPaginatedResponse, getPaginationDataKey, getPaginationWrapperKey, getApiOptions }; } function getConfigHeaders() { if (apiConfig.headers) { return typeof apiConfig.headers === "function" ? apiConfig.headers() : apiConfig.headers; } return {}; } function buildOptions(...options) { var _a, _b; const mergedOptions = {}; deepMerge(mergedOptions, (_b = (_a = apiConfig) == null ? void 0 : _a.requestOptions) != null ? _b : {}); deepMerge(mergedOptions, { headers: __spreadValues({ "Accept": "application/json" }, getConfigHeaders()) }); options.forEach((options2) => { deepMerge(mergedOptions, options2); }); return mergedOptions; } function getBaseEndpoints(ModelClass, setupConfig) { function isPaginatedResponse(response) { const paginationDataKey = setupConfig.getPaginationDataKey(); const paginationWrapperKey = setupConfig.getPaginationWrapperKey(); return typeof response === "object" && response !== null && paginationWrapperKey in response && paginationDataKey in response; } class ResponseError extends Error { constructor(message) { super(setupConfig.buildFullUrl() + ": " + message); } } class InvalidResponseError extends ResponseError { constructor(response) { super(`Invalid response. Expected pagination object or array, got "${JSON.stringify(response)}".`); } } return { rawRequest(_0) { return __async(this, arguments, function* (url, options = {}) { return yield $fetch(setupConfig.buildFullUrl(url), buildOptions(setupConfig.getApiOptions(), options)); }); }, request(url, options) { return __async(this, null, function* () { const response = yield this.rawRequest(url, options); if (isPaginatedResponse(response)) { const processedResponse = setupConfig.processPaginatedResponse(response); return new PaginatedResponse( processedResponse.data.map((item) => createInstance(ModelClass, item)), processedResponse.pagination ); } else if (Array.isArray(response)) { return new SimpleResponse(response.map((item) => createInstance(ModelClass, item))); } throw new InvalidResponseError(response); }); }, fetchAll(options) { return __async(this, null, function* () { return yield this.request("", __spreadValues({ method: "GET" }, options)); }); }, fetchOne(id, options) { return __async(this, null, function* () { return yield this.request(id.toString(), __spreadValues({ method: "GET" }, options)); }); }, fetchMany(ids, options) { return __async(this, null, function* () { return yield this.request("", __spreadProps(__spreadValues({ method: "GET" }, options), { body: { ids } })); }); }, createOne(data, options) { return __async(this, null, function* () { return yield this.request("", __spreadProps(__spreadValues({ method: "POST" }, options), { body: data })); }); }, updateOne(id, data, options) { return __async(this, null, function* () { return yield this.request(id.toString(), __spreadProps(__spreadValues({ method: "PATCH" }, options), { body: data })); }); }, updateMany(ids, data, options) { return __async(this, null, function* () { return yield this.request("", __spreadProps(__spreadValues({ method: "PATCH" }, options), { body: { ids, data } })); }); }, deleteOne(id, options) { return __async(this, null, function* () { return yield this.request(id.toString(), __spreadValues({ method: "DELETE" }, options)); }); }, deleteMany(ids, options) { return __async(this, null, function* () { return yield this.request("", __spreadProps(__spreadValues({ method: "DELETE" }, options), { body: { ids } })); }); } }; } function defineApi(ModelClass, setup) { const setupConfig = getSetupConfig(ModelClass, setup); const baseEndpoints = getBaseEndpoints(ModelClass, setupConfig); const endpointsWithBaseContext = {}; let setupEndpoints = {}; if (setup && setup.endpoints) { setupEndpoints = setup.endpoints; } Object.keys(setupEndpoints).forEach((key) => { const setupKey = key; const method = setupEndpoints[setupKey]; endpointsWithBaseContext[setupKey] = method.bind(__spreadValues(__spreadValues({}, baseEndpoints), setupEndpoints)); }); return function useApi() { return __spreadValues(__spreadValues({}, baseEndpoints), endpointsWithBaseContext); }; } // src/defineRepository.ts import { computed, readonly, ref, toRef } from "vue"; import { defineStore } from "pinia"; function createCollection() { const items = ref(/* @__PURE__ */ new Map()); return getBaseMethods(items); } function createModelStore(name) { return defineStore(name, () => { return { items: /* @__PURE__ */ new Map() }; }); } function getBaseMethods(items) { const list = computed(() => Array.from(items.value.values())); return { items, isEmpty() { return this.count() === 0; }, isNotEmpty() { return this.count() > 0; }, count() { return items.value.size; }, clear() { items.value.clear(); }, set(models) { this.clear(); models.forEach((model) => { items.value.set(getModelId(model), model); }); }, add(items2) { if (Array.isArray(items2)) { items2.forEach((item) => this.add(item)); } else { this.items.value.set(getModelId(items2), items2); } }, find(id) { var _a; return (_a = items.value.get(id)) != null ? _a : null; }, all() { return list.value; }, first() { var _a; return (_a = list.value[0]) != null ? _a : null; }, last() { var _a; return (_a = list.value[list.value.length - 1]) != null ? _a : null; }, update(item, data) { if (typeof data !== "undefined") { const id = typeof item === "object" ? getModelId(item) : item; const existingItem = this.find(id); if (existingItem) { items.value.set(id, existingItem.$update(data)); } } else { const model = item; const id = getModelId(model); const existingItem = this.find(id); if (existingItem) { items.value.set(id, existingItem.$update(model)); } } }, remove(models) { if (Array.isArray(models)) { models.forEach((model) => this.remove(model)); } else { items.value.delete( typeof models === "object" ? getModelId(models) : models ); } }, toArray() { return list.value; }, getWatchable() { return readonly(items); } }; } function getModelId(model) { const property = model.constructor.primaryKey; return model[property]; } function defineRepository(ModelClass, setup) { let setupMethods = {}; if (setup && setup.methods) { setupMethods = setup.methods; } if (setup && setup.local) { return function useRepository() { const collection = createCollection(); return __spreadValues(__spreadValues({}, collection), setupMethods); }; } const useModelStore = createModelStore(ModelClass.name); return function useRepository() { const store = useModelStore(); const items = toRef(store.items); const baseMethods = getBaseMethods(items); return __spreadValues(__spreadValues({}, baseMethods), setupMethods); }; } // src/definePagination.ts import { defineStore as defineStore2 } from "pinia"; import { ref as ref2 } from "vue"; function defineGlobalPagination(name) { const useStore = defineStore2(name + "Pagination", () => { const pagination = ref2(null); return { pagination }; }); return function useGlobalPagination() { const store = useStore(); function get() { return store.pagination; } function set(pagination) { store.pagination = pagination; } function clear() { set(null); } return { get, set, clear }; }; } function defineLocalPagination() { return () => { const _pagination = ref2(null); function get() { return _pagination.value; } function set(pagination) { _pagination.value = pagination; } function clear() { set(null); } return { get, set, clear }; }; } function definePagination(name, setup) { return (setup == null ? void 0 : setup.local) ? defineLocalPagination() : defineGlobalPagination(name); } // src/defineController.ts function _defineController(ModelClass, apiDefinition, repositoryDefinition, setup) { var _a, _b; const _useApi = apiDefinition ? apiDefinition : defineApi(ModelClass, setup == null ? void 0 : setup.api); const _useRepository = repositoryDefinition ? repositoryDefinition : defineRepository(ModelClass, setup == null ? void 0 : setup.repository); const api = _useApi(); const baseActions = {}; const setupActions = (_a = setup == null ? void 0 : setup.actions) != null ? _a : {}; const setupReactions = (_b = setup == null ? void 0 : setup.reactions) != null ? _b : {}; const usePagination = definePagination(ModelClass.name); function getResponseData(response) { return response._apiResponse ? response.getData() : response; } return function useController() { const repository = _useRepository(); const pagination = usePagination(); const responseHandlers = { fetchAll(response) { repository.set(getResponseData(response)); }, fetchOne(response) { repository.add(getResponseData(response)); }, fetchMany(response) { repository.add(getResponseData(response)); }, createOne(response) { repository.add(getResponseData(response)); }, updateOne(response) { repository.update(getResponseData(response)); }, updateMany(models) { models.forEach((model) => repository.update(model)); }, deleteOne(_, ...args) { repository.remove(args[0]); }, deleteMany(_, ...args) { repository.remove(args); } }; Object.keys(api).forEach((key) => { const endpointKey = key; baseActions[endpointKey] = function(...args) { return __async(this, null, function* () { var _a2; const response = yield api[endpointKey](...args); if (response._apiResponse && response.isPaginated()) { pagination.set((_a2 = response.getPagination()) != null ? _a2 : null); } if (endpointKey in responseHandlers) { responseHandlers[endpointKey](response, ...args); } else if (endpointKey in setupReactions) { setupReactions[endpointKey](response, ...args); } return response; }); }; }); return __spreadValues(__spreadValues({ api, repository, pagination }, baseActions), setupActions); }; } function defineController(ModelClass, apiDefinition, repositoryDefinition, setup) { let _apiDefinition; let _repositoryDefinition; if (arguments.length === 4) { _apiDefinition = apiDefinition; _repositoryDefinition = repositoryDefinition; } else if (arguments.length === 3) { const functionName = arguments[1].name.toLowerCase(); const composable = functionName.includes("api") ? "api" : "repository"; _apiDefinition = composable === "api" ? arguments[1] : void 0; _repositoryDefinition = composable === "repository" ? arguments[1] : void 0; setup = arguments[2]; } else if (arguments.length === 2) { if (typeof arguments[1] === "function") { const functionName = arguments[1].name.toLowerCase(); const composable = functionName.includes("api") ? "api" : "repository"; _apiDefinition = composable === "api" ? arguments[1] : void 0; _repositoryDefinition = composable === "repository" ? arguments[1] : void 0; } else { setup = arguments[1]; } } return _defineController(ModelClass, _apiDefinition, _repositoryDefinition, setup); } export { ApiResponse, Model, PaginatedResponse, SimpleResponse, createInstance, defineApi, defineConfig, defineController, defineRepository }; //# sourceMappingURL=index.mjs.map