UNPKG

aura-enum

Version:
1,759 lines (1,755 loc) 79.5 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var auraShared = require('aura-shared'); let EnumUtils$1 = class EnumUtils { /** * @description 从配置中获取值 * @private * @param item - 枚举项 * @param config - 配置项,可以是函数、字符串或字符串数组 * @returns 提取的值 */ static getValueFromConfig(item, config) { if (auraShared.isFunction(config)) { return config(item); } else if (auraShared.isArray(config)) { return config.map((key) => item[key]).filter((val) => val !== void 0).join(" "); } return item[config]; } /** * 创建排序函数 */ static createSortFunction(field) { if (typeof field === "function") return field; return (a, b) => { const aVal = a[field]; const bVal = b[field]; if (aVal === void 0 && bVal === void 0) return 0; if (aVal === void 0) return 1; if (bVal === void 0) return -1; return String(aVal).localeCompare(String(bVal)); }; } }; class EnumFactory { /** * @description 构造函数,用于初始化枚举工厂 * @param enumObj - 枚举对象,可以是简单的键值对或复杂对象 * @param options - 配置选项 * @param options.labelKeys - 用于提取标签的键或函数 * @param options.valueKeys - 用于提取值的键或函数 * @param options.key - 用于提取键的键或函数 * @param options.isEnumFactory - 用于提取键的键或函数 */ constructor(name, enumObj, options = {}) { this.strictMode = false; this.defaultLang = "zh"; // 假设默认语言是英语 this.plugins = []; this.isEnumStore = true; /** * 添加自定义验证规则 * @param rules - 验证规则数组 */ this.validationRules = []; this.cache = /* @__PURE__ */ new Map(); this.history = []; this.observers = /* @__PURE__ */ new Set(); this.metadata = /* @__PURE__ */ new Map(); this.indexes = /* @__PURE__ */ new Map(); this.version = 1; this.migrations = []; const { labelKeys = ["label"], valueKeys = ["value"], key = "key", isEnumStore = true } = options; this.name = name; this.originList = enumObj; this.isEnumStore = isEnumStore; const entries = Object.entries(enumObj); this.keys = []; this.labels = []; this.values = []; this.disposedList = []; this.entriesMapByKey = /* @__PURE__ */ new Map(); this.entriesMapByLabel = /* @__PURE__ */ new Map(); this.entriesMapByValue = /* @__PURE__ */ new Map(); if (entries.length === 0) return; if (typeof enumObj[entries[0][0]] === "string") { this._processSimpleEnum(entries); } else { this._processObjectEnum(entries, { labelKeys, valueKeys, key }); } if (this.isEnumStore) { enumStore.register(this.name, this); } } /** * 设置严格模式 * @param mode 是否启用严格模式 */ setStrictMode(mode) { this.strictMode = mode; console.log(`Strict mode has been set to ${this.strictMode}`); } /** * 设置默认语言 * @param lang 语言代码 */ setDefaultLang(lang) { this.defaultLang = lang; } /** * 使用插件 * @param plugin 插件实例 */ use(plugin) { this.plugins.push(plugin); } /** * @description 处理简单字符串枚举 * @private * @param entries - 枚举项条目数组 */ _processSimpleEnum(entries) { for (const [k, text] of entries) { const entry = { key: k, label: text }; this.entriesMapByKey.set(k, entry); this.entriesMapByLabel.set(text, entry); this.keys.push(k); this.labels.push(text); this.values.push(text); } } /** * @description 处理复杂对象枚举 * @private * @param entries - 枚举项条目数组 * @param options - 处理选项 */ _processObjectEnum(entries, options) { for (const [_, item] of entries) { const itemKey = EnumUtils$1.getValueFromConfig(item, options.key); let itemText = EnumUtils$1.getValueFromConfig(item, options.labelKeys); let itemValue = EnumUtils$1.getValueFromConfig(item, options.valueKeys); const entry = { key: itemKey, label: itemText, value: itemValue }; this.entriesMapByKey.set(itemKey, entry); this.entriesMapByLabel.set(itemText, entry); this.labels.push(itemText); this.keys.push(itemKey); this.disposedList.push({ ...item, ...entry }); if (itemValue !== void 0) { this.values.push(itemValue); this.entriesMapByValue.set(itemValue, entry); } } } /** * @description 根据键获取枚举项 * @param key - 要查找的键 * @returns 匹配的枚举项,如果不存在则返回 undefined */ getByKey(key) { return this.entriesMapByKey.get(key); } /** * @description 根据标签获取枚举项 * @param text - 要查找的标签文本 * @returns 匹配的枚举项,如果不存在则返回 undefined */ getByLabel(text) { return this.entriesMapByLabel.get(text); } /** * @description 根据值获取枚举项 * @param value - 要查找的值 * @returns 匹配的枚举项,如果不存在则返回 undefined */ getByValue(value) { return this.entriesMapByValue.get(value); } /** * @description 表格数据格式化函数 * @param r - 行数据(未使用) * @param c - 列数据(未使用) * @param val - 要格式化的值 * @returns 格式化后的标签文本 */ tableFormatter(r, c, val) { let entry; if (typeof val === "string") { entry = this.entriesMapByKey.get(val) || this.entriesMapByLabel.get(val); } else { entry = this.entriesMapByValue.get(val); } return entry ? entry.label : "notfound"; } /** * @description 获取所有枚举项的列表 */ getAllItems() { return Array.from(this.entriesMapByKey.values()); } /** * @description 检查是否存在某个键 */ hasKey(key) { return this.entriesMapByKey.has(key); } /** * @description 检查是否存在某个值 */ hasValue(value) { return this.entriesMapByValue.has(value); } /** * @description 检查是否存在某个标签 */ hasLabel(text) { return this.entriesMapByLabel.has(text); } /** * @description 获取所有键的列表 */ getKeys() { return [...this.entriesMapByKey.keys()]; } /** * @description 获取所有标签的列表 */ getLabels() { return [...this.entriesMapByLabel.keys()]; } /** * @description 获取所有值的列表 */ getValues() { return [...this.entriesMapByValue.keys()]; } /** * @description 根据索引获取枚举项 */ getByIndex(index) { if (index >= 0 && index < this.keys.length) { return this.getByKey(this.keys[index]); } return void 0; } /** * 获取指定范围的枚举项 * @param range - 范围数组 [开始索引, 结束索引] * @returns 指定范围的枚举项数组 */ getRange(start, end) { return this.keys.slice(start, end).map((key) => this.getByKey(key)).filter((item) => item); } /** * 获取前n个数据项 * * 根据提供的数量参数n,返回存储库中前n个数据项的列表。如果某些键没有对应的数据项,则这些键会被忽略。 * * @param n - 需要获取的数据项的数量。 * * @returns 返回包含前n个数据项的数组。如果n大于可用数据项的数量,则返回所有可用的数据项。 */ getFirst(n) { return this.keys.slice(0, n).map((key) => this.getByKey(key)).filter((item) => item); } /** * @description 根据标签搜索数据项 * * 在存储库中搜索具有特定标签的数据项。支持精确匹配和大小写敏感度选项。 * * @param searchText - 用于搜索的文本。 * @param exactMatch - 是否需要精确匹配,默认为false,即允许部分匹配。 * @param caseSensitive - 是否区分大小写,默认为false,即不区分大小写。 * * @returns 返回符合搜索条件的数据项列表。 */ searchByLabel(searchText, exactMatch = false, caseSensitive = false) { let searchFunc; if (exactMatch) { searchFunc = caseSensitive ? (text) => text === searchText : (text) => text.toLowerCase() === searchText.toLowerCase(); } else { searchFunc = caseSensitive ? (text) => text.includes(searchText) : (text) => text.toLowerCase().includes(searchText.toLowerCase()); } return Array.from(this.entriesMapByLabel.values()).filter((entry) => searchFunc(entry.label)).map((entry) => entry); } /** * @description 根据值进行查找 * @param searchValue 要查找的值 * @param exactMatch 是否启用精确匹配,默认为false * @param caseSensitive 是否区分大小写,默认为false * @returns 返回匹配的枚举项列表 */ searchByValue(searchValue, exactMatch = false, caseSensitive = false) { let searchFunc; if (auraShared.isString(searchValue)) { if (exactMatch) { searchFunc = caseSensitive ? (value) => value.toString() === searchValue : (value) => value.toString().toLowerCase() === searchValue.toLowerCase(); } else { searchFunc = caseSensitive ? (value) => value.toString().includes(searchValue) : (value) => value.toString().toLowerCase().includes(searchValue.toLowerCase()); } } else { searchFunc = (value) => value === searchValue; } return Array.from(this.entriesMapByValue.values()).filter((entry) => entry.value !== void 0 && searchFunc(entry.value)).map((entry) => entry); } /** * @description 带高亮的文本搜索 * @param searchText - 搜索文本 * @param options - 搜索选项 * @param options.caseSensitive - 是否区分大小写 * @param options.exactMatch - 是否精确匹配 * @param options.highlightColor - 高亮颜色 * @returns 带高亮标记的枚举项数组 */ searchByTextWithHighlight(searchText, options = {}) { const { caseSensitive = false, exactMatch = false, highlightColor = "#ffff00" } = options; const searchFunc = exactMatch ? (text) => caseSensitive ? text === searchText : text.toLowerCase() === searchText.toLowerCase() : (text) => caseSensitive ? text.includes(searchText) : text.toLowerCase().includes(searchText.toLowerCase()); return Array.from(this.entriesMapByLabel.values()).filter((entry) => searchFunc(entry.label)).map((entry) => { let highlightedText; if (exactMatch) { highlightedText = entry.label === searchText || !caseSensitive && entry.label.toLowerCase() === searchText.toLowerCase() ? `<mark style="background-color:${highlightColor};">${entry.label}</mark>` : entry.label; } else { const regExp = new RegExp(`(${searchText})`, caseSensitive ? "g" : "gi"); highlightedText = entry.label.replace(regExp, `<mark style="background-color:${highlightColor};">$1</mark>`); } return { ...entry, label: highlightedText }; }); } /** * @description 反转枚举项顺序 */ reverse() { this.keys.reverse(); this.labels.reverse(); this.values.reverse(); this.disposedList.reverse(); } /** * @description 基于自定义条件过滤枚举项 * @param predicate - 过滤函数 * @returns 过滤后的枚举项数组 */ filter(predicate) { return this.getAllItems().filter(predicate); } /** * @description 根据条件过滤枚举项 * @param predicate - 过滤函数 * @returns 过滤后的枚举项数组 */ updateItem(oldKey, newItem) { const existingItem = this.getByKey(oldKey); if (!existingItem) { throw new Error(`Item with key ${oldKey} does not exist.`); } const updatedItem = { ...existingItem, ...newItem }; this.entriesMapByKey.set(updatedItem.key, updatedItem); this.entriesMapByLabel.set(updatedItem.label, updatedItem); if (updatedItem.value !== void 0) { this.entriesMapByValue.set(updatedItem.value, updatedItem); } else if (existingItem.value !== void 0) { this.entriesMapByValue.delete(existingItem.value); } const index = this.keys.indexOf(oldKey); this.keys[index] = updatedItem.key; this.labels[index] = updatedItem.label; this.values[index] = updatedItem.value; } /** * 获取所有枚举条目 */ getAllEntries() { return Array.from(this.entriesMapByKey.values()); } /** * @description 删除枚举条目 * @param key 要删除的条目的键 */ deleteEntry(key) { const entry = this.entriesMapByKey.get(key); if (!entry) return; this.entriesMapByKey.delete(key); this.entriesMapByLabel.delete(entry.label); if (entry.value !== void 0) { this.entriesMapByValue.delete(entry.value); } this.keys = Array.from(this.entriesMapByKey.keys()); this.labels = Array.from(this.entriesMapByLabel.keys()); this.values = Array.from(this.entriesMapByValue.keys()); } /** * @description 更新枚举项 * @param key - 要更新的枚举项的键 * @param newItem - 新的枚举项数据 * @throws 如果指定的键不存在 */ updateEntry(key, newItem) { const originalEntry = this.entriesMapByKey.get(key); if (!originalEntry) return; const updatedEntry = { ...originalEntry, ...newItem }; this.entriesMapByKey.set(key, updatedEntry); this.entriesMapByLabel.delete(originalEntry.label); this.entriesMapByLabel.set(updatedEntry.label, updatedEntry); if (originalEntry.value !== void 0) { this.entriesMapByValue.delete(originalEntry.value); } if (updatedEntry.value !== void 0) { this.entriesMapByValue.set(updatedEntry.value, updatedEntry); } } /** * 将枚举项转换为下拉选择框选项格式 * @param valueField - 值字段名称,默认为 'value' * @param labelField - 标签字段名称,默认为 'label' * @param extraFields - 额外需要包含的字段 * @returns 选择框选项数组 */ toSelectOptions(options = {}) { const { valueField = "value", labelField = "label", extraFields = [] } = options; return this.disposedList.map((item) => { var _a; const result = { [valueField]: (_a = item.value) != null ? _a : item.key, [labelField]: item.label }; extraFields.forEach((field) => { if (field in item) { result[field] = item[field]; } }); return result; }); } /** * 将枚举项转换为键值对对象 * @param valueType - 值的类型,'value' 使用 value 字段,'label' 使用 label 字段 * @returns 键值对对象 */ toObject(valueType = "value") { return this.getAllItems().reduce((acc, item) => { var _a; return { ...acc, [item.key]: valueType === "value" ? (_a = item.value) != null ? _a : item.key : item.label }; }, {}); } /** * 按照指定字段对枚举项进行排序 * @param field - 排序字段,可以是 'key', 'label', 'value' 或自定义函数 * @param direction - 排序方向,'asc' 或 'desc' */ sort(options) { const { field, direction = "asc" } = options; const sortFn = EnumUtils$1.createSortFunction(field); this.disposedList.sort((a, b) => direction === "asc" ? sortFn(a, b) : sortFn(b, a)); this.keys = this.disposedList.map((item) => item.key); this.labels = this.disposedList.map((item) => item.label); this.values = this.disposedList.map((item) => item.value); } /** * 批量添加枚举项 * @param items - 要添加的枚举项数组 * @throws 如果存在重复的键或值 */ addBatch(items) { const validations = items.map((item) => ({ item, validation: this.validateItem(item) })); const invalidItems = validations.filter((v) => !v.validation.isValid); if (invalidItems.length > 0) { throw new Error(`Invalid items found: ${invalidItems.map((i) => `${i.item.key}: ${i.validation.errors.join(", ")}`).join("; ")}`); } items.forEach(this._addItem.bind(this)); } /** * 添加单个枚举项 */ _addItem(item) { this.entriesMapByKey.set(item.key, item); this.entriesMapByLabel.set(item.label, item); if (item.value !== void 0) { this.entriesMapByValue.set(item.value, item); } this.disposedList.push(item); this.keys.push(item.key); this.labels.push(item.label); if (item.value !== void 0) { this.values.push(item.value); } } /** * 获取指定枚举项的相邻项 * @param key - 当前枚举项的键 * @returns 相邻项对象,包含前一项和后一项 */ getAdjacent(key) { const currentIndex = this.disposedList.findIndex((item) => item.key === key); if (currentIndex === -1) return {}; return { prev: currentIndex > 0 ? this.disposedList[currentIndex - 1] : void 0, next: currentIndex < this.disposedList.length - 1 ? this.disposedList[currentIndex + 1] : void 0 }; } /** * 导出为 JSON 字符串 * @param pretty - 是否美化输出 * @returns JSON 字符串 */ toJSON(pretty = false) { return JSON.stringify(this.disposedList, null, pretty ? 2 : 0); } /** * 从 JSON 字符串导入枚举项 * @param json - JSON 字符串 * @throws 如果 JSON 格式无效 */ static fromJSON(json) { try { const data = JSON.parse(json); if (!auraShared.isArray(data)) { throw new Error("JSON must be an array of enum items"); } const enumObj = data.reduce((acc, item) => { if (!item.key || !item.label) { throw new Error("Invalid enum item format"); } acc[item.key] = item; return acc; }, {}); return new EnumFactory(this.name, enumObj, { isEnumStore: false }); } catch (error) { throw new Error(`Failed to parse JSON: ${error.message}`); } } /** * 创建枚举项的深拷贝 * @returns 新的枚举工厂实例 */ clone() { const clonedEnum = new EnumFactory(this.name, {}); clonedEnum.entriesMapByKey = new Map(this.entriesMapByKey); clonedEnum.entriesMapByLabel = new Map(this.entriesMapByLabel); clonedEnum.entriesMapByValue = new Map(this.entriesMapByValue); clonedEnum.keys = [...this.keys]; clonedEnum.labels = [...this.labels]; clonedEnum.values = [...this.values]; clonedEnum.disposedList = JSON.parse(JSON.stringify(this.disposedList)); clonedEnum.originList = { ...this.originList }; return clonedEnum; } /** * 验证枚举项是否有效 * @param item - 要验证的枚举项 * @returns 验证结果对象 */ validateItem(item) { const errors = []; if (!item.key) { errors.push("\u952E\u503C\u4E0D\u80FD\u4E3A\u7A7A"); } if (!item.label) { errors.push("\u6807\u7B7E\u4E0D\u80FD\u4E3A\u7A7A"); } if (item.key && this.hasKey(item.key)) { errors.push("\u952E\u503C\u5DF2\u5B58\u5728"); } if (item.value !== void 0 && this.hasValue(item.value)) { errors.push("\u503C\u5DF2\u5B58\u5728"); } return { isValid: errors.length === 0, errors }; } /** * @description 更新内部数组 */ _updateArraysFromDisposedList() { this.keys.length = 0; this.labels.length = 0; this.values.length = 0; this.disposedList.forEach((item) => { this.keys.push(item.key); this.labels.push(item.label); if (item.value !== void 0) { this.values.push(item.value); } }); } /** * @description 分页获取枚举项 * 该方法根据提供的分页选项对枚举项进行分页处理,并返回分页结果。 * * @param options - 分页选项,包含以下属性: * @param options.pageNum - 页码,默认为 1。 * @param options.pageSize - 每页的项数,默认为 10。 * @param options.withTotal - 是否包含总项数和总页数信息,默认为 true。 * * @returns 分页结果对象,包含以下属性: * @returns result.items - 分页后的枚举项数组。 * @returns result.pageNum - 当前页码。 * @returns result.pageSize - 每页的项数。 * @returns result.total - 总项数(如果 withTotal 为 true)。 * @returns result.totalPages - 总页数(如果 withTotal 为 true)。 * * @example * ```typescript * // 示例使用 * const enumFactory = new EnumFactory({...}); * const paginationOptions: PaginationOptions = { pageNum: 2, pageSize: 5, withTotal: true }; * const paginationResult = enumFactory.paginate(paginationOptions); * console.log(paginationResult); * // 输出可能如下: * // { * // items: [枚举项数组], * // pageNum: 2, * // pageSize: 5, * // total: 20, * // totalPages: 4 * // } * ``` */ paginate(options) { const { pageNum = 1, pageSize = 10, withTotal = true } = options; const start = (pageNum - 1) * pageSize; const end = start + pageSize; const items = this.disposedList.slice(start, end); const result = { items, pageNum, pageSize }; if (withTotal) { result.total = this.disposedList.length; result.totalPages = Math.ceil(this.disposedList.length / pageSize); } return result; } /** * 将枚举项转换为 Map 对象 * 该方法根据指定的键字段和值字段,将枚举项列表转换为一个 Map 对象。 * 可以通过指定 `keyField` 和 `valueField` 来确定 Map 中的键和值的来源。 * * @param keyField - 用于作为 Map 的键的枚举项字段,默认为 'key'。 * @param valueField - 用于作为 Map 的值的枚举项字段,默认为 'value'。 * * @returns 转换后的 Map 对象,键为 `keyField` 对应的枚举项属性,值为 `valueField` 对应的枚举项属性。 * * @example * ```typescript * // 示例使用 * const enumFactory = new EnumFactory({...}); * // 假设枚举项的结构为 { key: 'item1', label: 'Item One', value: 1 } * const map = enumFactory.toMap(); * console.log(map); * // 输出可能如下: * // Map(1) { 'item1' => 1 } * * const mapWithCustomFields = enumFactory.toMap('label', 'key'); * console.log(mapWithCustomFields); * // 输出可能如下: * // Map(1) { 'Item One' => 'item1' } * ``` */ // public toMap(keyField: keyof EnumItem<T> = 'key', valueField: keyof EnumItem<T> = 'value'): Map<any, any> { // return new Map(this.disposedList.map(item => [item[keyField], item[valueField]])); // } /** * 批量更新枚举项 * @param updates - 更新数据,键为枚举项的key,值为要更新的数据 */ batchUpdate(updates) { Object.entries(updates).forEach(([key, updateData]) => { const item = this.getByKey(key); if (item) this.updateItem(key, updateData); }); } /** * 移动枚举项位置 * @param fromKey - 要移动的项的键 * @param toKey - 目标位置的项的键 */ moveItem(fromKey, toKey) { const fromIndex = this.disposedList.findIndex((item2) => item2.key === fromKey); const toIndex = this.disposedList.findIndex((item2) => item2.key === toKey); if (fromIndex === -1 || toIndex === -1) return; const [item] = this.disposedList.splice(fromIndex, 1); this.disposedList.splice(toIndex, 0, item); this._updateArraysFromDisposedList(); } /** * 对枚举项进行转换 * @param options - 转换选项 * @returns 转换后的数组 */ transform(options = {}) { const { includeUndefined = false, transform } = options; return this.disposedList.filter((item) => includeUndefined || item.value !== void 0).map((item) => transform ? transform(item) : item); } /** * 交换两个枚举项的位置 * @param key1 - 第一个项的键 * @param key2 - 第二个项的键 */ swapItems(key1, key2) { const index1 = this.disposedList.findIndex((item) => item.key === key1); const index2 = this.disposedList.findIndex((item) => item.key === key2); if (index1 === -1 || index2 === -1) return; [this.disposedList[index1], this.disposedList[index2]] = [this.disposedList[index2], this.disposedList[index1]]; this._updateArraysFromDisposedList(); } /** * 获取统计信息 * @returns 统计信息对象 */ getStats() { return { total: this.disposedList.length, withValue: this.disposedList.filter((item) => item.value !== void 0).length, withoutValue: this.disposedList.filter((item) => item.value === void 0).length }; } /** * 导出枚举数据 * 此方法根据提供的导出选项将枚举数据导出为不同的格式。支持的格式包括 JSON、CSV 和对象。 * * @param options - 导出选项,包含以下属性: * @param options.format - 导出格式,默认为 'json',可选值为 'json'、'csv'、'object' 等。 * @param options.pretty - 是否格式化输出,仅适用于 'json' 格式,默认为 false。 * @param options.fields - 要导出的字段列表,默认为 ['key', 'label', 'value']。 * @param options.separator - 仅适用于 CSV 格式,字段之间的分隔符,默认为 ','。 * * @returns 导出的数据,格式为字符串。 * * @throws Error 如果使用了不支持的导出格式,将抛出错误。 * * @example * ```typescript * const enumFactory = new EnumFactory({...}); * const exportOptions: ExportOptions = { format: 'json', pretty: true }; * try { * const exportedData = enumFactory.export(exportOptions); * console.log(exportedData); * } catch (error) { * console.error(error.message); * } * ``` */ export(options = {}) { const { format = "json", pretty = false, fields = ["key", "label", "value"], separator = "," } = options; const data = this.disposedList.map( (item) => fields.reduce((acc, field) => ({ ...acc, [field]: item[field] }), {}) ); switch (format) { case "json": try { return JSON.stringify(data, null, pretty ? 2 : 0); } catch (jsonError) { throw new Error(`Failed to export data in JSON format: ${jsonError.message}`); } case "csv": try { const header = fields.join(separator); const rows = data.map((item) => fields.map((field) => { var _a; return String((_a = item[field]) != null ? _a : ""); }).join(separator)); return [header, ...rows].join("\n"); } catch (csvError) { throw new Error(`Failed to export data in CSV format: ${csvError.message}`); } case "object": try { return JSON.stringify(data.reduce((acc, item) => ({ ...acc, [item.key]: item }), {})); } catch (objectError) { throw new Error(`Failed to export data in object format: ${objectError.message}`); } default: throw new Error(`Unsupported format: ${format}`); } } /** * 比较两个枚举项 * @param item1 - 第一个枚举项 * @param item2 - 第二个枚举项 * @param options - 比较选项 * @returns 是否相等 */ compareItems(item1, item2, options = {}) { const { fields = ["key", "label", "value"], strict = true } = options; return fields.every((field) => { if (strict) return item1[field] === item2[field]; return String(item1[field]) === String(item2[field]); }); } /** * 执行枚举项的搜索操作。 * 该方法根据提供的查询字符串和搜索选项,在枚举项列表中查找匹配的项。 * 可以指定搜索的字段、是否区分大小写、是否进行模糊搜索和结果数量限制。 * * @param query - 要搜索的字符串。 * @param options - 搜索选项,包含以下属性: * @param options.fields - 搜索时要检查的字段列表,默认为 ['key', 'label']。 * @param options.caseSensitive - 是否区分大小写,默认为 false。 * @param options.fuzzy - 是否进行模糊搜索,默认为 true。 * @param options.limit - 结果数量的限制,默认为无限制。 * * @returns 匹配的枚举项数组。 * * @example * ```typescript * const enumFactory = new EnumFactory({...}); * // 假设枚举项列表包含:[{ key: 'item1', label: 'Item One', value: 1 }, { key: 'item2', label: 'Another Item', value: 2 }] * // 执行搜索 * const searchOptions: SearchOptions = { fields: ['label'], caseSensitive: false, fuzzy: true, limit: 1 }; * const searchResults = enumFactory.search('item', searchOptions); * console.log(searchResults); * // 输出可能如下: * // [{ key: 'item1', label: 'Item One', value: 1 }] * ``` */ search(query, options = {}) { const { fields = ["key", "label"], caseSensitive = false, fuzzy = true, limit } = options; const searchQuery = caseSensitive ? query : query.toLowerCase(); const results = this.disposedList.filter((item) => { return fields.some((field) => { var _a; const value = String((_a = item[field]) != null ? _a : ""); const compareValue = caseSensitive ? value : value.toLowerCase(); if (fuzzy) return compareValue.includes(searchQuery); return compareValue === searchQuery; }); }); return limit ? results.slice(0, limit) : results; } addValidationRules(rules) { this.validationRules.push(...rules); } /** * 使用自定义规则验证枚举项 * @param item - 要验证的枚举项 * @returns 验证结果 */ validateWithRules(item) { const errors = []; for (const rule of this.validationRules) { const value = item[rule.field]; if (!rule.validator(value)) { errors.push(rule.message); } } return { isValid: errors.length === 0, errors }; } /** * 获取重复项 * @param field - 要检查的字段 * @returns 重复的项 */ getDuplicates(field) { const valueMap = /* @__PURE__ */ new Map(); this.disposedList.forEach((item) => { const value = item[field]; if (!valueMap.has(value)) { valueMap.set(value, []); } valueMap.get(value).push(item); }); return Array.from(valueMap.values()).filter((items) => items.length > 1).flat(); } /** * 合并另一个枚举工厂实例 * @param other - 要合并的枚举工厂实例 * @param strategy - 合并策略 */ merge(other, strategy = "skip") { other.disposedList.forEach((item) => { const exists = this.hasKey(item.key); if (exists) { switch (strategy) { case "skip": return; case "override": this.updateItem(item.key, item); break; case "throw": throw new Error(`Duplicate key found: ${item.key}`); } } else { this._addItem(item); } }); } /** * 创建枚举项的快照 * @returns 快照数据 */ createSnapshot() { return JSON.stringify({ disposedList: this.disposedList, timestamp: (/* @__PURE__ */ new Date()).toISOString() }); } /** * 从快照恢复 * @param snapshot - 快照数据 */ restoreFromSnapshot(snapshot) { try { const { disposedList } = JSON.parse(snapshot); this.entriesMapByKey.clear(); this.entriesMapByLabel.clear(); this.entriesMapByValue.clear(); this.disposedList.length = 0; this.keys.length = 0; this.labels.length = 0; this.values.length = 0; disposedList.forEach(this._addItem.bind(this)); } catch (error) { throw new Error(`Failed to restore from snapshot: ${error.message}`); } } /** * 批量操作 * @param operations - 操作数组 */ batch(operations) { const snapshot = this.createSnapshot(); try { operations.forEach((op) => { switch (op.type) { case "add": if (op.data) { this._addItem(op.data); } break; case "update": if (op.key && op.data) { this.updateItem(op.key, op.data); } break; case "delete": if (op.key) { this.deleteEntry(op.key); } break; } }); } catch (error) { this.restoreFromSnapshot(snapshot); throw error; } } /** * 高级分组功能 * @param options - 分组选项 * @returns 分组结果 */ groupBy(options) { const { by, sort = "asc", keepEmpty = false } = options; const groups = {}; this.disposedList.forEach((item) => { const key = typeof by === "function" ? by(item) : String(item[by]); if (!groups[key]) { groups[key] = []; } groups[key].push(item); }); Object.keys(groups).forEach((key) => { if (!keepEmpty && groups[key].length === 0) { delete groups[key]; return; } groups[key].sort((a, b) => { const comparison = a.label.localeCompare(b.label); return sort === "asc" ? comparison : -comparison; }); }); return groups; } /** * 带缓存的获取方法 * @param key - 缓存键 * @param fetcher - 数据获取函数 * @param options - 缓存选项 * @returns 缓存的结果 */ getCached(key, fetcher, options = {}) { const { ttl = 5e3, autoRefresh = false } = options; const now = Date.now(); const cached = this.cache.get(key); if (cached && now - cached.timestamp < ttl) { return cached.value; } const value = fetcher(); this.cache.set(key, { value, timestamp: now }); if (autoRefresh) { setTimeout(() => { this.cache.delete(key); }, ttl); } return value; } /** * 比较两个枚举工厂实例的差异 * @param other - 要比较的枚举工厂实例 * @returns 差异结果 */ diff(other) { const result = { added: [], removed: [], modified: [] }; other.disposedList.forEach((item) => { const existing = this.getByKey(item.key); if (!existing) { result.added.push(item); } else if (!this.compareItems(item, existing)) { result.modified.push({ key: item.key, before: existing, after: item }); } }); this.disposedList.forEach((item) => { if (!other.getByKey(item.key)) result.removed.push(item); }); return result; } /** * 获取详细统计信息 * @returns 统计信息 */ getDetailedStats() { const stats = { total: this.disposedList.length, byValue: {}, byLabel: {}, empty: 0, distribution: {} }; this.disposedList.forEach((item) => { var _a; const value = String((_a = item.value) != null ? _a : "undefined"); stats.byValue[value] = (stats.byValue[value] || 0) + 1; stats.byLabel[item.label] = (stats.byLabel[item.label] || 0) + 1; if (item.value === void 0) { stats.empty++; } const valueType = typeof item.value; stats.distribution[valueType] = (stats.distribution[valueType] || 0) + 1; }); return stats; } /** * 创建枚举项的迭代器 */ *[Symbol.iterator]() { yield* this.disposedList; } /** * 批量验证 * @param items - 要验证的枚举项数组 * @returns 验证结果 */ validateBatch(items) { return items.map((item) => ({ item, result: this.validateWithRules(item) })); } /** * 创建枚举项的深层代理 * @param key - 枚举项的键 * @returns 代理对象 */ createProxy(key) { const item = this.getByKey(key); if (!item) { throw new Error(`Item with key ${key} not found`); } return new Proxy(item, { get: (target, prop) => { if (prop === "value" && target.value === void 0) { return target.key; } return target[prop]; }, set: (target, prop, value) => { if (prop in target) { this.updateItem(key, { [prop]: value }); return true; } return false; } }); } // ... existing code ... /** * 添加观察者 * @param callback - 观察者回调函数 * @returns 取消订阅函数 */ subscribe(callback) { this.observers.add(callback); return () => this.observers.delete(callback); } /** * 通知所有观察者 */ notifyObservers(action, item, metadata) { this.observers.forEach((observer) => observer(action, item, metadata)); } /** * 记录历史 */ addHistory(record) { this.history.push({ ...record, timestamp: Date.now() }); } /** * 获取操作历史 * @param limit - 限制返回的记录数 */ getHistory(limit) { const records = [...this.history]; return limit ? records.slice(-limit) : records; } /** * 设置元数据 */ setMetadata(key, value) { this.metadata.set(key, value); } /** * 获取元数据 */ getMetadata(key) { return this.metadata.get(key); } /** * 序列化枚举工厂 */ serialize(options = {}) { const { format = "json", compress = false, withMetadata = false } = options; const data = { items: this.disposedList, ...withMetadata ? { metadata: Object.fromEntries(this.metadata) } : {} }; switch (format) { case "json": return JSON.stringify(data, null, compress ? 0 : 2); case "yaml": throw new Error("YAML format not implemented"); case "xml": throw new Error("XML format not implemented"); default: throw new Error(`Unsupported format: ${format}`); } } /** * 创建枚举项的时间序列 */ createTimeSeries(timeField, valueField, interval = "day") { return this.disposedList.sort((a, b) => new Date(a[timeField]).getTime() - new Date(b[timeField]).getTime()).map((item) => ({ time: new Date(item[timeField]).toISOString(), value: item[valueField] })); } /** * 聚合计算 */ aggregate(options) { const { groupBy, aggregate, fn } = options; const groups = this.groupBy({ by: groupBy }); const result = {}; for (const [key, items] of Object.entries(groups)) { const values = items.map((item) => item[aggregate]); if (typeof fn === "function") { result[key] = fn(values); } else { switch (fn) { case "sum": result[key] = values.reduce((a, b) => Number(a) + Number(b), 0); break; case "avg": result[key] = values.reduce((a, b) => Number(a) + Number(b), 0) / values.length; break; case "count": result[key] = values.length; break; case "min": result[key] = Math.min(...values.map(Number)); break; case "max": result[key] = Math.max(...values.map(Number)); break; } } } return result; } /** * 批量更新带有乐观锁 */ batchUpdateWithLock(updates) { const result = { success: [], failed: [] }; updates.forEach(({ key, data, version }) => { const item = this.getByKey(key); if (!item || item.version !== version) { result.failed.push(key); return; } try { this.updateItem(key, { ...data, version: version + 1 }); result.success.push(key); } catch { result.failed.push(key); } }); return result; } /** * 创建枚举项的依赖图 */ createDependencyGraph(dependencyField) { const graph = /* @__PURE__ */ new Map(); this.disposedList.forEach((item) => { const dependencies = item[dependencyField]; if (auraShared.isArray(dependencies)) { graph.set(item.key, new Set(dependencies)); } }); return graph; } /** * 检查循环依赖 */ checkCircularDependencies(dependencyField) { const graph = this.createDependencyGraph(dependencyField); const cycles = []; const visited = /* @__PURE__ */ new Set(); const path = []; function dfs(node) { if (path.includes(node)) { const cycle = path.slice(path.indexOf(node)); cycles.push(cycle); return; } if (visited.has(node)) return; visited.add(node); path.push(node); const dependencies = graph.get(node) || /* @__PURE__ */ new Set(); dependencies.forEach((dep) => dfs(dep)); path.pop(); } graph.forEach((_, node) => dfs(node)); return cycles; } /** * @description 创建枚举项的树形结构 */ createTree(parentField, childrenField = "children") { const items = [...this.disposedList]; const map = /* @__PURE__ */ new Map(); const roots = []; items.forEach((item) => { map.set(item.key, { ...item, [childrenField]: [] }); }); items.forEach((item) => { const node = map.get(item.key); const parentKey = item[parentField]; if (parentKey && map.has(parentKey)) { map.get(parentKey)[childrenField].push(node); } else { roots.push(node); } }); return roots; } /** * 执行自定义聚合操作 */ customAggregate(aggregator, filter) { const items = filter ? this.disposedList.filter(filter) : this.disposedList; return aggregator(items); } /** * @description 创建索引 * * 根据提供的配置创建一个索引来加速数据查询。 * 如果设置了唯一约束(unique=true),则确保索引字段的组合值是唯一的。 * * @param config - 索引配置对象,包含以下属性: * - fields: (string[]) 必须,用于创建索引的数据项中的字段名数组。 * - unique: (boolean) 可选,默认为false。如果设置为true,则要求索引字段的组合值必须唯一。 * - name: (string) 可选,索引名称,默认使用fields数组连接后的字符串作为名称。 * * @throws 当unique设置为true且发现重复值时,抛出错误提示违反唯一性约束。 */ createIndex(config) { const { fields, unique = false, name = fields.join("_") } = config; const index = /* @__PURE__ */ new Map(); this.disposedList.forEach((item) => { const indexKey = fields.map((field) => item[field]).join("|"); if (!index.has(indexKey)) { index.set(indexKey, /* @__PURE__ */ new Set()); } if (unique && index.get(indexKey).size > 0) { throw new Error(`Unique constraint violation for index ${name}`); } index.get(indexKey).add(item.key); }); this.indexes.set(name, index); } /** * 高级查询 * * 提供灵活的数据查询能力,包括过滤、排序、分页和字段选择。 * * @param options - 查询选项对象,包含以下属性: * - where: ({[key: string]: any}) 可选。用于过滤结果的条件。支持字符串匹配、正则表达式、函数等。 * - orderBy: ([string, 'asc' | 'desc'][]) 可选。指定排序规则的数组,每个元素是一个元组,第一个元素是字段名,第二个元素是排序方向。 * - offset: (number) 可选。指定从第几条记录开始返回。 * - limit: (number) 可选。限制返回的最大记录数。 * - select: (string[]) 可选。指定需要返回的字段名数组。 * * @returns 返回符合查询条件的记录列表。 * * @example * * * // 简单查询所有记录 * let result = repo.query({}); * console.log(result); * * // 查询名字为'John Doe'的记录 * result = repo.query({ where: { name: 'John Doe' } }); * console.log(result); * * // 查询年龄大于等于30的记录,并按年龄升序排序 * result = repo.query({ * where: { age: (age) => age >= 30 }, * orderBy: [['age', 'asc']] * }); * console.log(result); * * // 分页获取数据,跳过第一条,限制返回两条 * result = repo.query({ offset: 1, limit: 2 }); * console.log(result); * * // 只选择特定字段 * result = repo.query({ select: ['name'] }); * console.log(result); * * */ query(options) { let result = [...this.disposedList]; if (options.where) { result = result.filter((item) => { return Object.entries(options.where).every(([key, value]) => { if (value instanceof RegExp) { return value.test(String(item[key])); } if (typeof value === "function") { return value(item[key]); } return item[key] === value; }); }); } if (options.orderBy) { result.sort((a, b) => { for (const [field, direction] of options.orderBy) { const aVal = a[field]; const bVal = b[field]; const comparison = String(aVal).localeCompare(String(bVal)); if (comparison !== 0) { return direction === "asc" ? comparison : -comparison; } } return 0; }); } if (options.offset) { result = result.slice(options.offset); } if (options.limit) { result = result.slice(0, options.limit); } if (options.select) { result = result.map((item) => { const selected = {}; options.select.forEach((field) => { selected[field] = item[field]; }); return selected; }); } return result; } /** * 添加数据迁移 * * 将新的迁移配置添加到现有的迁移列表中,并根据版本号对所有迁移进行排序。 * 这有助于确保在执行数据迁移时按照正确的顺序处理。 * * @param migration - 迁移配置对象,包含以下属性: * - version: (number) 必须。表示迁移的版本号,用于确定迁移执行的顺序。 * - migrate: ((data: T[]) => void) 必须。实际执行迁移操作的函数,接收当前的数据列表作为参数。 */ addMigration(migration) { this.migrations.push(migration); this.migrations.sort((a, b) => a.version - b.version); } /** * 执行数据迁移 * @param targetVersion - 目标版本 */ async migrate(targetVersion) { const currentVersion = this.version; if (targetVersion > currentVersion) { for (const migration of this.migrations) { if (migration.version <= currentVersion) continue; if (migration.version > targetVersion) break; this.disposedList = await migration.up(this.disposedList); } } else if (targetVersion < currentVersion) { for (const migration of this.migrations.reverse()) { if (migration.version > currentVersion) continue; if (migration.version <= targetVersion) break; this.disposedList = await migration.down(this.disposedList); } } this.version = targetVersion; this._updateArraysFromDisposedList(); } /** * 从缓存恢复 * @param key - 缓存键 * @param strategy - 缓存策略 */ restoreFromCache(key, strategy = "memory") { var _a; let data = null; switch (strategy) { case "memory": const cached = this.cache.get(key); data = (_a = cached == null ? void 0 : cached.value) != null ? _a : null; break; case "localStorage": data = localStorage.getItem(key); break; case "sessionStorage": data = sessionStorage.getItem(key); break; } if (data) { const parsed = JSON.parse(data); this.disposedList = parsed.items; if (parsed.metadata) { this.metadata = new Map(Object.entries(parsed.metadata)); } this._updateArraysFromDisposedList(); return true; } return false; } /** * 批量导入数据 * @param items - 要导入的数据 * @param options - 导入选项 */ async bulkImport(items, options = {}) { const { skipValidation = false, batchSize = 100, onProgress } = options; const result = { success: 0, failed: 0, errors: [] }; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); try { if (!skipValidation) { const validations = this.validateBatch(batch); const invalid = validations.filter((v) => !v.result.isValid); if (invalid.length > 0) { throw new Error(`Validation failed for ${invalid.length} items`); } } await Promise.all(batch.map(async (item) => { try { this._addItem(item); result.success++; } catch (error) { result.failed++; result.errors.push(error); } })); if (onProgress) onProgress((i + batch.length) / items.length); } catch (error) { result.failed += batch.length; result.errors.push(error); } } return result; } /** * 创建枚举项的快照并返回差异 * @param snapshot - 快照数据 */ diffWithSnapshot(snapshot) { const snapshotData = JSON.parse(snapshot); const currentKeys = new Set(this.keys); const snapshotKeys = new Set(snapshotData.items.map((item) => item.key)); const added = this.disposedList.filter((item) => !snapshotKeys.has(item.key)); const removed = snapshotData.items.filter((item) => !currentKeys.has(item.key)); const modified = this.disposedList.filter((current) => { const snapshot2 = snapshotData.items.find((item) => item.key === current.key); return snapshot2 && !this.compareItems(current, snapshot2); }).map((item) => ({ key: item.key, changes: this.getItemChanges( item, snapshotData.items.find((i) => i.key === item.key) ) })); return { added, removed, modified }; } /** * 获取两个枚举项之间的差异 */ getItemChanges(current, previous) { const changes = {}; Object.keys(current).forEach((key) => { if (current[key] !== previous[key]) { changes[key] = current[key]; } }); return changes; } /** * 将枚举列表转换为树形结构 * @param options - 树形结构配置选项 * @returns 树形结构数组 * @example * * ``` * // 基本树形结构转换 * const tree = enumFactory.toTree({ * parentField: 'parentId', * childrenField: 'children', * rootParentValue: null * }); * * // 带有自定义转换的树形结构 * const customTree = enumFactory.toTree({ * parentField: 'parentId', * fieldMapping: { * key: 'id', * label: 'name' * }, * transform: (node) => ({ * ...node, * title: `${node.label} (${node.key})`, * expanded: false * }), * sort: (a, b) => a.label.localeCompare(b.label), * withTreeInfo: true * }); */ toTree(options) { const { parentField, childrenField = "children", rootParentValue = null, fieldMapping = {}, transform, sort, keepEmptyChildren = false, withTreeInfo = false } = options; const nodeMap = /* @__PURE__ */ new Map(); const roots = []; this.disposedList.forEach((item) => { const node = transform ? transform(item) : Object.entries(item).reduce((acc, [key, value]) => ({ ...acc, [fieldMapping[key] || key]: value }), {}); node[childrenField] = []; nodeMap.set(item.key, node); }); this.disposedList.forEach((item) => { const node = nodeMap.get(item.key); const parentKey = item[parentField]; if (parentKey === rootParentValue) { roots.push(node); } else { const parentNode = nodeMap.get(parentKey); if (parentNode) { parentNode[childrenField].push(node); } else { roots.push(node); } } }); const processNode = (node, level = 0, parentPath = []) => { const currentPath = [...parentPath, node.key]; const children = node[childrenField]; if (withTreeInfo) { node.treeInfo = { level, path: currentPath, isLeaf: children.length === 0, parentKey: parentPath[parentPath.length - 1], index: children.length, siblings: children.length }; } if (sort && children.length > 0) { children.sort(sort); } children.forEach((child, index) => { if (withTreeInfo) { child.treeInfo = { ...child.treeInfo, index, siblings: children.length }; } processNode(child, level + 1, currentPath); }); if (!keepEmptyChildren && children.length === 0) { delete node[childrenField]; } }; if (sort) { roots.sort(sort); } roots.forEach((root, index) => { if (withTreeInfo) { root.treeInfo = { ...root.treeInfo, index, siblings: roots.length }; } processNode(root); }); return roots; } /** * 在树形结构中查找节点 * @param predicate - 查找条件函数,接收节点和上下文参数 * @param options - 查找选项 * @param options.mode - 搜索模式,'dfs'(深度优先) 或 'bfs'(广度优先),默认 'dfs' * @param options.findAll - 是否查找所有匹配项,默认 false * @param options.maxDepth - 最大搜索深度,默认 Infinity * @param options.includeAncestors - 是否包含祖先节点,默认 false * @param options.includeDescendants - 是否包含后代节点,默认 false * @param options.resultTransform - 结果转换函数 * @returns 查找结果数组 * * @example * // 基本查找 * const result = enumFactory.findInTree( * node => node.key === 'targetKey', * { parentField: 'parentId' } * ); * * // 高级查找示例 * const results = enumFactory.findInTree( * (node, context) => { * return node.value > 100 && * context.depth < 3 && * context.ancestors.some(a => a.type === 'folder'); * }, * { * parentField: 'parentId', * mode: 'bfs', * findAll: true, * includeAncestors: true, * maxDepth: 3 * } * ); * * // 使用结果转换 * const transformedResults = enumFactory.findInTree( * node => node.type === 'file', * { * parentField: 'parentId', * resultTransform: (node, context) => ({ * id: node.key, * path: context.path.map(n => n.label).join('/') * }) * } * ); * * @test * it('should find node by key', () => { * const result = enumFactory.findInTree(node => node.key === 'key1'); * expect(result[0].node.key).toBe('key1'); * }); * * it('should respect maxDepth option', () => { * const results = enumFactory.findInTree( * () => true, * { maxDepth: 2, findAll: true } * ); * expect(results.every(r => r.depth <= 2)).toBe(true); * }); */ findInTree(predicate, options = { parentField: "" }) { const { mode = "dfs", findAll = false, maxDepth = Infinity, includeAncestors = false, includeDescendants = false, resultTransform, ...treeOptions } = options; const tree = this.toTree(treeOptions); const results = []; let matchCount = 0; const getDescendants = (node) => { const descendants = []; const stack = [...node.children || []]; whil