@knowmax/genericlist-core
Version:
Knowmax Generic list with basic CRUD support without any user interface implementation.
1,070 lines (1,051 loc) • 38.2 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// src/utils/error.ts
var providerErrorMessage = (provider) => `use${provider} must be used within a ${provider}Provider.`;
// src/types/constants.ts
var PARAM_PAGE = "page";
var PARAM_ORDER = "order";
var PARAM_DELETED = "deleted";
// src/types/IFilter.ts
var FilterOperator = /* @__PURE__ */ ((FilterOperator2) => {
FilterOperator2["eq"] = "eq";
FilterOperator2["ne"] = "ne";
FilterOperator2["contains"] = "contains";
FilterOperator2["gt"] = "gt";
FilterOperator2["ge"] = "ge";
FilterOperator2["lt"] = "lt";
FilterOperator2["le"] = "le";
return FilterOperator2;
})(FilterOperator || {});
var FILTEROPERATORSSTRING = ["contains" /* contains */, "eq" /* eq */, "ne" /* ne */];
var FILTEROPERATORSNUMBER = ["eq" /* eq */, "ne" /* ne */, "gt" /* gt */, "ge" /* ge */, "lt" /* lt */, "le" /* le */];
var FILTEROPERATORSDATE = ["eq" /* eq */, "ne" /* ne */, "gt" /* gt */, "ge" /* ge */, "lt" /* lt */, "le" /* le */];
var FilterValueType = /* @__PURE__ */ ((FilterValueType2) => {
FilterValueType2[FilterValueType2["string"] = 0] = "string";
FilterValueType2[FilterValueType2["number"] = 1] = "number";
FilterValueType2[FilterValueType2["date"] = 2] = "date";
FilterValueType2[FilterValueType2["boolean"] = 3] = "boolean";
return FilterValueType2;
})(FilterValueType || {});
// src/utils/serialization.ts
var deserializeListState = (list, params) => {
const initialstate = listState(list);
let paramsupdated = false;
deserializeDeleted(list, params);
if (deserializeFilters(list, params)) {
paramsupdated = true;
}
if (deserializeOrder(list, params)) {
paramsupdated = true;
}
if (deserializePage(list, params, initialstate)) {
paramsupdated = true;
}
return paramsupdated;
};
var listState = (list) => `f=${list.filter ?? ""}&o=${list.orderExpression}&s=${list.search ?? ""}&d=${list.deleted}`;
var deserializeOrder = (list, params) => {
const orderparamid = getParamId(PARAM_ORDER, list.id);
const ordervalue = params.get(orderparamid);
if (ordervalue) {
const [field, descending] = parseOrderExpression(ordervalue);
const order = list.orderList.find((order2) => order2.field === field && (order2.descending === descending || order2.descending === void 0 && descending === false));
if (order && (!order.isAvailable || order.isAvailable(order, params, list.id))) {
list.setOrder(order);
} else {
params.delete(orderparamid);
list.setOrder(list.orderList?.find((o) => o.default === true));
return true;
}
}
return false;
};
var deserializeFilters = (list, params) => {
let paramsupdated = false;
let hassearch = false;
const filters = [];
list.filterList.forEach((filter) => {
if (filter.deserializeCustom) {
const customfilters = filter.deserializeCustom(filter, params, list.id);
if (customfilters) {
filters.push(...customfilters);
}
} else {
const paramid = getParamId(filter.id, list.id);
const value = params.get(paramid);
if (value && value !== "") {
if (filter.isAvailable && !filter.isAvailable(filter, params, list.id)) {
params.delete(paramid);
params.delete(fieldIdForOperator(paramid));
paramsupdated = true;
} else if (filter.options) {
const option = filter.options.find((o) => o.id === value);
if (option) {
if (option.expression !== void 0 && option.expression !== "") {
filters.push(option.expression);
} else if (option.value !== void 0 && (typeof option.value === "number" || typeof option.value === "boolean" || option.value !== "")) {
filters.push(`${filter.field ?? filter.id} ${serializeFilterOperator(option.operator)} ${serializeFilterValue(option.value, filter.valueType)}`);
}
}
} else if (filter.isSearch === true) {
list.setSearch(value);
hassearch = true;
} else {
filters.push(`${filter.field ?? filter.id} ${serializeFilterOperator(operatorForFilter(filter, params, list.id))} ${serializeFilterValue(value, filter.valueType)}`);
}
}
}
});
list.setFilter(
filters.length > 0 ? filters.join(" and ") : void 0
/*list.filter*/
);
if (!hassearch) {
list.setSearch(void 0);
}
return paramsupdated;
};
var deserializePage = (list, params, initialState) => {
const pageparamid = getParamId(PARAM_PAGE, list.id);
const page = params.get(pageparamid);
if (page && initialState === listState(list)) {
const p = parseInt(page, 10);
if (isNaN(p) || p === 1) {
if (list.page !== 1) {
list.setPage(1);
params.delete(pageparamid);
return true;
}
} else {
list.setPage(p);
}
} else if (list.page !== 1) {
list.setPage(1);
params.delete(pageparamid);
return true;
}
return false;
};
var deserializeDeleted = (list, params) => {
const paramid = getParamId(PARAM_DELETED, list.id);
const value = params.get(paramid);
list.setDeleted(params.has(paramid) && value !== "false");
};
var operatorForFilter = (filter, params, listId) => {
if (filter.valueType === 3 /* boolean */) {
return "eq" /* eq */;
} else {
const operatorvalue = params.get(fieldIdForOperator(getParamId(filter.id, listId)));
if (operatorvalue) {
const o = FilterOperator[operatorvalue];
if (filter.operators && filter.operators.indexOf(o) !== -1) {
return o;
}
}
return filter.operator;
}
};
// src/utils/filterutils.ts
var serializeFilterValue = (value, valueType) => {
if (typeof value === "boolean" || typeof value === "number") {
return value.toString();
} else if ((valueType === 0 /* string */ || valueType === void 0) && value.indexOf(" ") !== -1) {
return `"${value}"`;
} else {
return value;
}
};
var serializeFilterOperator = (operator) => FilterOperator[operator ?? "eq" /* eq */];
var fieldIdForOperator = (id) => `${id}-o`;
// src/utils/orderutils.ts
var parseOrderExpression = (expression) => {
const parts = expression.trim().split(/\s+/);
if (parts.length > 0) {
return [parts[0].trim(), parts.length === 2 && parts[1].trim() === "desc"];
} else {
return [expression.trim(), false];
}
};
// src/utils/params.ts
var getParamId = (id, listId) => {
if (listId !== void 0 && listId !== "") {
return `${listId}.${id}`;
} else {
return id;
}
};
var getParamValue = (id, params, listId) => {
return params.get(getParamId(id, listId));
};
var hasListPrefix = (id, listId) => {
return id.startsWith(`${listId}.`);
};
// src/list/GenericList.ts
import { action as action2, computed, makeObservable as makeObservable2, observable as observable2 } from "mobx";
// src/list/GenericListSettings.ts
import { makeObservable, observable, action } from "mobx";
var GenericListSettings = class {
constructor() {
/** Never use directly. Always use getter. */
__publicField(this, "_onFetch", defaultFetch);
__publicField(this, "_onFetchSet", false);
/** Never use directly. Always use getter. */
__publicField(this, "_onTranslate", defaultTranslate);
__publicField(this, "_onTranslateSet", false);
/** Set after required configuration of onFetch and onTranslate were made. */
__publicField(this, "ready", false);
/** Set to required bearer token for use in authentication of fetch requests. */
__publicField(this, "token");
/** Set to required language of user for use in fetch requests to get localized data from server. */
__publicField(this, "language");
/** Contains current set of values components user for state. */
__publicField(this, "params", new URLSearchParams());
/** Override to handle custom serialization of user settings like sorting, filtering and paging. */
__publicField(this, "onSerialize", defaultSerialize);
/** Override for custom implementation of header generation for fetch requests. */
__publicField(this, "onGetHeaders", defaultOnGetHeaders);
/** Override for custom implementation of error message extraction after for example fetch requests. */
__publicField(this, "onErrorMessage", defaultErrorMessage);
makeObservable(this, {
ready: observable,
setReady: action,
token: observable,
setToken: action,
language: observable,
setLanguage: action,
params: observable,
setParams: action
});
}
/** Internally called after configuration of required settings was done. No need to call it externally. */
setReady(value) {
this.ready = value;
}
setToken(value) {
this.token = value;
}
setLanguage(value) {
this.language = value;
}
/** Set to current set of values for components to use for state. */
setParams(value) {
this.params = value;
}
/** Internally used to serialize params. By default set to internally maintained params. Override for custom onSerialize for custom serialization. */
serialize(params) {
this.onSerialize(params, this);
}
/** Used for fetching data from server. */
get onFetch() {
return this._onFetch;
}
set onFetch(value) {
this._onFetch = value;
this._onFetchSet = true;
this.setReady(this._onFetchSet && this._onTranslateSet);
}
/** Used for translation of all language sensitive contents. */
get onTranslate() {
return this._onTranslate;
}
set onTranslate(value) {
this._onTranslate = value;
this._onTranslateSet = true;
this.setReady(this._onFetchSet && this._onTranslateSet);
}
};
var defaultFetch = (url, options) => {
return new Promise((resolve, reject) => {
console.warn("onFetch not configured");
reject("not implemented");
});
};
var defaultTranslate = (key, options) => {
console.warn("onTranslate not configured");
return key;
};
var defaultSerialize = (params, settings) => {
settings.setParams(params);
};
var defaultOnGetHeaders = (token, language, headers2) => {
const h = headers2 ?? {};
if (token) {
h["Authorization"] = "Bearer " + token;
}
if (language) {
h["Accept-Language"] = language;
}
return h;
};
var defaultErrorMessage = (error) => {
return new Promise((resolve) => {
resolve(error.message);
});
};
// src/list/GenericList.ts
var GenericList = class {
constructor(configuration) {
__publicField(this, "configuration");
__publicField(this, "_settingsUpdated", false);
__publicField(this, "_loadOptions");
/** Unique id representing this list. Will be provided to methods from settings when called to determine active list. Optional due to backward compatibility. */
__publicField(this, "id");
/** Settings for all generic lists. */
__publicField(this, "settings", new GenericListSettings());
/** Set while loading a single item */
__publicField(this, "loadingSingle", false);
/** Set after initially loading data. */
__publicField(this, "loaded", false);
/** Set while loading data. */
__publicField(this, "loading", false);
__publicField(this, "error");
/** Available options for order. */
__publicField(this, "orderList");
/** Selected order expression. Set to IOrder from orderList or string order expression. */
__publicField(this, "order");
__publicField(this, "deleted", false);
/** List with all filter definitions available for this list. */
__publicField(this, "filterList");
/** Filter to use while loading data. */
__publicField(this, "filter");
/** Query to be used while loading data. */
__publicField(this, "search");
/** Current selected page. 1-based. */
__publicField(this, "page");
/** Number of items per page. */
__publicField(this, "pageSize");
/** List containing actual page of data. */
__publicField(this, "list");
/** Total number of items available. */
__publicField(this, "totalCount");
/** Will be used as post fix for currently configured endpoint. */
__publicField(this, "endpointPostFix");
/** Hash at the moment of last update of list with server data. */
__publicField(this, "currentHash");
/** Set to selected item for specific purpose. */
__publicField(this, "selectedItem");
/** Tag to identify purpose of selected item.. */
__publicField(this, "selectedItemTag");
makeObservable2(this, {
ready: computed,
id: observable2,
setId: action2,
settings: observable2,
setSettings: action2,
loadingSingle: observable2,
setLoadingSingle: action2,
loaded: observable2,
setLoaded: action2,
loading: observable2,
setLoading: action2,
error: observable2,
setError: action2,
orderList: observable2,
setOrderList: action2,
order: observable2,
setOrder: action2,
orderExpression: computed,
deleted: observable2,
setDeleted: action2,
filterList: observable2,
setFilterList: action2,
filter: observable2,
setFilter: action2,
search: observable2,
setSearch: action2,
page: observable2,
setPage: action2,
pageSize: observable2,
setPageSize: action2,
list: observable2,
setList: action2,
totalCount: observable2,
setTotalCount: action2,
endpointPostFix: observable2,
setEndpointPostFix: action2,
hash: computed,
currentHash: observable2,
setCurrentHash: action2,
selectedItem: observable2,
selectedItemTag: observable2,
setSelectedItem: action2,
load: action2,
loadSingle: action2
});
this.configuration = configuration;
this.id = configuration.id;
this.orderList = configuration.orderList ?? [];
this.order = configuration.orderList?.find((o) => o.default === true);
this.filterList = configuration.filterList ?? [];
this.list = [];
this.page = configuration.page ?? 1;
this.pageSize = configuration.pageSize ?? 10;
this.totalCount = 0;
}
/** Set when this generic list is ready for use. Essentially this means settings configuration is ready. */
get ready() {
return this.settings.ready;
}
setId(value) {
this.id = value;
}
/** Usually set from value provided GenericListSettinsProvider. */
setSettings(value) {
this._settingsUpdated = true;
this.settings = value;
}
setLoadingSingle(value) {
this.loadingSingle = value;
}
setLoaded(value) {
this.loaded = value;
}
setLoading(value) {
this.loading = value;
}
setError(value) {
this.error = value;
}
setOrderList(value) {
this.orderList = value;
}
setOrder(value) {
this.order = value;
}
/** Server expression based on current order setting. */
get orderExpression() {
return this.order ? typeof this.order !== "string" ? `${this.order.field}${this.order.descending === true ? " desc" : ""}` : this.order : "";
}
setDeleted(value) {
this.deleted = value;
}
setFilterList(value) {
this.filterList = value;
}
setFilter(value) {
this.filter = value;
}
setSearch(value) {
this.search = value;
}
setPage(value) {
this.page = value;
}
setPageSize(value) {
this.pageSize = value;
}
setList(value) {
this.list = value;
}
setTotalCount(value) {
this.totalCount = value;
}
/** Set to postfix for current endpoint. For example the value "abc" will result in "/abc" postfix after currently configured endpoint. */
setEndpointPostFix(value) {
this.endpointPostFix = value;
}
/** Dynamic hash representing current state of component. Use as dependecy to trigger effects */
get hash() {
return `id=${this.id}&p=${this.page}&ps=${this.pageSize}&o=${this.orderExpression}&f=${this.filter}&s=${this.search}${this.deleted ? "&d=true" : ""}&t=${this.settings.token}&epf=${this.endpointPostFix}`;
}
setCurrentHash(value) {
this.currentHash = value;
}
/** Set when hash was updated. Rather use hash as dependecy to trigger effects. */
get hashUpdated() {
return this.hash !== this.currentHash;
}
/** Selects item for specific purpose (not edit or delete). */
setSelectedItem(value, tag) {
this.selectedItem = value;
this.selectedItemTag = value ? tag : void 0;
}
/** Load current page of items.
* @param reload If true, will reload current page of items using original options used on inital load.
* @param options Options to use while loading data. If not provided, will use options used on inital load only in case of reload.
*/
async load(reload = false, options) {
if (!this.settings.token || this.configuration.requiredEndpointPostfix === true && (!this.endpointPostFix || this.endpointPostFix.trim() === "")) {
this.setList([]);
this.setTotalCount(0);
this.setLoaded(false);
} else if (this.hashUpdated || reload) {
try {
try {
this.checkSettings();
this.setCurrentHash(this.hash);
this.setError(void 0);
this.setLoading(true);
if (reload && !options && this._loadOptions) {
options = this._loadOptions;
} else if (!reload) {
this._loadOptions = options;
}
const endpoint = this.configuration.requiredEndpointPostfix === true && this.endpointPostFix && this.endpointPostFix.trim() !== "" ? `${this.configuration.endpoint}/${this.endpointPostFix}` : this.configuration.endpoint;
const method = options?.method ?? "post";
const searchparams = Array.isArray(options?.searchParams) ? [...options.searchParams] : { ...options?.searchParams };
if (this.deleted) {
if (Array.isArray(searchparams)) {
searchparams.push(["deleted", "true"]);
} else {
searchparams.deleted = "true";
}
}
const response = await this.settings.onFetch(endpoint, {
searchParams: searchparams,
//options?.searchParams,
method,
headers: options?.headers ?? this.settings.onGetHeaders(this.settings.token, this.settings.language, void 0, this.id),
// Make sure we never post json when not post was specified as method
json: method === "post" ? options?.json ?? {
count: true,
take: this.pageSize,
skip: (this.page - 1) * this.pageSize,
orderBy: this.orderExpression,
filter: this.filter,
search: this.search
} : void 0
}, this.id);
this.setList(response.value ?? []);
this.setTotalCount(response.count ?? 0);
this.setLoaded(true);
} catch (e) {
if (e instanceof Error) {
this.setError(e);
}
this.setLoaded(false);
}
} finally {
this.setLoading(false);
}
}
}
/** Load single item identified by id.
* @param id Id of item to load.
* @param options Options to use while loading data.
* @param endpoint Endpoint to use while loading data. If not provided, will use endpointGetSingle or endpoint from configuration.
*/
async loadSingle(id, options, endpoint) {
this.setError(void 0);
if (this.settings.token) {
try {
this.checkSettings();
this.setLoadingSingle(true);
try {
return await this.settings.onFetch(`${endpoint ?? this.configuration.endpointGetSingle ?? this.configuration.endpoint}/${id}`, {
searchParams: options?.searchParams,
method: options?.method ?? "get",
headers: options?.headers ?? this.settings.onGetHeaders(this.settings.token, this.settings.language, void 0, this.id)
}, this.id);
} catch (e) {
if (e instanceof Error) {
this.setError(e);
}
}
} finally {
this.setLoadingSingle(false);
}
}
}
checkSettings() {
if (!this._settingsUpdated) {
console.warn(`Settings not set for generic list with endpoint ${this.configuration.endpoint}`);
}
}
};
// src/list/GenericCudList.ts
import { action as action3, computed as computed2, observable as observable3, makeObservable as makeObservable3 } from "mobx";
var GenericCudList = class extends GenericList {
constructor(cudConfiguration) {
super(cudConfiguration);
__publicField(this, "cudConfiguration");
/** Set in case of Create Update Delete error. Prefer using specialized error fields per mode. */
__publicField(this, "cudError");
/** Might be set in case custom validation method returned error message. */
__publicField(this, "validationError");
/** Set to editable item when in create or update mode. */
__publicField(this, "editableItem");
/** Set to original editable item when starting update mode. */
__publicField(this, "editableItemUnmodified");
/** Set to item when in delete mode. */
__publicField(this, "deleteItem");
/** Set with data of just deleted item. */
__publicField(this, "deletedItem");
/** Set with data of newly created item. */
__publicField(this, "createdItem");
/** Set to item when in restore mode. */
__publicField(this, "restoreItem");
/** Set with data of just restored item. */
__publicField(this, "restoredItem");
/** Start create mode for item. */
__publicField(this, "create", (template) => {
this.deleteItem = void 0;
this.restoreItem = void 0;
this.editableItem = template ?? this.cudConfiguration.defaultItem();
this.editableItemUnmodified = void 0;
this.cudError = void 0;
this.validationError = void 0;
});
/** Cancel any mode we are currently in (create, update, delete, restore). Using lambda syntax here so we can directly hook this method to onClick/onChange like events of user interface. */
__publicField(this, "cancel", () => {
this.editableItem = void 0;
this.editableItemUnmodified = void 0;
this.deleteItem = void 0;
this.restoreItem = void 0;
this.cudError = void 0;
this.validationError = void 0;
});
makeObservable3(this, {
cudError: observable3,
setCudError: action3,
validationError: observable3,
setValidationError: action3,
hasCudOrValidationError: computed2,
createError: computed2,
updateError: computed2,
deleteError: computed2,
restoreError: computed2,
editableItem: observable3,
setEditableItem: action3,
editableItemUnmodified: observable3,
setEditableItemUnmodified: action3,
deleteItem: observable3,
setDeleteItem: action3,
deletedItem: observable3,
setDeletedItem: action3,
createdItem: observable3,
setCreatedItem: action3,
restoreItem: observable3,
setRestoreItem: action3,
restoredItem: observable3,
setRestoredItem: action3,
create: action3,
update: action3,
delete: action3,
restore: action3,
cancel: action3,
isCreate: computed2,
isUpdate: computed2,
isDelete: computed2,
isRestore: computed2,
isModified: computed2,
isValidated: computed2,
save: action3,
deleteConfirmed: action3,
restoreConfirmed: action3
});
this.cudConfiguration = cudConfiguration;
}
setCudError(value) {
this.cudError = value;
}
setValidationError(value) {
this.validationError = value;
}
/** Set in case of CUD error or validation error (latter only when in update mode). */
get hasCudOrValidationError() {
return (this.isCreate || this.isUpdate) && this.cudError !== void 0 || this.isUpdate && this.validationError !== void 0;
}
/** Set after error in create mode. */
get createError() {
return this.isCreate && this.cudError !== void 0 ? this.cudError : void 0;
}
/** Set after error in update mode. */
get updateError() {
return this.isUpdate && this.cudError !== void 0 ? this.cudError : void 0;
}
/** Set after error in delete mode. */
get deleteError() {
return this.isDelete && this.cudError !== void 0 ? this.cudError : void 0;
}
/** Set after error in restore mode. */
get restoreError() {
return this.isRestore && this.cudError !== void 0 ? this.cudError : void 0;
}
setEditableItem(value) {
this.editableItem = value;
const validationerror = value !== void 0 && this.cudConfiguration.validateItem && this.cudConfiguration.validateItem(value);
if (typeof validationerror === "string") {
this.setValidationError(validationerror);
} else {
this.setValidationError(void 0);
}
}
setEditableItemUnmodified(value) {
this.editableItemUnmodified = value;
}
setDeleteItem(value) {
this.deleteItem = value;
}
setDeletedItem(value) {
this.deletedItem = value;
}
setCreatedItem(value) {
this.createdItem = value;
}
setRestoreItem(value) {
this.restoreItem = value;
}
setRestoredItem(value) {
this.restoredItem = value;
}
/** Start update mode for given item. */
update(entity) {
this.deleteItem = void 0;
this.restoreItem = void 0;
this.editableItem = { ...entity };
this.editableItemUnmodified = { ...entity };
this.cudError = void 0;
this.validationError = void 0;
}
/** Start delete mode for given item. */
delete(entity) {
this.editableItem = void 0;
this.editableItemUnmodified = void 0;
this.deleteItem = entity;
this.restoreItem = void 0;
this.cudError = void 0;
this.validationError = void 0;
}
/** Start restore mode for given item. */
restore(entity) {
this.editableItem = void 0;
this.editableItemUnmodified = void 0;
this.deleteItem = void 0;
this.restoreItem = entity;
this.cudError = void 0;
this.validationError = void 0;
}
/** True if in create mode. */
get isCreate() {
return this.editableItem !== void 0 && this.idForItem(this.editableItem) === void 0;
}
/** True if in update mode. */
get isUpdate() {
return this.editableItem !== void 0 && this.idForItem(this.editableItem) !== void 0;
}
/** True if in delete mode. */
get isDelete() {
return this.deleteItem !== void 0;
}
/** True if in restore mode. */
get isRestore() {
return this.restoreItem !== void 0;
}
/** True is editable data was changed while in update mode. */
get isModified() {
return this.editableItemUnmodified === void 0 || JSON.stringify(this.editableItemUnmodified) !== JSON.stringify(this.editableItem);
}
/** True if editable data contains valid data while in create or update mode. */
get isValidated() {
return this.editableItem && (!this.cudConfiguration.validateItem || this.cudConfiguration.validateItem(this.editableItem) === true);
}
/** Perform actual create or update operation of item set in create or update mode. */
async save() {
this.setCudError(void 0);
this.setCreatedItem(void 0);
if (this.settings.token && this.editableItem) {
try {
const serveritem = this.cudConfiguration.serverItem ? this.cudConfiguration.serverItem(this.editableItem) : { ...this.editableItem };
if (this.isCreate) {
const created = await this.settings.onFetch(this.cudConfiguration.endpointPost ?? this.configuration.endpoint, {
method: "post",
headers: this.settings.onGetHeaders(this.settings.token, this.settings.language, void 0, this.id),
json: serveritem
}, this.id);
this.cancel();
this.setCreatedItem(created);
return true;
} else if (this.isUpdate && this.idForItem(this.editableItem) !== void 0) {
await this.settings.onFetch(`${this.cudConfiguration.endpointPut ?? this.configuration.endpoint}/${this.idForItem(this.editableItem)}`, {
method: "put",
headers: this.settings.onGetHeaders(this.settings.token, this.settings.language, void 0, this.id),
json: serveritem
}, this.id);
this.setEditableItemUnmodified({ ...this.editableItem });
return true;
}
} catch (e) {
if (e instanceof Error) {
this.setCudError(e);
}
}
}
return false;
}
/** Perform actual delete operation of item previously set in delete mode. */
async deleteConfirmed() {
this.setCudError(void 0);
if (this.isDelete && this.deleteItem && this.settings.token) {
try {
await this.settings.onFetch(`${this.cudConfiguration.endpointDelete ?? this.configuration.endpoint}/${this.idForItem(this.deleteItem)}`, {
method: "delete",
headers: this.settings.onGetHeaders(this.settings.token, this.settings.language, void 0, this.id)
// TODO: reintroduce retry
//retry: 0
}, this.id);
this.setDeletedItem(this.deleteItem);
this.cancel();
return true;
} catch (e) {
if (e instanceof Error) {
this.setCudError(e);
}
}
}
return false;
}
/** Perform actual restore operation of item previously set in restore mode. */
async restoreConfirmed() {
this.setCudError(void 0);
if (this.isRestore && this.restoreItem && this.settings.token) {
try {
await this.settings.onFetch(`${this.cudConfiguration.endpointRestore ?? this.configuration.endpoint}/restore/${this.idForItem(this.restoreItem)}`, {
method: "post",
headers: this.settings.onGetHeaders(this.settings.token, this.settings.language, void 0, this.id)
// TODO: reintroduce retry
//retry: 0
}, this.id);
this.setRestoredItem(this.restoreItem);
this.cancel();
return true;
} catch (e) {
if (e instanceof Error) {
this.setCudError(e);
}
}
}
return false;
}
/** Return id of given item using settings provided in provided @see IGenericCudListConfiguration. */
idForItem(item) {
const idField = this.cudConfiguration.idField ?? "id";
return this.getNestedProperty(item, idField);
}
/** Return name of given item using settings provided in provided @see IGenericCudListConfiguration. */
nameForItem(item) {
const nameField = this.cudConfiguration.nameField ?? "name";
return this.getNestedProperty(item, nameField);
}
/** Helper function to get nested property values using dot notation (e.g., "user.name.surname"). */
getNestedProperty(obj, path) {
if (!obj || !path) {
return void 0;
}
const keys = path.split(".");
let current = obj;
for (const key of keys) {
if (current === null || current === void 0 || typeof current !== "object") {
return void 0;
}
current = current[key];
}
return typeof current === "string" || typeof current === "number" ? current : void 0;
}
};
// src/list/GenericListHook.tsx
import { useEffect, useMemo as useMemo2 } from "react";
// src/list/GenericListCacheProvider.tsx
import { createContext, useContext, useMemo } from "react";
import { observer } from "mobx-react-lite";
// src/list/GenericListCache.ts
var GenericListCache = class {
constructor(maxSize) {
__publicField(this, "list", []);
__publicField(this, "maxSize");
this.maxSize = maxSize;
}
get(key) {
const list = this.list.find((item) => item.key === key)?.value;
return list ? list : void 0;
}
getOrCreate(key, creator) {
return this.list.find((item) => item.key === key)?.value ?? this.set(key, creator);
}
set(key, creator) {
const list = creator();
const index = this.list.findIndex((item) => item.key === key);
if (index !== -1) {
this.list[index].value = list;
} else {
if (this.list.length === this.maxSize) {
this.list.shift();
}
this.list.push({ key, value: list });
}
return list;
}
remove(key) {
const index = this.list.findIndex((item) => item.key === key);
if (index !== -1) {
this.list.splice(index, 1);
}
}
};
// src/list/GenericListCacheProvider.tsx
import { jsx } from "react/jsx-runtime";
var GenericListCacheContext = createContext(null);
var useGenericListCache = () => {
const store = useContext(GenericListCacheContext);
if (!store) {
throw new Error(providerErrorMessage("GenericListCache"));
}
return store;
};
var GenericListCacheProvider = observer(({ children }) => {
const genericlistcache = useMemo(() => new GenericListCache(10), []);
return /* @__PURE__ */ jsx(GenericListCacheContext.Provider, { value: genericlistcache, children });
});
// src/list/GenericListHook.tsx
var useList = (configuration, autoLoad = true, cache = true, dependencies) => {
const listsettings = useGenericListSettings();
const listcache = useGenericListCache();
const list = useMemo2(() => {
if (cache) {
const key = configuration.id ? `${configuration.id}:${configuration.endpoint}` : configuration.endpoint;
if (dependencies) {
listcache.remove(key);
}
return listcache.getOrCreate(key, () => new GenericList(configuration));
} else {
return new GenericList(configuration);
}
}, [cache, configuration.endpoint, configuration.id, listcache, ...dependencies || []]);
useEffect(() => {
list.setSettings(listsettings);
}, [list, listsettings]);
useEffect(() => {
const params = new URLSearchParams(listsettings.params);
if (deserializeListState(list, params)) {
listsettings.serialize(params);
}
}, [list, listsettings.params]);
useEffect(() => {
if (autoLoad) {
list.load();
}
}, [list.hash, list, autoLoad]);
return list;
};
// src/list/GenericListSettingsProvider.tsx
import { createContext as createContext2, useContext as useContext2, useMemo as useMemo3 } from "react";
import { observer as observer2 } from "mobx-react-lite";
import { jsx as jsx2 } from "react/jsx-runtime";
var GenericListSettingsContext = createContext2(null);
var useGenericListSettings = () => {
const genericlistsettings = useContext2(GenericListSettingsContext);
if (!genericlistsettings) {
throw new Error(providerErrorMessage("GenericListSettings"));
}
return genericlistsettings;
};
var GenericListSettingsProvider = observer2(({ children }) => {
const genericlistsettings = useMemo3(() => new GenericListSettings(), []);
return /* @__PURE__ */ jsx2(GenericListSettingsContext.Provider, { value: genericlistsettings, children: /* @__PURE__ */ jsx2(GenericListCacheProvider, { children }) });
});
// src/list/SimpleListHook.tsx
import { useEffect as useEffect2, useMemo as useMemo4, useState, useCallback } from "react";
import { headers, FetchError } from "@knowmax/http-utils";
var useSimpleList = (configuration, dependencies) => {
const [result, setResult] = useState(void 0);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(void 0);
const [status, setStatus] = useState(void 0);
const fetchData = useCallback(async (abortSignal) => {
if (configuration.token) {
try {
setIsLoading(true);
setError(void 0);
const requestBody = {
take: configuration.pageSize ?? 10,
skip: ((configuration.page ?? 1) - 1) * (configuration.pageSize ?? 10),
orderBy: configuration.order || "",
filter: configuration.filter,
search: configuration.search,
count: configuration.count ?? false
};
const requestHeaders = headers().withContentTypeJson().withBearer(configuration.token);
const response = await fetch(configuration.endpoint, {
method: "post",
headers: requestHeaders.export(),
body: JSON.stringify(requestBody),
signal: abortSignal
});
setStatus(response.status);
if (!response.ok) {
const errorData = await response.json().catch(() => void 0);
throw new FetchError(response, errorData);
}
const fetchedResult = await response.json();
if (!abortSignal?.aborted) {
setResult(fetchedResult);
}
} catch (err) {
if (err instanceof Error && err.name === "AbortError") {
return;
}
const errorInstance = err instanceof Error ? err : new Error("An unknown error occurred");
if (!abortSignal?.aborted) {
setError(errorInstance);
setResult(void 0);
}
} finally {
if (!abortSignal?.aborted) {
setIsLoading(false);
}
}
}
}, [configuration.endpoint, configuration.token, configuration.order, configuration.filter, configuration.page, configuration.pageSize]);
const refetch = useCallback(async () => {
await fetchData();
}, [fetchData]);
useEffect2(() => {
const abortController = new AbortController();
fetchData(abortController.signal);
return () => {
abortController.abort();
};
}, dependencies ? [fetchData, configuration.token, ...dependencies] : [fetchData]);
return useMemo4(() => ({
result,
isLoading,
error,
status,
refetch
}), [result, isLoading, error, status, refetch]);
};
// src/index.ts
if (!new class {
constructor() {
__publicField(this, "x");
}
}().hasOwnProperty("x")) throw new Error("Transpiler is not configured correctly - required for MobX support");
export {
FILTEROPERATORSDATE,
FILTEROPERATORSNUMBER,
FILTEROPERATORSSTRING,
FilterOperator,
FilterValueType,
GenericCudList,
GenericList,
GenericListSettings,
GenericListSettingsProvider,
PARAM_DELETED,
PARAM_ORDER,
PARAM_PAGE,
deserializeListState,
fieldIdForOperator,
getParamId,
getParamValue,
hasListPrefix,
operatorForFilter,
parseOrderExpression,
providerErrorMessage,
serializeFilterOperator,
serializeFilterValue,
useGenericListSettings,
useList,
useSimpleList
};
//# sourceMappingURL=index.mjs.map