UNPKG

dehub

Version:
1,237 lines (1,236 loc) 46.9 kB
"use strict"; /** * DEData 模块 * 这是一个数据管理模块,负责数据的存储、加载、更新和状态管理 * @module DEData */ Object.defineProperty(exports, "__esModule", { value: true }); exports.getData = exports.clearDataState = exports.clearTempDataState = exports.delFromDB = exports.getFromDB = exports.updateData = exports.saveToDB = exports.clearDatabase = exports.hasDataState = exports.getStore = exports.getStoreName = void 0; const lodash_1 = require("lodash"); const DEHub_1 = require("./DEHub"); const DETypes_1 = require("./DETypes"); const idb_keyval_1 = require("idb-keyval"); /** 数据加载超时时间(毫秒) */ const LOADING_TIMEOUT = 15000; /** 临时数据清理延迟时间(毫秒) */ const TEMP_DATA_CLEANUP_DELAY = 2000; /** 全局数据状态映射表 */ const dataStateMap = new Map(); /** 全局临时数据状态映射表 */ const dataTempStateMap = new Map(); /** * 获取存储名称 * @param database 数据库名称 * @returns [数据库名称, 客户端标识] */ const getStoreName = (database) => { return [`db.${database ?? 'DEHub'}`, database || '$client']; }; exports.getStoreName = getStoreName; /** * 获取存储实例 * @param database 数据库名称 * @returns 存储实例 */ const getStore = (database) => { const dbName = (0, exports.getStoreName)(database); return (0, idb_keyval_1.createStore)(dbName[0], dbName[1]); }; exports.getStore = getStore; /** * 检查是否存在数据状态 * @param tag 数据标签 * @returns 是否存在数据状态 */ const hasDataState = (tag) => { const itemMapPath = (tag instanceof DETypes_1.Tag ? tag : new DETypes_1.Tag(tag)).path; return dataStateMap.has(itemMapPath); }; exports.hasDataState = hasDataState; /** * 清空数据库 * @param database 数据库名称 */ const clearDatabase = async (database) => { const db = (0, exports.getStore)(database); await (0, idb_keyval_1.clear)(db); }; exports.clearDatabase = clearDatabase; /** * 保存数据到数据库 * @param tag 数据标签 * @param data 数据对象 * @param database 数据库名称 */ const saveToDB = async (tag, data, database) => { const store = (0, exports.getStore)(database); const tTag = tag && tag instanceof DETypes_1.Tag ? tag : new DETypes_1.Tag(tag); dataTempStateMap.set(tTag.path, data); await (0, idb_keyval_1.set)(tTag.path, data, store); }; exports.saveToDB = saveToDB; const updateData = (tag, data, database) => { const tTag = tag && tag instanceof DETypes_1.Tag ? tag : new DETypes_1.Tag(tag); const targetData = (0, exports.getData)(tTag); if (targetData) { targetData.update(data, true); } else { (0, exports.getFromDB)(tTag, database).then(storeData => { if (!storeData) { return; } storeData.data = data; (0, exports.saveToDB)(tTag, storeData, database); }); } }; exports.updateData = updateData; const getFromDB = async (tag, database) => { return new Promise((resolve, reject) => { const store = (0, exports.getStore)(database); const wTimerId = setTimeout(async () => { await (0, exports.delFromDB)(tag, database); reject(`timeout:获取标签为${tag.path}的本地存储超时。`); }, 1000); (0, idb_keyval_1.get)(tag.path, store).then((value) => { resolve(value); }, (reason) => { reject(reason); }).catch((reason) => { reject(reason); }).finally(() => { clearTimeout(wTimerId); }); }); }; exports.getFromDB = getFromDB; const delFromDB = async (tag, database) => { const store = (0, exports.getStore)(database); const tTag = tag && tag instanceof DETypes_1.Tag ? tag : new DETypes_1.Tag(tag); dataTempStateMap.delete(tTag.path); await (0, idb_keyval_1.del)(tTag.path, store); }; exports.delFromDB = delFromDB; const clearTempDataState = (tag) => { const tTag = tag && tag instanceof DETypes_1.Tag ? tag : new DETypes_1.Tag(tag); const objMapPath = tTag.path; dataStateMap.delete(objMapPath); dataTempStateMap.delete(objMapPath); }; exports.clearTempDataState = clearTempDataState; const clearDataState = (database, tag) => { return new Promise(async (resolve, reject) => { try { if ((!database || database === "*") && !tag) { dataStateMap.clear(); dataTempStateMap.clear(); const store = (0, exports.getStore)(database); await (0, idb_keyval_1.clear)(store); } else if (tag) { const tTag = tag && tag instanceof DETypes_1.Tag ? tag : new DETypes_1.Tag(tag); const objMapPath = tTag.path; dataStateMap.delete(objMapPath); dataTempStateMap.delete(objMapPath); const store = (0, exports.getStore)(database); await (0, idb_keyval_1.del)(tTag.path, store); } else { const allDdbkeys = Array.from(dataStateMap.keys()).filter(k => k.startsWith(`${database ?? '$'}.`)); allDdbkeys.forEach(k => { dataStateMap.delete(k); dataTempStateMap.delete(k); }); await (0, exports.clearDatabase)(database); } return resolve(); } catch (err) { return reject(err); } }); }; exports.clearDataState = clearDataState; /** * resolve目标状态映射 * 说明:键为当前状态,值为等待的兼容状态,即所有待等的兼容状态都可以认为目标达成 * 需要考虑的状态:Deleted,Ready,Failed,Timeout */ const resolveStatusMap = { [DETypes_1.ObjectStatus.Ready]: [DETypes_1.ObjectStatus.Deleted], [DETypes_1.ObjectStatus.Deleted]: [DETypes_1.ObjectStatus.Ready], [DETypes_1.ObjectStatus.Failed]: [DETypes_1.ObjectStatus.Timeout] }; const resolveStatus = (targetStatus) => { const resStatus = [...(resolveStatusMap[targetStatus] ?? []), targetStatus]; return resStatus; }; /** * reject目标状态映射 * 说明:键为当前状态,值为相反状态,当到达当前状态时,值状态为reject. * 需要考虑的状态:Deleted,Ready,Failed,Timeout */ const rejectStatusMap = { [DETypes_1.ObjectStatus.Ready]: [DETypes_1.ObjectStatus.Failed, DETypes_1.ObjectStatus.Timeout], [DETypes_1.ObjectStatus.Failed]: [DETypes_1.ObjectStatus.Ready, DETypes_1.ObjectStatus.Loading], [DETypes_1.ObjectStatus.Timeout]: [DETypes_1.ObjectStatus.Ready, DETypes_1.ObjectStatus.Loading] }; const rejectStatus = (targetStatus) => { const rejStatus = rejectStatusMap[targetStatus] ?? []; return rejStatus; }; const statusWaitersMap = new Map(); /** * 数据对象类 * 实现了 IData 接口,提供数据的存储、加载、更新和状态管理功能 */ class DEData { /** * 数据对象的标签 */ tag; /** 自动保存定时器ID */ #autoSaveTimerId; /** 存储对象 */ #storeObj; /** 当前数据 */ #data = {}; /** 临时状态 */ #tempState = null; /** 配置选项 */ #options; /** 临时数据 */ #temp = {}; /** 脏数据 */ #dirty = null; /** 是否有图像数据 */ #hasImage = false; /** * 构造函数 * @param tag 数据标签 * @param options 配置选项 * @param image 图像数据 */ constructor(tag, options, image) { if (tag instanceof DETypes_1.Tag) { this.tag = tag; } else { this.tag = new DETypes_1.Tag(tag); } if (!dataStateMap.has(this.tag.path)) { dataStateMap.set(this.tag.path, this); } if (!statusWaitersMap.has(this.tag.path)) { statusWaitersMap.set(this.tag.path, new Map()); } this.#options = options; if (this.#options.state) { this.#tempState = this.#options.state; } if (image) { this.#hasImage = true; this.#storeObj = image; } this.#init(); } /** * 获取脏数据路径 */ get dirtyDataPath() { return `${this.#options.database ?? '*'}.${this.tag.path}`; } /** * 获取状态等待器 */ get statusWaiters() { return statusWaitersMap.get(this.tag.path); } /** * 初始化数据对象 */ #init = () => { if (!this.#options.loadingMode) { this.#options.loadingMode = 'localDB'; } if (this.options.saveDirtyData === true) { this.#loadDirtyDataFromSesstion(); } //加载临时状态 const tempState = dataTempStateMap.get(this.tag.path); if (tempState) { /** 有临时存储和本地存储,则以临时存储优先进行合并 */ this.#storeObj = { ...this.#storeObj, ...tempState }; if (this.#options.default) { this.#storeObj.default = (0, lodash_1.assign)(tempState.default ?? {}, this.#options.default); } try { if (!this.#storeObj.expires && this.options.expireTime) { this.#storeObj.expires = new Date().valueOf() + this.options.expireTime; } if (this.#storeObj.status === DETypes_1.ObjectStatus.Ready) { this.#storeObj.status = DETypes_1.ObjectStatus.Loading; dataTempStateMap.delete(this.tag.path); this.#setStatus(DETypes_1.ObjectStatus.Ready); this.#mergeData(); return; } if (this.#options.autoLoad !== false) { this.load(); } } catch (err) { (0, DEHub_1.error)(this, err); } } else { const dataObj = this.#storeObj ?? { data: {}, state: {}, status: DETypes_1.ObjectStatus.New, default: this.#options.default, virtual: {}, expires: new Date().valueOf() + (this.#options.expireTime ?? 5000), retryCount: 0 }; this.#storeObj = dataObj; if (this.#options.autoLoad !== false) { this.load(); } } this.#mergeData(); }; /** * 保存数据 * @param immediate 是否立即保存 */ #save = (immediate = false) => { this.#mergeData(); return new Promise(async (resolve, reject) => { if (Object.values(this.#storeObj.data).length === 0 && Object.values(this.#storeObj.virtual).length === 0) { return resolve(); } const saveAction = async () => { if (this.#options.loadingMode !== 'none') { try { await (0, idb_keyval_1.set)(this.tag.path, this.#storeObj, this.database); this.#tempState = null; } catch (err) { return reject(err); } } }; clearTimeout(this.#autoSaveTimerId); if (immediate) { await saveAction(); } else { this.#autoSaveTimerId = setTimeout(saveAction, 3000); } return resolve(); }); }; /** * 重试计数器ID */ #retryLaterId; /** * 延迟重试 * 当操作失败时,根据配置的重试间隔和次数进行重试 */ #retryLater = () => { if (!this.#options.retryInterval) { return; } const retryLimit = this.#options.retryLimit ?? 5; if ((this.#storeObj.retryCount ?? 0) > retryLimit) { return; } this.#storeObj.retryCount = (this.#storeObj.retryCount ?? 0) + 1; clearTimeout(this.#retryLaterId); this.#retryLaterId = setTimeout(() => { switch (this.#storeObj.retryStatus) { case DETypes_1.ObjectStatus.New: case DETypes_1.ObjectStatus.Loading: this.#storeObj.status = DETypes_1.ObjectStatus.New; this.load(true); break; case DETypes_1.ObjectStatus.Ready: clearTimeout(this.#retryLaterId); break; } }, this.#options.retryInterval * this.#storeObj.retryCount); }; /** * 获取过期时间 */ get expires() { return this.#storeObj.expires; } /** * 获取数据键列表 */ get keys() { return Object.keys(this.data); } /** * 获取默认值 */ get default() { if (!this.#storeObj.default) { this.#storeObj.default = {}; } return this.#storeObj.default; } /** * 获取状态 */ get state() { if (this.status !== DETypes_1.ObjectStatus.Ready || this.#tempState != null) { return (0, lodash_1.assign)(this.#storeObj.state ?? {}, this.#tempState); } return this.#storeObj.state; } /** * 更新状态 * @param state 新状态 */ updateState = async (state) => { if (this.status !== DETypes_1.ObjectStatus.Ready && state != null) { this.#tempState = state; } this.#storeObj.state = (0, lodash_1.assign)(this.#storeObj.state ?? {}, state); const emitContext = { sender: this, detail: { value: state }, event: DETypes_1.EventNames.StateChanging, stage: DETypes_1.EventStage.PostOperation, property: 'state' }; const [emitResut, emitPromises] = (0, DEHub_1.emit)(emitContext); if (emitResut.cancel !== true) { if (emitPromises && emitPromises.length > 0) { const asyncResults = await Promise.allSettled(emitPromises); (0, DETypes_1.rejectedCheck)(asyncResults); } } this.#tempState = null; }; /** * 获取存储对象 */ get storeObject() { return this.#storeObj; } /** * 设置存储对象 */ set storeObject(state) { this.#storeObj = state; if (this.storeObject.expires && this.storeObject.expires < new Date().valueOf()) { this.#retryLater(); } } /** * 等待数据达到指定状态 * @param status 目标状态 */ until = (status) => { return new Promise((resolve, reject) => { const canRes = resolveStatus(status); if (canRes.indexOf(this.status) >= 0) { return resolve(); } const canRej = rejectStatus(status); if (canRej && canRej.indexOf(this.status) >= 0) { return reject(); } const promiseGroup = this.statusWaiters.get(status) ?? []; promiseGroup?.push([resolve, reject]); this.statusWaiters.set(status, promiseGroup); }); }; /** * 重置数据 */ reset = async () => { await this.until(DETypes_1.ObjectStatus.Ready); await this.#setStatus(DETypes_1.ObjectStatus.Reset); if (this.options.saveDirtyData === true) { this.dirty = null; } this.#dirty = {}; sessionStorage.removeItem(this.dirtyDataPath); await this.#setStatus(DETypes_1.ObjectStatus.Ready); this.#mergeData(); }; /** * 清空数据 */ clear = async () => { await this.until(DETypes_1.ObjectStatus.Ready); await this.#setStatus(DETypes_1.ObjectStatus.Clear); sessionStorage.removeItem(this.dirtyDataPath); this.#storeObj.data = {}; await (0, idb_keyval_1.del)(this.tag.path, this.database); await this.#setStatus(DETypes_1.ObjectStatus.New); this.#mergeData(); }; /** * 获取数据库实例 */ get database() { const db = (0, exports.getStore)(this.#options.database); return db; } /** * 获取属性值 * @param attribute 属性名 */ get = (attribute) => { return this.data[attribute]; }; /** * 获取配置选项 */ get options() { return this.#options; } /** * 更新配置选项 * @param options 新的配置选项 */ updateOption(options) { this.#options = options; if (options && options.default) { this.#storeObj.default = options.default; } if (options && options.state) { this.#tempState = options.state; } this.#mergeData(); } #customLoadData = () => { return new Promise(async (resolve, reject) => { const emitContext = { sender: this, status: DETypes_1.ObjectStatus.Loading, event: DETypes_1.EventNames.Submit, stage: DETypes_1.EventStage.PreOperation, database: this.#options.database }; try { const [loadingContext, loadingTasks] = (0, DEHub_1.emit)(emitContext); if (loadingContext.cancel !== true) { if (loadingTasks && loadingTasks.length > 0) { const asyncResults = await Promise.allSettled(loadingTasks); (0, DETypes_1.rejectedCheck)(asyncResults); } } else { this.#setStatus(DETypes_1.ObjectStatus.Ready); return reject(loadingContext); } //获取输出结果 if (loadingContext.output?.data) { this.original = loadingContext.output.data; } //获取提供的默认值 if (loadingContext.output?.default) { this.#storeObj.default = loadingContext.output.default; } //获取状态 if (loadingContext.output?.state) { await this.updateState(loadingContext.output.state); } this.#mergeData(); const postContext = { ...emitContext, stage: DETypes_1.EventStage.PostOperation }; (0, DEHub_1.emit)(postContext); return resolve(); } catch (err) { (0, DEHub_1.error)(this, err); return reject(err); } }); }; /** * 加载数据 * 从 IndexedDB 中获取数据,更新数据对象的状态 */ #loadingPromise = undefined; #loadingTimeoutId = null; load = (force = false) => { if ((!force && this.#loadingPromise) || (force && this.#loadingTimeoutId && this.#loadingPromise)) { /** !PATCH 补丁:意外的一直无法返回情况*/ setTimeout(() => { if (this.status === DETypes_1.ObjectStatus.New) { this.#loadDataProcess(force).finally(() => this.#cleanupLoaderResource()); } }, 500); return this.#loadingPromise; } const loadPromise = this.#loadDataProcess(force).finally(() => this.#cleanupLoaderResource()); this.#loadingPromise = loadPromise; return this.#loadingPromise; }; #cleanupLoaderResource() { if (this.#loadingTimeoutId) { clearTimeout(this.#loadingTimeoutId); this.#loadingTimeoutId = undefined; } this.#loadingPromise = undefined; } #loadDataProcess = (force) => new Promise(async (resolve, reject) => { const loadingMode = this.#options.loadingMode; switch (loadingMode) { case 'localDB': case 'custom': try { const tempData = dataTempStateMap.get(this.tag.path); let localData = null; if (tempData && this.#hasImage) { localData = this.#storeObj; } else if (tempData) { localData = tempData; } else if (this.#hasImage) { localData = this.#storeObj; } else { localData = await (0, exports.getFromDB)(this.tag, this.#options.database); } if (!force && !tempData && this.#hasImage !== true && this.status === DETypes_1.ObjectStatus.Loading) { await this.until(DETypes_1.ObjectStatus.Ready); return resolve(this.data); } if (localData) { !tempData && (this.storeObject = localData); if (this.#options.default && !(0, lodash_1.isEqual)(this.#storeObj.default, this.#options.default)) { this.#storeObj.default = (0, lodash_1.merge)(this.default, this.#options.default); } if (this.storeObject.expires && this.storeObject.expires < new Date().valueOf()) { force = true; } else { switch (this.#storeObj.status) { case DETypes_1.ObjectStatus.New: force = true; break; case DETypes_1.ObjectStatus.Ready: if (!force) { if (this.expires && this.expires < new Date().valueOf()) { this.#customLoadData(); } this.#storeObj.status = DETypes_1.ObjectStatus.Loading; this.#mergeData(); await this.#setStatus(DETypes_1.ObjectStatus.Ready); return resolve(this.data); } break; case DETypes_1.ObjectStatus.Loading: if (!force || this.#loadingTimeoutId) { await this.until(DETypes_1.ObjectStatus.Ready); return resolve(this.data); } break; case DETypes_1.ObjectStatus.Deleted: this.#storeObj.status = DETypes_1.ObjectStatus.Deleting; await this.#setStatus(DETypes_1.ObjectStatus.Deleted); return reject(`数据${this.tag.path}已删除`); default: this.#storeObj.status = DETypes_1.ObjectStatus.New; force = true; break; } } } await this.#setStatus(DETypes_1.ObjectStatus.Loading); if (this.#loadingTimeoutId) { await this.until(DETypes_1.ObjectStatus.Ready); return resolve(this.data); } if (!localData || force === true) { this.#loadingTimeoutId = setTimeout(() => { this.#storeObj.status === DETypes_1.ObjectStatus.Loading && (this.#setStatus(DETypes_1.ObjectStatus.Timeout)); this.#cleanupLoaderResource(); }, this.#options.timeout || LOADING_TIMEOUT); await this.#customLoadData(); } this.#cleanupLoaderResource(); setTimeout(() => { dataTempStateMap.delete(this.tag.path); }, TEMP_DATA_CLEANUP_DELAY); this.#mergeData(); await this.#setStatus(DETypes_1.ObjectStatus.Ready); return resolve(this.data); } catch (reason) { this.#cleanupLoaderResource(); console.error(reason); this.#setStatus(DETypes_1.ObjectStatus.Failed); return reject(reason); } default: /** !PATCH:解决其他线程提前完成了Ready */ if (this.status === DETypes_1.ObjectStatus.Ready) { setTimeout(() => { dataTempStateMap.delete(this.tag.path); }, TEMP_DATA_CLEANUP_DELAY); return resolve(this.data); } this.#setStatus(DETypes_1.ObjectStatus.Loading); try { await this.#customLoadData(); this.#mergeData(); await this.#setStatus(DETypes_1.ObjectStatus.Ready); return resolve(this.data); } catch (err) { await this.#setStatus(DETypes_1.ObjectStatus.Failed); return reject(err); } } }); /** * 设置数据对象的状态 * @param statusValue 数据对象的状态 */ #setStatus = (statusValue) => { var preStatus = this.status; if (statusValue === DETypes_1.ObjectStatus.Ready && this.#tempState) { this.#storeObj.state = (0, lodash_1.assign)(this.#storeObj.state, this.#tempState); } return new Promise(async (resolve, reject) => { if (preStatus === statusValue) { return resolve(); } this.storeObject.status = statusValue; if ([DETypes_1.ObjectStatus.Ready, DETypes_1.ObjectStatus.Deleting, DETypes_1.ObjectStatus.Loading, DETypes_1.ObjectStatus.Uploading, DETypes_1.ObjectStatus.Timeout, DETypes_1.ObjectStatus.Failed].indexOf(statusValue) >= 0) { try { await this.#save(true); } catch (err) { (0, DEHub_1.error)(this, err); return reject(err); } } //console.log(preStatus,this.state.status); if ([DETypes_1.ObjectStatus.Failed, DETypes_1.ObjectStatus.Timeout].indexOf(statusValue) >= 0) { this.storeObject.retryStatus = preStatus; this.#retryLater(); } if ([DETypes_1.ObjectStatus.Deleted, DETypes_1.ObjectStatus.Ready, DETypes_1.ObjectStatus.Failed, DETypes_1.ObjectStatus.Timeout].indexOf(statusValue) >= 0) { /** 处理statusWaiters 的resolve情况 */ const resStatus = resolveStatus(statusValue); for (const tStatus of resStatus) { const promiseGroup = this.statusWaiters.get(tStatus) ?? []; if (promiseGroup.length === 0) { continue; } for (const [resStateResolve] of promiseGroup) { try { resStateResolve(); } catch (err) { (0, DEHub_1.error)(this, err); } } this.statusWaiters.delete(tStatus); } /** 处理statusWaiters 的reject情况 */ const rejStatus = this.statusWaiters.keys(); for (const tStatus of rejStatus) { const promiseGroup = this.statusWaiters.get(tStatus) ?? []; promiseGroup.length > 0 && promiseGroup.forEach((promiseItem) => { const resStateReject = promiseItem[1]; resStateReject(`${this.tag.path}的状态已变更为${statusValue}`); }); this.statusWaiters.delete(tStatus); } } if (this.#storeObj.retryCount && this.#storeObj.retryCount > 0 && [DETypes_1.ObjectStatus.Deleted, DETypes_1.ObjectStatus.Ready].indexOf(statusValue) >= 0) { this.#storeObj.retryCount = 0; } //执行状态变更后消息推送; const updateArgs = { value: statusValue, preImage: preStatus, postImage: statusValue }; const emitContext = { sender: this, status: statusValue, event: DETypes_1.EventNames.StatusChanging, stage: DETypes_1.EventStage.PostOperation, detail: updateArgs, database: this.#options.database }; try { const [emitResult, emitPromises] = (0, DEHub_1.emit)(emitContext); emitPromises && (0, DETypes_1.rejectedCheck)(await Promise.allSettled(emitPromises)); return resolve(); } catch (err) { console.error(err); return reject(err); } }); }; /** * 获取数据对象的状态 * @returns 数据对象的状态 */ get status() { if (this.#loadingTimeoutId != null && !this.#loadingPromise && this.storeObject.status === DETypes_1.ObjectStatus.Loading) { return DETypes_1.ObjectStatus.New; } return this.storeObject.status; } get original() { const original = (0, lodash_1.assign)({ ...this.default }, { ...this.storeObject.data }); return original; } set original(original) { this.storeObject.data = original; this.storeObject.expires = new Date().valueOf() + (this.#options.expireTime ?? 10000); } #mergeData = () => { if (this.#tempState) { const newState = (0, lodash_1.assign)(this.#storeObj.state ?? {}, this.#tempState); this.#storeObj.state = newState; } this.#data = (0, lodash_1.assign)({ ...this.virtual }, (0, lodash_1.assign)({ ...this.original }, (0, lodash_1.assign)({ ...this.dirty }, this.#temp))); }; get data() { return this.#data; } #loadDirtyDataFromSesstion = () => { const dirtyStr = sessionStorage.getItem(this.dirtyDataPath); if (!dirtyStr) { return {}; } try { const dirtyObj = JSON.parse(dirtyStr); this.#dirty = dirtyObj; return dirtyObj; } catch (err) { (0, DEHub_1.error)(this, err); return {}; } }; get dirty() { return this.#dirty ?? {}; } set dirty(dirty) { if (this.#options.saveDirtyData === true) { try { if (dirty == null) { sessionStorage.removeItem(this.dirtyDataPath); } else { const newDirty = (0, DETypes_1.clearNullNodes)((0, lodash_1.assign)(this.#dirty, dirty)); this.#dirty = newDirty; sessionStorage.setItem(this.dirtyDataPath, JSON.stringify(newDirty)); this.#mergeData(); } } catch (err) { (0, DEHub_1.error)(this, `数据${JSON.stringify(this.tag)}转换值失败`); throw err; } } else { this.#dirty = (0, lodash_1.assign)(this.#dirty, dirty); } } get virtual() { return this.#storeObj.virtual; } /** * 设置属性值 * @param attribute 属性名 * @param value 属性值 * @returns 更新后的数据对象 */ #setActionVer = 0; #attributeVer = new Map(); set = (attribute, value, isVirtual) => { const setVer = this.#setActionVer++; return new Promise(async (resolve, reject) => { if ((0, lodash_1.isEqual)(this.data[attribute], value)) { return resolve(value); } const preValue = this.data[attribute]; this.#temp[attribute] = value; this.#attributeVer.set(attribute, setVer); this.#mergeData(); const target = { [attribute]: value }; const dataContext = { value: target, preImage: { [attribute]: preValue } }; const emitContext = { sender: this, attribute, event: DETypes_1.EventNames.ValueChanged, stage: DETypes_1.EventStage.PreOperation, detail: dataContext, database: this.#options.database }; try { const [setvalueContext, setValuePromoses] = (0, DEHub_1.emit)(emitContext); if (setvalueContext.cancel === true) { return reject(setvalueContext); } if (setvalueContext.output.value) { this.#temp[attribute] = setvalueContext.output.value; dataContext.value = setvalueContext.output.value; value = setvalueContext.output.value; target[attribute] = setvalueContext.output.value; } (0, DETypes_1.rejectedCheck)(await Promise.allSettled(setValuePromoses)); const mergedValue = (0, lodash_1.assign)((isVirtual === true ? this.virtual : this.dirty) ?? {}, target); if (this.#attributeVer.get(attribute) !== setVer) { mergedValue[attribute] = this.data[attribute]; } if (isVirtual === true) { if (this.#storeObj.data[attribute] !== undefined) { delete this.#storeObj.data[attribute]; } this.#storeObj.virtual = mergedValue; } else { this.dirty = mergedValue; } this.#mergeData(); } catch (err) { return reject(err); } finally { delete this.#temp[attribute]; } dataContext.postImage = { [attribute]: this.data[attribute] }; const postContext = { ...emitContext, stage: DETypes_1.EventStage.PostOperation }; try { const [postResult, postFuncs] = (0, DEHub_1.emit)(postContext); if (postFuncs && postFuncs.length > 0) { (0, DETypes_1.rejectedCheck)(await Promise.allSettled(postFuncs)); } resolve(this.data[attribute]); } catch (err) { (0, DEHub_1.error)(this, err); reject(err); } }); }; /** * 更新数据对象 * * 相当于批量的set操作; * @param target * @returns */ update = (data, noTrigger = false) => { let updatedObj = {}; const target = data.data; for (var att of Object.keys(target)) { var attVal = target[att]; if ((0, lodash_1.isEqual)(attVal, this.get(att))) { continue; } if (noTrigger === true) { this.#storeObj.data[att] = attVal; } else { this.set(att, attVal); } updatedObj[att] = attVal; } this.#save(true); return updatedObj; }; /** * 提交数据 * 将脏数据保存到 IndexedDB 中,并清空脏数据,更新数据对象的状态 * @param target 强制提交的数据 * @param mergeData 是否合并,默认为合并,即提交后将更新部分与原数据进行合并; * @returns */ //#submitPromise = new Map<number,Promise<any>>(); submit = (target = {}, mergeData = true) => { return new Promise(async (resolve, reject) => { //提交的条件:1,有修改的数据(dirty);2,有强制提交的数据(target); if (Object.values(this.dirty).length === 0 && Object.keys(target).length === 0) { await this.#save(); await this.#setStatus(DETypes_1.ObjectStatus.Ready); return resolve(target); } try { await this.#setStatus(DETypes_1.ObjectStatus.Uploading); } catch (err) { await this.#setStatus(DETypes_1.ObjectStatus.Ready); return reject(err); } const submitValue = { ...this.dirty, ...target }; const preImage = { ...this.original }; const updateArgs = { value: submitValue, preImage: preImage }; const emitContext = { sender: this, event: DETypes_1.EventNames.Submit, status: DETypes_1.ObjectStatus.Uploading, stage: DETypes_1.EventStage.PreOperation, detail: updateArgs, database: this.#options.database }; let submitTimerId; try { if (this.#options.loadingMode === 'custom') { submitTimerId = setTimeout(() => { this.status === DETypes_1.ObjectStatus.Uploading && (this.#setStatus(DETypes_1.ObjectStatus.Timeout)); return reject(`timeout`); }, this.#options.timeout || 15000); } const [eventContext, emitPromises] = (0, DEHub_1.emit)(emitContext); const asyncResults = await Promise.allSettled(emitPromises); (0, DETypes_1.rejectedCheck)(asyncResults); const updatedData = eventContext.output.data ?? updateArgs.value; this.original = mergeData ? (0, DETypes_1.mergeObject)(this.original, updatedData) : updatedData; this.#dirty = {}; if (this.options.saveDirtyData === true) { this.dirty = null; } sessionStorage.removeItem(this.dirtyDataPath); await this.#save(true); await this.#setStatus(DETypes_1.ObjectStatus.Ready); } catch (err) { await this.#setStatus(DETypes_1.ObjectStatus.Failed); return reject(err); } finally { clearTimeout(submitTimerId); } updateArgs.postImage = { ...this.original }; const postContext = { ...emitContext, stage: DETypes_1.EventStage.PostOperation }; try { const [postResult, postTasks] = (0, DEHub_1.emit)(postContext); if (postTasks && postTasks.length > 0) { (0, DETypes_1.rejectedCheck)(await Promise.allSettled(postTasks)); } return resolve(updateArgs.postImage); } catch (err) { return reject(err); } }); }; delete = () => { return new Promise(async (resolve, reject) => { try { await this.until(DETypes_1.ObjectStatus.Ready); await this.#setStatus(DETypes_1.ObjectStatus.Deleting); } catch (err) { return reject(err); } const updateArgs = { value: { ...this.dirty }, preImage: { ...this.original } }; const emitContext = { sender: this, event: DETypes_1.EventNames.Submit, status: DETypes_1.ObjectStatus.Deleting, stage: DETypes_1.EventStage.PreOperation, detail: updateArgs, database: this.#options.database }; try { const [eventContext, emitPromises] = (0, DEHub_1.emit)(emitContext); emitPromises && await Promise.all(emitPromises); if (eventContext.cancel !== true) { if (this.#options.loadingMode !== 'none') { await (0, idb_keyval_1.del)(this.tag.path, this.database); sessionStorage.removeItem(this.dirtyDataPath); dataStateMap.delete(this.tag.path); } await this.#setStatus(DETypes_1.ObjectStatus.Deleted); } else { await this.#setStatus(DETypes_1.ObjectStatus.Ready); reject(eventContext); } this.#mergeData(); } catch (err) { await this.#setStatus(DETypes_1.ObjectStatus.Ready); return reject(err); } const postContext = { ...emitContext, stage: DETypes_1.EventStage.PostOperation }; try { const [postResult, postPromises] = (0, DEHub_1.emit)(postContext); postPromises && await Promise.all(postPromises); return resolve(); } catch (err) { return reject(err); } }); }; } /** * 获取数据对象 * @param tag 数据标签 * @returns 数据对象 */ const getData = (tag) => { let targetData; if (tag instanceof DETypes_1.Tag) { targetData = dataStateMap.get(tag.path); } else if (typeof tag === 'string') { targetData = dataStateMap.get(tag); } else { targetData = dataStateMap.get(DETypes_1.TagsSerializer.toPath(tag)); } /**!PATCH 对可能的异常加载做再次执行加载操作; */ // if (targetData && targetData.status == ObjectStatus.Loading) { // targetData.load(); // } return targetData; }; exports.getData = getData; /** * 获取或创建数据对象 * @param tag 数据标签 * @param options 配置选项 * @param waitToReady 是否等待就绪 * @returns 数据对象 */ const fetchData = async (tag, options, waitToReady = false) => { let tTag; if (tag instanceof DETypes_1.Tag) { tTag = tag; } else if (typeof tag === 'string') { tTag = new DETypes_1.Tag(DETypes_1.TagsSerializer.fromPath(tag)); } else { tTag = new DETypes_1.Tag(tag); } /** 获取存在已创建那的数据对象 */ let dataTarget = dataStateMap.get(tTag.path); if (dataTarget != null) { (dataTarget.status === DETypes_1.ObjectStatus.New || dataTarget.status === DETypes_1.ObjectStatus.Failed || dataTarget.status === DETypes_1.ObjectStatus.Reset || dataTarget.status === DETypes_1.ObjectStatus.Timeout) && dataTarget.load(); waitToReady && await dataTarget.until(DETypes_1.ObjectStatus.Ready); if (options) { /** 如果提供了新的数据配置,则更新配置; */ dataTarget.updateOption((0, DETypes_1.mergeObject)((dataTarget.options ?? {}), options)); } return dataTarget; } dataTarget = dataStateMap.get(tTag.path); let targetOptions = { ...((dataTarget ? dataTarget.options : options) ?? {}) }; const emitSender = { tag: tTag, options: targetOptions, image: dataTarget ? dataTarget.data : null }; const emitContext = { sender: emitSender, detail: emitSender, status: DETypes_1.ObjectStatus.New, event: DETypes_1.EventNames.StatusChanging, stage: DETypes_1.EventStage.PreOperation, database: targetOptions.database }; let prePromiseResults = []; let emitResult = null; let preEmitPromises = []; try { [emitResult, preEmitPromises] = (0, DEHub_1.emit)(emitContext); if (preEmitPromises.length > 0) { prePromiseResults = await Promise.allSettled(preEmitPromises); (0, DETypes_1.rejectedCheck)(prePromiseResults); } } catch (err) { (0, DEHub_1.error)(emitSender, err); throw err; } if (emitResult.output.options) { targetOptions = (0, lodash_1.assign)(targetOptions, emitResult.output.options); } let defaultValue = targetOptions.default ?? {}; if (emitResult.output.default) { defaultValue = (0, lodash_1.assign)(defaultValue ?? {}, emitResult.output.default ?? {}); targetOptions.default = defaultValue; } //预加载状态; if (emitResult.output.state) { targetOptions.state = emitResult.output.state; } let localState; if (targetOptions?.loadingMode === 'none') { localState = undefined; } else { localState = await (0, exports.getFromDB)(tTag, targetOptions?.database); } try { dataTarget = (0, exports.getData)(tTag); if (!dataTarget) { if (localState?.status === DETypes_1.ObjectStatus.Loading) { localState.status = DETypes_1.ObjectStatus.New; } dataTarget = new DEData(tTag, targetOptions ?? { loadingMode: 'localDB' }, localState); } const postEmitContent = { ...emitContext, sender: dataTarget, stage: DETypes_1.EventStage.PostOperation }; const [postEmitResult, postEmitPromises] = (0, DEHub_1.emit)(postEmitContent); if (postEmitPromises.length > 0) { const postPromiseResults = await Promise.allSettled(postEmitPromises); (0, DETypes_1.rejectedCheck)(postPromiseResults); } } catch (err) { (0, DEHub_1.error)(emitSender, err); throw err; } //预加载本地数据 dataTarget = (0, exports.getData)(tTag); if (waitToReady === true) { await dataTarget.until(DETypes_1.ObjectStatus.Ready); } return dataTarget; }; exports.default = fetchData;