@kovalson/prevue
Version:
A Vue 3 package based on Pinia that helps to manage resource models.
739 lines (723 loc) • 23.4 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
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 __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
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/index.ts
var src_exports = {};
__export(src_exports, {
ApiResponse: () => ApiResponse,
Model: () => Model,
PaginatedResponse: () => PaginatedResponse,
SimpleResponse: () => SimpleResponse,
createInstance: () => createInstance,
defineApi: () => defineApi,
defineConfig: () => defineConfig,
defineController: () => defineController,
defineRepository: () => defineRepository
});
module.exports = __toCommonJS(src_exports);
// src/Model.ts
var import_lodash = require("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, import_lodash.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
var import_ofetch = require("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
var import_lodash2 = require("lodash");
var import_pluralize = __toESM(require("pluralize"));
function getInferredUri(ModelClass) {
const modelName = typeof ModelClass === "function" && ModelClass.prototype !== void 0 ? ModelClass.name : ModelClass.constructor.name;
const kebabCaseName = (0, import_lodash2.kebabCase)(modelName);
const segments = kebabCaseName.split("-");
if (segments.length > 1) {
const lastSegment = segments.pop();
const pluralized = (0, import_pluralize.default)(lastSegment);
return segments.concat(pluralized).join("-");
}
return (0, import_pluralize.default)(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 (0, import_ofetch.$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
var import_vue = require("vue");
var import_pinia = require("pinia");
function createCollection() {
const items = (0, import_vue.ref)(/* @__PURE__ */ new Map());
return getBaseMethods(items);
}
function createModelStore(name) {
return (0, import_pinia.defineStore)(name, () => {
return {
items: /* @__PURE__ */ new Map()
};
});
}
function getBaseMethods(items) {
const list = (0, import_vue.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 (0, import_vue.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 = (0, import_vue.toRef)(store.items);
const baseMethods = getBaseMethods(items);
return __spreadValues(__spreadValues({}, baseMethods), setupMethods);
};
}
// src/definePagination.ts
var import_pinia2 = require("pinia");
var import_vue2 = require("vue");
function defineGlobalPagination(name) {
const useStore = (0, import_pinia2.defineStore)(name + "Pagination", () => {
const pagination = (0, import_vue2.ref)(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 = (0, import_vue2.ref)(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);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ApiResponse,
Model,
PaginatedResponse,
SimpleResponse,
createInstance,
defineApi,
defineConfig,
defineController,
defineRepository
});
//# sourceMappingURL=index.js.map