"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RunningTree = exports.MODI_NEXT_PATH_SUFFIX = void 0; const assert_1 = require("oak-domain/lib/utils/assert"); const lodash_1 = require("oak-domain/lib/utils/lodash"); const filter_1 = require("oak-domain/lib/store/filter"); const modi_1 = require("oak-domain/lib/store/modi"); const relation_1 = require("oak-domain/lib/store/relation"); const types_1 = require("oak-domain/lib/types"); const Feature_1 = require("../types/Feature"); const uuid_1 = require("oak-domain/lib/utils/uuid"); exports.MODI_NEXT_PATH_SUFFIX = ':next'; const START_LSN = 100; function mergeOperation(schema, entity, oper1, oper2) { const { action, data } = oper2; const operMerged = (0, lodash_1.cloneDeep)(oper1); (0, assert_1.assert)(action !== 'create'); if (action === 'remove') { if (oper1.action == 'create') { // 操作抵消 return null; } else { operMerged.action = 'remove'; operMerged.data = (0, lodash_1.cloneDeep)(data); } } else { if (action !== 'update') { if (oper1.action === 'update') { oper1.action = action; } else { (0, assert_1.assert)(action === oper1.action, '不应当有冲突的update模式'); } } const { data: dataMerged } = operMerged; // merge 两者的data,要处理cascade部分 for (const attr in data) { const rel = (0, relation_1.judgeRelation)(schema, entity, attr); if (rel === 1) { dataMerged[attr] = (0, lodash_1.cloneDeep)(data[attr]); } else if (rel === 2) { if (dataMerged[attr]) { const merged = mergeOperation(schema, attr, dataMerged[attr], data[attr]); if (!merged) { (0, lodash_1.unset)(dataMerged, attr); } else { dataMerged[attr] = merged; } } else { dataMerged[attr] = (0, lodash_1.cloneDeep)(data[attr]); } } else if (typeof rel === 'string') { if (dataMerged[attr]) { const merged = mergeOperation(schema, rel, dataMerged[attr], data[attr]); if (!merged) { (0, lodash_1.unset)(dataMerged, attr); } else { dataMerged[attr] = (0, lodash_1.cloneDeep)(merged); } } else { dataMerged[attr] = data[attr]; } } else { (0, assert_1.assert)(rel instanceof Array); if (!dataMerged[attr]) { dataMerged[attr] = (0, lodash_1.cloneDeep)(data[attr]); } else { // 这条路径在前台跑出来的情况现在还不好断言,先赋值,再调试 process.env.NODE_ENV === 'development' && console.warn('need check by Xc'); dataMerged[attr] = (0, lodash_1.cloneDeep)(data[attr]); } } } } return operMerged; } /** * 树结点上的更新日志管理 */ class UpdateLogManager { logs = []; schema; entity; maxLsn; constructor(schema, entity, maxLsn) { this.schema = schema, this.entity = entity, this.maxLsn = maxLsn || START_LSN; } /** * 合并两个filter完全一致的更新 * @param oper1 * @param oper2 */ mergeOperation(oper1, oper2) { return mergeOperation(this.schema, this.entity, oper1, oper2); } /** * 增加一条更新日志记录 * @param lsn * @param action * @param data * @param filter */ push(lsn, oper2) { // 插入到最后一次update中 const { action, filter, data } = oper2; let ids = action === 'create' ? [data.id] : (0, filter_1.getRelevantIds)(filter); const key = JSON.stringify(ids.length > 0 ? ids : filter); if (this.logs.length > 0) { const update = this.logs[this.logs.length - 1]; (0, assert_1.assert)(update.lsn <= lsn); if (update.lsn === lsn) { // 如果是同一个lsn,尝试合并 const { operations: logs } = update; if (logs[key]) { const newOper = this.mergeOperation(logs[key], oper2); if (!newOper) { (0, lodash_1.unset)(logs, key); } else { logs[key] = newOper; } return; } logs[key] = { id: (0, uuid_1.generateNewId)(), action, data, filter, }; return; } } this.logs.push({ lsn, operations: { [key]: { id: (0, uuid_1.generateNewId)(), action, data, filter, } }, }); if (lsn > this.maxLsn) { this.maxLsn = lsn; } } undo(filter) { const ids = (0, filter_1.getRelevantIds)(filter); const key = JSON.stringify(ids.length > 0 ? ids : filter); this.logs.forEach((log) => { const { operations } = log; (0, lodash_1.unset)(operations, key); }); } /** * 将lsn大于传入lsn的日志全部回滚 * @param lsn */ rollback(lsn) { const newLogs = this.logs.filter(ele => ele.lsn <= lsn); this.logs = newLogs; this.maxLsn = lsn || START_LSN; } makeOperations() { const opers = {}; ; for (const log of this.logs) { const { operations } = log; for (const key in operations) { if (opers[key]) { const newOper = this.mergeOperation(opers[key], operations[key]); if (newOper) { opers[key] = newOper; } else { (0, lodash_1.unset)(opers, key); } } else { opers[key] = (0, lodash_1.cloneDeep)(operations[key]); } } } return Object.values(opers); } isEmpty() { return this.logs.length === 0; } } class Node extends Feature_1.Feature { zombie = false; stale; fullPath; /** * 当前有几个组件正在使用这个结点 */ count = 0; executing = false; dirty; extraData = {}; refreshing = 0; refreshCallbacks = []; constructor(fullPath) { super(); this.fullPath = fullPath; } isInModiNextBranch() { return this.fullPath.includes(exports.MODI_NEXT_PATH_SUFFIX); } increaseCount() { this.count++; return this.count; } decreaseCount() { this.count--; return this.count; } getCount() { return this.count; } setZombie(zombie) { this.zombie = zombie; } isZombie() { return this.zombie; } isDirty() { return !!this.dirty; } isLoading() { return !!this.refreshing; } isExecuting() { return this.executing; } setExecuting(executing) { this.executing = executing; this.publish(); } startRefreshing(num) { if (this.refreshing) { return new Promise((resolve, reject) => { this.refreshCallbacks.push([resolve, reject]); }); } this.refreshing = num; for (const child in this.children) { this.children[child].startRefreshing(num); } return num; } endRefreshing(num, error) { /** * 考虑树上可能有枝干是在父亲开始refresh之后才构造并开始自身的refresh * 这里就用magicNumber来区分一下 */ if (this.refreshing === num) { this.refreshing = 0; for (const child in this.children) { this.children[child].endRefreshing(num, error); } const callbacks = this.refreshCallbacks; this.refreshCallbacks = []; callbacks.forEach(([resolve, reject]) => { if (error) { reject(error); } else { resolve(); } }); } } saveExtraData(key, data) { this.extraData[key] = data; } loadExtraData(key) { return this.extraData[key]; } isStale() { return !!this.stale; } } class EntityNode extends Node { entity; // protected fullPath: string; schema; projection; // 只在Page层有 parent; cache; // protected createData?: CreateDataDef; ulManager; constructor(entity, schema, cache, fullPath, projection, parent) { super(fullPath); this.entity = entity; this.schema = schema; this.cache = cache; this.projection = projection; this.parent = parent; this.dirty = undefined; this.executing = false; this.ulManager = new UpdateLogManager(this.schema, this.entity); } getEntity() { return this.entity; } getSchema() { return this.schema; } setDirty() { if (!this.dirty) { this.dirty = true; } // 现在必须要将父结点都置dirty了再publish,因为整棵树是在一起redoOperation了 if (this.parent) { this.parent.setDirty(); } this.publish(); } getParent() { return this.parent; } getProjection() { const projection = typeof this.projection === 'function' ? this.projection() : (this.projection && (0, lodash_1.cloneDeep)(this.projection)); return projection; } setProjection(projection) { // assert(!this.projection); this.projection = projection; } judgeRelation(attr) { const attr2 = attr.split(':')[0]; // 处理attr:next return (0, relation_1.judgeRelation)(this.schema, this.entity, attr2); } } const DEFAULT_PAGINATION = { currentPage: 0, pageSize: 20, more: true, total: undefined, count: 0, }; class ListNode extends EntityNode { children = {}; filters; sorters; getTotal; pagination = { ...DEFAULT_PAGINATION }; sr = {}; loadingMore = false; syncHandler; setFiltersAndSortedApplied() { this.filters.forEach(ele => ele.applied = true); this.sorters.forEach(ele => ele.applied = true); for (const k in this.children) { this.children[k].setFiltersAndSortedApplied(); } } startLoading() { const number = Math.random() + 1; return this.startRefreshing(number); } checkIfClean(child) { if (!this.ulManager.isEmpty()) { return false; } for (const k in this.children) { if (this.children[k] === child) { continue; } if (this.children[k].isDirty()) { return false; } } return true; } getModiOperations() { if (this.schema[this.entity].toModi) { const ids = Object.keys(this.sr); if (ids.length > 0) { const modies = this.cache.get('modi', { data: { id: 1, targetEntity: 1, entity: 1, entityId: 1, iState: 1, action: 1, data: 1, filter: 1, }, filter: { entity: this.entity, entityId: { $in: ids, }, iState: 'active', }, sorter: [ { $attr: { $$createAt$$: 1, }, $direction: 'asc', } ], }); if (modies.length > 0) { return (0, modi_1.createOperationsFromModies)(modies); } } } const modiOperations = []; for (const c in this.children) { const child = this.children[c]; const result = child.getModiOperations(); if (result) { modiOperations.push(...result); } } if (modiOperations.length > 0) { return modiOperations; } } onCacheSync(records) { if (this.isLoading()) { return; } const tryAddRowToList = (id, filter) => { if (this.sr[id]) { return; } if (!filter || this.cache.checkFilterContains(this.entity, filter, { id, }, true)) { this.sr[id] = {}; // 如果有aggr怎么办,一般有aggr的页面不会出现这种情况,以后再说 if (typeof this.pagination.total === 'number') { this.pagination.total += 1; } if (typeof this.pagination.count === 'number') { this.pagination.count += 1; } } }; // let hasUpdated = false; for (const record of records) { const { a } = record; switch (a) { case 'c': { const { e, d } = record; if (e === this.entity) { const filters = this.constructFilters(true, true, true); const filter = filters && (0, filter_1.combineFilters)(e, this.schema, filters); if (d instanceof Array) { d.forEach((dd) => tryAddRowToList(dd.id, filter)); } else { tryAddRowToList(d.id, filter); } } // hasUpdated = true; break; } case 'r': { const { e, f } = record; if (e === this.entity) { if (!f) { this.sr = {}; this.pagination.total = 0; } else if (f.id) { // 绝大多数删除情况 // 后台一定会返回删除的ids (0, assert_1.assert)(f.id.$in instanceof Array); f.id.$in.forEach((id) => { if (this.sr.hasOwnProperty(id)) { (0, lodash_1.unset)(this.sr, id); if (typeof this.pagination.total === 'number') { this.pagination.total -= 1; } if (typeof this.pagination.count === 'number') { this.pagination.count -= 1; } } }); } else { for (const id in this.sr) { if (!f || this.cache.checkFilterContains(e, f, { id }, true)) { (0, lodash_1.unset)(this.sr, id); if (typeof this.pagination.total === 'number') { this.pagination.total -= 1; } if (typeof this.pagination.count === 'number') { this.pagination.count -= 1; } } } } } // hasUpdated = true; break; } case 's': { const { d } = record; for (const entity in d) { if (entity === this.entity) { const filters = this.constructFilters(true, true, true); const filter = filters && (0, filter_1.combineFilters)(entity, this.schema, filters); for (const id in d[entity]) { tryAddRowToList(id, filter); } } } break; } case 'u': default: { // 更新可能会引起本行不再满足条件 const { e, d, f } = record; if (e === this.entity) { const { id } = f; const ids = typeof id === 'string' ? [id] : id.$in; const currentIds = Object.keys(this.sr); const intersected = (0, lodash_1.intersection)(ids, currentIds); const diffed = (0, lodash_1.difference)(ids, currentIds); /** 检查原本就在的,现在还在不在, * 以及原本不在的,现在是不是满足条件了 * (后一种情况也可能原本就满足但不在当前的ids中,这时是判断不出来的,total+1可能不对,但是应该是较少的case)*/ const filters = this.constructFilters(true, true, true); if (intersected.length) { const rows = this.cache.get(this.entity, { data: { id: 1, }, filter: (0, filter_1.combineFilters)(this.entity, this.schema, [ ...(filters || []), { id: { $in: intersected, } } ]) }); const rowIds = rows.map(ele => ele.id); const missed = (0, lodash_1.difference)(intersected, rowIds); missed.forEach((id) => { (0, lodash_1.unset)(this.sr, id); if (this.pagination.total) { this.pagination.total--; } }); } if (diffed.length) { const filter = filters && (0, filter_1.combineFilters)(e, this.schema, filters); diffed.forEach((ele) => tryAddRowToList(ele, filter)); } } // hasUpdated = true; break; } } } // 现在在getFreshValue时,会判定this.sr中的id是否满足条件,所以不用再处理update行为了 /* if (hasUpdated) { const ids = Object.keys(this.sr); const { filter } = this.constructSelection(true, true, true); if (filter && ids.length > 0) { const filter2 = combineFilters(this.entity, this.cache.getSchema(), [filter, { id: { $in: ids, } }]); const rows = this.cache.get(this.entity, { data: { id: 1, }, filter: filter2, }); const ids2 = rows.map(ele => ele.id!); const illegalIds = difference(ids, ids2); illegalIds.forEach( (id) => unset(this.sr, id) ); } } */ } destroy() { this.cache.unbindOnSync(this.syncHandler); for (const k in this.children) { this.children[k].destroy(); } } constructor(entity, schema, cache, fullPath, projection, parent, path, filters, sorters, getTotal, pagination, stale) { super(entity, schema, cache, fullPath, projection, parent); this.filters = filters || []; this.sorters = sorters || []; this.getTotal = getTotal; this.sr = {}; this.pagination = pagination ? { ...pagination, currentPage: pagination.currentPage - 1, more: true, total: undefined, count: 0, getTotal } : { ...DEFAULT_PAGINATION, getTotal }; this.stale = stale; this.syncHandler = (records) => this.onCacheSync(records); this.cache.bindOnSync(this.syncHandler); if (parent) { (0, assert_1.assert)(path); parent.addChild(path, this); } } getPagination() { return this.pagination; } setPagination(pagination, dontRefresh) { const newPagination = Object.assign(this.pagination, pagination); this.pagination = newPagination; if (!dontRefresh) { this.refresh(0, false); } } isLoadingMore() { return this.loadingMore; } addChild(path, node) { (0, assert_1.assert)(!this.children[path]); // assert(path.length > 10, 'List的path改成了id'); this.children[path] = node; (0, assert_1.assert)(this.sr); // listNode的子结点不可能在取得数据前就建立吧 by Xc node.saveRefreshResult({ [path]: this.sr[path] || {}, }); } removeChild(path) { (0, lodash_1.unset)(this.children, path); } getChild(path) { return this.children[path]; } getNamedFilters() { return this.filters; } getNamedFilterByName(name) { const filter = this.filters.find((ele) => ele['#name'] === name); return filter; } setNamedFilters(filters, refresh) { this.filters = filters.map((ele) => Object.assign({}, ele, { applied: false })); if (refresh) { this.refresh(0, false); } else { this.publish(); } } addNamedFilter(filter, refresh) { // filter 根据#name查找 const fIndex = this.filters.findIndex((ele) => filter['#name'] && ele['#name'] === filter['#name']); if (fIndex >= 0) { this.filters.splice(fIndex, 1, Object.assign({}, filter, { applied: false })); } else { this.filters.push(Object.assign({}, filter, { applied: false })); } if (refresh) { this.refresh(0, false); } else { this.publish(); } } removeNamedFilter(filter, refresh) { // filter 根据#name查找 const fIndex = this.filters.findIndex((ele) => filter['#name'] && ele['#name'] === filter['#name']); if (fIndex >= 0) { this.filters.splice(fIndex, 1); } this.pagination.total = undefined; if (refresh) { this.refresh(0, false); } else { this.publish(); } } removeNamedFilterByName(name, refresh) { // filter 根据#name查找 const fIndex = this.filters.findIndex((ele) => ele['#name'] === name); if (fIndex >= 0) { this.filters.splice(fIndex, 1); } this.pagination.total = undefined; if (refresh) { this.refresh(0, false); } else { this.publish(); } } getNamedSorters() { return this.sorters; } getNamedSorterByName(name) { const sorter = this.sorters.find((ele) => ele && ele['#name'] === name); return sorter; } setNamedSorters(sorters, refresh) { this.sorters = sorters.map(ele => Object.assign({}, ele, { applied: false })); if (refresh) { this.refresh(0, false); } else { this.publish(); } } addNamedSorter(sorter, refresh) { // sorter 根据#name查找 const fIndex = this.sorters.findIndex((ele) => sorter['#name'] && ele['#name'] === sorter['#name']); if (fIndex >= 0) { this.sorters.splice(fIndex, 1, Object.assign({}, sorter, { applied: false })); } else { this.sorters.push(Object.assign({}, sorter, { applied: false })); } if (refresh) { this.refresh(0, false); } else { this.publish(); } } removeNamedSorter(sorter, refresh) { // sorter 根据#name查找 const fIndex = this.sorters.findIndex((ele) => sorter['#name'] && ele['#name'] === sorter['#name']); if (fIndex >= 0) { this.sorters.splice(fIndex, 1); } if (refresh) { this.refresh(0, false); } else { this.publish(); } } removeNamedSorterByName(name, refresh) { // sorter 根据#name查找 const fIndex = this.sorters.findIndex((ele) => ele['#name'] === name); if (fIndex >= 0) { this.sorters.splice(fIndex, 1); } if (refresh) { this.refresh(0, false); } else { this.publish(); } } getFreshValue() { /** * 现在简化情况,只取sr中有id的数据,以及addItem中的create数据 * 但是对于modi查询,需要查询“热更新”部分的数据(因为这部分数据不会被sync到内存中,逻辑不严密,后面再说) */ const inModiNextBranch = this.isInModiNextBranch(); const ids = Object.keys(this.sr); const operations = this.ulManager.makeOperations(); const createIds = operations.filter(o => o.action === 'create').map(o => o.data.id); const { data, sorter, filter } = this.constructSelection(true, false, true, true); const ids2 = ids.concat(createIds); /** * 在非modi状态下,当前的逻辑是用当前filter加以过滤,不然就处理不了在列表中操作改变了filter中外联的条件(例如为order创建了一个ship,但查询条件是没有ship)时,列表不发生变化 * 所以在这里当前是要求用户在取数据的时候,将可能影响的这种外联的数据也取到cache当中 * by Xc 20250315 */ const filter2 = inModiNextBranch ? filter : (ids2.length > 0 ? (filter ? (0, filter_1.combineFilters)(this.entity, this.cache.getSchema(), [filter, { id: { $in: ids2, } }]) : { id: { $in: ids2, } }) : undefined); if (filter2 && data) { let sr = { ...this.sr, }; if (createIds.length) { createIds.forEach((id) => Object.assign(sr, { [id]: {}, })); } const result = this.cache.get(this.entity, { data, filter: inModiNextBranch ? filter : filter2, sorter, }, inModiNextBranch ? undefined : sr); return result; } return []; } addItemInner(item, lsn) { // 如果数据键值是一个空字符串则更新成null for (const k in item) { if (item[k] === '') { Object.assign(item, { [k]: null, }); } } const id = item.id || (0, uuid_1.generateNewId)(); const now = Date.now(); this.ulManager.push(lsn, { action: 'create', data: Object.assign(item, { id, [types_1.CreateAtAttribute]: now, [types_1.UpdateAtAttribute]: now, }), }); return id; } addItem(lsn, item) { const id = this.addItemInner(item, lsn); this.setDirty(); return id; } addItems(lsn, items) { const ids = items.map((item) => this.addItemInner(item, lsn)); this.setDirty(); return ids; } removeItemInner(id, lsn) { this.ulManager.push(lsn, { action: 'remove', data: { [types_1.DeleteAtAttribute]: Date.now(), }, filter: { id, }, }); } removeItem(lsn, id) { this.removeItemInner(id, lsn); this.setDirty(); } removeItems(lsn, ids) { ids.forEach((id) => this.removeItemInner(id, lsn)); this.setDirty(); } recoverItemInner(id) { this.ulManager.undo({ id }); } recoverItem(id) { this.recoverItemInner(id); this.setDirty(); } recoverItems(ids) { ids.forEach((id) => this.recoverItemInner(id)); this.setDirty(); } resetItem(id) { this.ulManager.undo({ id }); } preProcessUpdateData(data) { const undefinedAttrs = []; // 如果数据键值是一个空字符串则更新成null,undefined则unset掉 for (const k in data) { if (data[k] === '' && !this.schema[this.entity].attributes[k].notNull) { Object.assign(data, { [k]: null, }); } else if (data[k] === undefined) { undefinedAttrs.push(k); } } undefinedAttrs.forEach((attr) => (0, lodash_1.unset)(data, attr)); } updateItemInner(lsn, data, id, action) { this.ulManager.push(lsn, { action: action || 'update', data: Object.assign({}, data, { [types_1.UpdateAtAttribute]: Date.now(), }), filter: { id, }, }); } /** * 目前只支持根据itemId进行更新 * @param data * @param id * @param beforeExecute * @param afterExecute */ updateItem(lsn, data, id, action) { this.preProcessUpdateData(data); this.updateItemInner(lsn, data, id, action); this.setDirty(); } updateItems(lsn, data, ids, action) { this.preProcessUpdateData(data); ids.forEach((id) => this.updateItemInner(lsn, data, id, action)); this.setDirty(); } composeOperations(paths) { if (!this.dirty) { return; } const intrinsticFilter = this.getIntrinsticFilters(); const ulmLsn = this.ulManager.maxLsn; paths?.shift(); for (const id in this.children) { const childOperations = this.children[id].composeOperations(paths); if (childOperations) { childOperations.forEach((childOperation) => this.ulManager.push(ulmLsn + 100, childOperation.operation)); } } const operations = this.ulManager.makeOperations(); this.ulManager.rollback(ulmLsn); return operations.map((operation) => { const { filter, ...rest } = operation; return { entity: this.entity, /* 现在update有一对多或者多对一的外键对象,oak-db会报错,暂时先封掉 operation: { ...rest, // 加上instrinstic filter有机率降低后台各种检查对数据库的访问(先决条件已经满足约束) filter: combineFilters(this.entity, this.schema, [filter, intrinsticFilter]), } */ operation }; }); } getProjection() { const projection = super.getProjection(); // List必须自主决定Projection /* if (this.children.length > 0) { const subProjection = await this.children[0].getProjection(); return merge(projection, subProjection); } */ return projection; } constructFilters(withParent, ignoreNewParent, ignoreUnapplied, onlyHot) { const { filters: ownFilters } = this; const filters = ownFilters.filter(ele => (!ignoreUnapplied || ele.applied === true || ele.applied === undefined) // 如果是undefined,说明不可以移除(构造时就存在),也得返回 && (!onlyHot || ele.hot) // 如果是getFreshValue,则只取hot的敏感过滤条件 ).map((ele) => { const { filter } = ele; if (typeof filter === 'function') { return filter(); } return filter; }); if (withParent && this.parent) { if (this.parent instanceof SingleNode) { // @ts-ignore const filterOfParent = this.parent.getParentFilter(this, ignoreNewParent); if (filterOfParent) { filters.push(filterOfParent); } else { // 说明有父结点但是却没有相应的约束,此时不应该去refresh(是一个insert动作) return undefined; } } } // 返回的filter在上层做check的时候可能被改造掉 return (0, lodash_1.cloneDeep)(filters); } constructSelection(withParent, ignoreNewParent, ignoreUnapplied, onlyHot) { const { sorters, getTotal } = this; const data = this.getProjection(); // assert(data, '取数据时找不到projection信息'); const sorterArr = sorters.length > 0 ? sorters.filter(ele => !ignoreUnapplied || ele.applied).map((ele) => { const { sorter } = ele; if (typeof sorter === 'function') { return sorter(); } return sorter; }).flat().filter((ele) => !!ele) : [ { $attr: { $$createAt$$: 1, }, $direction: 'desc' } ]; const filters = this.constructFilters(withParent, ignoreNewParent, ignoreUnapplied, onlyHot); const filters2 = filters?.filter((ele) => !!ele); const filter = filters2 ? (0, filter_1.combineFilters)(this.entity, this.schema, filters2) : undefined; const { currentPage, pageSize } = this.pagination; // 只有当没有计算过total或者有新的filter才需要显示的查找total const needGetTotal = this.pagination.total === undefined || !!this.filters.find(ele => !ele.applied); return { data, filter, sorter: sorterArr, total: needGetTotal ? getTotal : undefined, indexFrom: currentPage * pageSize, count: pageSize, }; } /** * 存留查询结果 */ saveRefreshResult(sr, append, currentPage) { const { data, total } = sr; (0, assert_1.assert)(data); const count = Object.keys(data).length; const getTotal = this.getTotal; this.pagination.more = count === this.pagination.pageSize; if (typeof currentPage === 'number' && !append) { this.pagination.currentPage = currentPage; } if (typeof total === 'number') { this.pagination.total = total; } if (typeof getTotal === 'number') { const total2 = this.pagination.total || 0; if (this.pagination.more && total2 < getTotal) { const pages = Math.floor((total2 - 1) / this.pagination.pageSize) + 1; this.pagination.more = this.pagination.currentPage + 1 < pages; } } if (append) { this.pagination.count += count; this.sr = { ...this.sr, ...data, }; } else { this.pagination.count = count; this.sr = data || {}; } for (const k in this.children) { const child = this.children[k]; child.saveRefreshResult({ [k]: this.sr[k] || {}, }); } // this.publish(); } async refresh(pageNumber, append, resetTotal) { if (resetTotal) { // 清空之前查询计算的total值,目前只有父结点id改变会这样调用 this.pagination.total = undefined; } const { entity, pagination } = this; const { currentPage, pageSize, randomRange } = pagination; const currentPage3 = typeof pageNumber === 'number' ? pageNumber : currentPage; (0, assert_1.assert)(!randomRange || !currentPage3, 'list在访问数据时,如果设置了randomRange,则不应再有pageNumber'); const { data: projection, filter, sorter, total, } = this.constructSelection(true, true); // 若不存在有效的过滤条件(若有父结点但却为空时,说明父结点是一个create动作,不用刷新),则不能刷新 if ((!this.getParent() || this.getParent() instanceof VirtualNode || filter) && projection) { const lr = !append && this.startLoading(); if (lr instanceof Promise) { return lr; } try { if (append) { this.loadingMore = true; } this.publish(); await this.cache.refresh(entity, { data: projection, filter, sorter, indexFrom: currentPage3 * pageSize, count: pageSize, randomRange, total, }, undefined, (selectResult) => { typeof lr === 'number' && this.endRefreshing(lr); this.setFiltersAndSortedApplied(); if (append) { this.loadingMore = false; } // 这里不能publish,因为 this.saveRefreshResult(selectResult, append, currentPage3); }); } catch (err) { typeof lr === 'number' && this.endRefreshing(lr, err); if (append) { this.loadingMore = false; } this.publish(); throw err; } } else { // 不刷新也publish一下,触发页面reRender,不然有可能导致页面不进入formData this.setFiltersAndSortedApplied(); this.sr = {}; this.publish(); } } async loadMore() { const { filters, sorters, pagination, entity } = this; const { pageSize, more, currentPage, count } = pagination; if (!more || this.loadingMore || !count) { return; } // 要根据total来计算下一页 (0, assert_1.assert)(count); const currentPage2 = count / pageSize; await this.refresh(currentPage2, true); } setCurrentPage(currentPage) { this.refresh(currentPage, false); } clean(lsn, dontPublish) { for (const k in this.children) { this.children[k].clean(lsn, true); } if (this.dirty) { this.ulManager.rollback(lsn || 0); if (!lsn) { this.dirty = undefined; } if (!dontPublish) { this.publish(); } } } // 查看这个list上所有数据必须遵守的限制 getIntrinsticFilters() { const filters = this.constructFilters(undefined, true, true); return (0, filter_1.combineFilters)(this.entity, this.schema, filters || []); } } class SingleNode extends EntityNode { id; sr; children; filters; constructor(entity, schema, cache, fullPath, path, projection, parent, id, filters, stale) { super(entity, schema, cache, fullPath, projection, parent); this.children = {}; this.sr = {}; this.filters = filters; this.stale = stale; // addChild有可能为本结点赋上id值,所以要先行 if (parent) { (0, assert_1.assert)(path); parent.addChild(path, this); } if (id) { if (this.id) { (0, assert_1.assert)(id === this.id, 'singleNode初始化的id必须一致'); } else { this.id = id; } } } getModiOperations() { if (this.schema[this.entity].toModi) { const modies = this.cache.get('modi', { data: { id: 1, targetEntity: 1, entity: 1, entityId: 1, iState: 1, action: 1, data: 1, filter: 1, }, filter: { entity: this.entity, entityId: this.id, iState: 'active', }, sorter: [ { $attr: { $$createAt$$: 1, }, $direction: 'asc', } ], }); if (modies.length > 0) { return (0, modi_1.createOperationsFromModies)(modies); } } // 当前假设设计中不存在modi嵌套modi的情况,找到一个即可返回 for (const c in this.children) { const child = this.children[c]; if (child instanceof SingleNode) { const result = child.getModiOperations(); if (result) { return result; } } } } setFiltersAndSortedApplied() { for (const k in this.children) { this.children[k].setFiltersAndSortedApplied(); } } startLoading() { const number = Math.random() + 1; return this.startRefreshing(number); } checkIfClean(node) { if (!this.ulManager.isEmpty()) { return false; } for (const k in this.children) { if (this.children[k] === node) { continue; } if (this.children[k].isDirty()) { return false; } } return true; } destroy() { for (const k in this.children) { this.children[k].destroy(); } } getChild(path) { return this.children[path]; } setId(id) { if (id !== this.id) { const operations = this.ulManager.makeOperations(); (0, assert_1.assert)(operations.length <= 1, 'singleNode在setId时出现数据不一致'); const [operation] = operations; if (operation?.action === 'create') { if (operation.data.id === id) { // 如果本身是create, 这里无视就行(因为框架原因会调用一次) return; } else { // 这种情况是oakId属性没有初始化完成 this.clean(0, true); } } (0, assert_1.assert)(!this.dirty, 'setId时结点是dirty,在setId之前应当处理掉原有的update'); this.id = id; this.refreshListChildren(); this.publish(); } } unsetId() { if (this.id) { this.id = undefined; this.refreshListChildren(); this.publish(); } } getId() { if (this.id) { return this.id; } const operations = this.ulManager.makeOperations(); (0, assert_1.assert)(operations.length <= 1); const [operation] = operations; if (operation && operation?.action === 'create') { return operation.data.id; } } getChildren() { return this.children; } addChild(path, node) { (0, assert_1.assert)(!this.children[path], `无法定义相同路径[${path}]下的子节点`); this.children[path] = node; this.passRsToChild(path); } removeChild(path) { (0, lodash_1.unset)(this.children, path); } getFreshValue() { const inModiNextBranch = this.isInModiNextBranch(); const projection = this.getProjection(false); const filter = this.getFilter(undefined, true); if (projection && filter) { /** * 这里在非modi状态下,原来的代码是不会去刷新缺失的数据,原因不明,可能是认为页面应当自己负责数据的获取 * 在modi状态下,有些外键指向的数据无法预先获取,因此需要加上这个逻辑 * * 先放回来,不知道有什么问题 * by Xc 20240229 */ const id = this.getId(); const result = this.cache.get(this.entity, { data: projection, filter, }, inModiNextBranch ? undefined : { [id]: this.sr, }); return result[0]; } } // 当node的id重置时,其一对多的儿子结点都应当刷新数据(条件已经改变) refreshListChildren() { for (const k in this.children) { const child = this.children[k]; if (child instanceof ListNode) { child.refresh(0, false); } } } create(lsn, data) { const id = (0, uuid_1.generateNewId)(); (0, assert_1.assert)(!this.id && !this.dirty, 'create前要保证singleNode为空'); // 如果数据键值是一个空字符串则更新成null for (const k in data) { if (data[k] === '') { Object.assign(data, { [k]: null, }); } } const now = Date.now(); this.ulManager.push(lsn, { action: 'create', data: Object.assign({}, data, { id, [types_1.CreateAtAttribute]: now, [types_1.UpdateAtAttribute]: now, }), }); this.refreshListChildren(); this.setDirty(); } update(lsn, data, action) { // 如果数据键值是一个空字符串则更新成null,如果是undefined则取消此键值 const undefinedAttrs = []; for (const k in data) { if (data[k] === '' && !this.schema[this.entity].attributes[k].notNull) { Object.assign(data, { [k]: null, }); } else if (data[k] === undefined) { undefinedAttrs.push(k); } } undefinedAttrs.forEach((attr) => (0, lodash_1.unset)(data, attr)); this.ulManager.push(lsn, { action: action || 'update', data: Object.assign({}, data, { [types_1.UpdateAtAttribute]: Date.now(), }), filter: { id: this.getId(), } }); // 处理外键,如果update的数据中有相应的外键,其子对象上的动作应当被clean掉 // 并将sr传递到子组件上 // 这里可能有问题,还是应该由页面上显式传入对象主键保险。by Xc 20240623 /* for (const attr in data!) { if (attr === 'entityId') { assert(data.entity, '设置entityId时请将entity也传入'); if (this.children[data.entity]) { this.children[data.entity].clean(true); this.passRsToChild(data.entity); } } else if (this.schema[this.entity]!.attributes[attr as any]?.type === 'ref') { const refKey = attr.slice(0, attr.length - 2); if (this.children[refKey]) { this.children[refKey].clean(true); this.passRsToChild(refKey); } } } */ this.setDirty(); } remove(lsn) { const id = this.getId(); (0, assert_1.assert)(id, '当前节点的remove必须提供id'); this.ulManager.push(lsn, { action: 'remove', data: { [types_1.DeleteAtAttribute]: Date.now(), }, filter: { id, } }); this.setDirty(); } setDirty() { const id = this.getId(); (0, assert_1.assert)(id, "不能对没有id的singleNode设置dirty"); this.ulManager.push(this.ulManager.maxLsn, { action: 'update', data: {}, filter: { id, } }); super.setDirty(); } /** * * @param path 如果指定了某条路径,则只处理这条路径上的operation(区分modi的前后项) * @returns */ composeOperations(paths) { if (this.dirty) { const lsnMax = this.ulManager.maxLsn; const path = paths?.shift(); for (const ele in this.children) { if (path && path !== ele) { continue; } const ele2 = ele.includes(':') ? ele.slice(0, ele.indexOf(':')) : ele; const child = this.children[ele]; const childOperations = child.composeOperations(paths?.length ? paths : undefined); if (childOperations) { if (child instanceof SingleNode) { (0, assert_1.assert)(childOperations.length === 1, 'singleNode在composeOperations时出现数据不一致'); this.ulManager.push(lsnMax + 100, { action: 'update', data: { [ele2]: childOperations[0].operation, }, filter: { id: this.getId(), } }); } else { (0, assert_1.assert)(child instanceof ListNode, 'child必须是ListNode'); this.ulManager.push(lsnMax + 100, { action: 'update', data: { [ele2]: childOperations.map(({ operation }) => operation), }, filter: { id: this.getId(), } }); } } } const operations = this.ulManager.makeOperations(); this.ulManager.rollback(lsnMax); (0, assert_1.assert)(operations.length <= 1); const [operation] = operations; if (operation) { const { filter, ...rest } = operation; const realFilter = this.getFilter(true); return [ { entity: this.entity, operation: { ...rest, filter: realFilter, }, } ]; } } } getProjection(withDecendants) { const projection = super.getProjection(); if (projection && withDecendants) { for (const k in this.children) { if (projection[k] && process.env.NODE_ENV === 'development') { console.warn(`父结点都定义了${k}路径上的projection,和子结点产生冲突`); } if (!k.includes(exports.MODI_NEXT_PATH_SUFFIX)) { const rel = this.judgeRelation(k.includes(':') ? k.split(':')[0] : k); if (rel === 2) { const subProjection = this.children[k].getProjection(true); Object.assign(projection, { entity: 1, entityId: 1, [k]: subProjection, }); } else if (typeof rel === 'string') { const subProjection = this.children[k].getProjection(true); Object.assign(projection, { [`${k}Id`]: 1, [k]: subProjection, }); } else if (!k.endsWith('$$aggr')) { const child = this.children[k]; (0, assert_1.assert)(rel instanceof Array && child instanceof ListNode); const subSelection = child.constructSelection(); const subEntity = child.getEntity(); Object.assign(projection, { [k]: Object.assign(subSelection, { $entity: subEntity, }) }); } } } } return projection; } passRsToChild(k) { /** * 把返回的结果中的total和aggr相关的值下降到相关的子结点上去 */ const projection = this.getProjection(true); const [value] = this.cache.get(this.entity, { data: projection, filter: { id: this.id, }, }, this.sr); const keys = k ? [k] : Object.keys(this.children || {}); for (const k of keys) { const child = this.children[k]; const k2 = k.split(':')[0]; // 处理:next const rel = this.judgeRelation(k2); if (rel === 2) { if (value?.entityId) { (0, assert_1.assert)(child instanceof SingleNode, 'child必须是singleNode'); (0, assert_1.assert)(value.entity === child.getEntity(), 'singleNode的entity必须一致'); child.saveRefreshResult({ [value.entityId]: this.sr[k2] || {}, }); } else if (value && value.entityId === undefined && process.env.NODE_ENV === 'development') { console.warn(`singleNode 的子路径「${k2}」上没有找到相应的entityId值,可能是取数据不全,请检查`); } } else if (typeof rel === 'string') { if (value && value[`${k2}Id`]) { (0, assert_1.assert)(child instanceof SingleNode); (0, assert_1.assert)(rel === child.getEntity()); child.saveRefreshResult({ [value[`${k2}Id`]]: this.sr[k2] || {}, }); } else if (value && value[`${k2}Id`] === undefined && process.env.NODE_ENV === 'development') { console.warn(`singleNode 的子路径「${k2}」上没有找到相应的${k2}Id值,可能是取数据不全,请检查`); } } else { (0, assert_1.assert)(rel instanceof Array); (0, assert_1.assert)(child instanceof ListNode); // assert(this.sr![k]); if (this.sr[k2]) { child.saveRefreshResult(this.sr[k2]); } } } } saveRefreshResult(data) { const ids = Object.keys(data); if (ids.length === 0) { (0, assert_1.assert)(!this.id, '通过id查询数据结果为空'); } // 这个情况不应该出现,查询数据的时候有判断,这里不可能出现>1的情况 (0, assert_1.assert)(ids.length === 1, '通过id查询数据结果不唯一'); this.setId(ids[0]); this.sr = data[ids[0]]; this.passRsToChild(); // this.publish(); } async refresh() { // SingleNode如果是非根结点,其id应该在第一次refresh的时候来确定 const projection = this.getProjection(true); const filter = this.getFilter(true); if (projection && filter) { const lr = this.startLoading(); if (lr instanceof Promise) { return lr; } this.publish(); try { await this.cache.refresh(this.entity, { data: projection, filter, }, undefined, (result) => { // 刷新后所有的更新都应当被丢弃(子层上可能会自动建立了this.create动作) 这里可能会有问题 by Xc 20230329 this.saveRefreshResult(result.data); this.setFiltersAndSortedApplied(); this.endRefreshing(lr); //this.clean(); }); } catch (err) { this.endRefreshing(lr, err); this.publish(); throw err; } } else { // 不刷新也publish一下,触发页面reRender,不然有可能导致页面不进入formData this.publish(); } } clean(lsn, dontPublish) { for (const child in this.children) { this.children[child].clean(lsn, true); } if (this.dirty) { this.ulManager.rollback(lsn || 0); if (!lsn) { this.dirty = undefined; } if (!dontPublish) { this.publish(); } } } getFilter(ignoreNew, onlyHot) { // 如果是新建,等于没有filter const [operation] = this.ulManager.makeOperations(); if (operation?.action === 'create') { if (ignoreNew) { return; } const { id } = operation.data; return { id }; } // singleNode增加一些限定的filter可以优化后台权限的判断范围和一些trigger的条件 // 如果没有this.id则不返回,避免一些奇怪的边界(比如execute以后refresh) if (this.id) { let filter = { id: this.id, }; if (this.filters) { filter = (0, filter_1.combineFilters)(this.entity, this.schema, this.filters.filter(ele => !onlyHot || ele.hot).map(ele => typeof ele.filter === 'function' ? ele.filter() : ele.filter).concat(filter)); } if (this.parent && this.parent instanceof ListNode && this.parent.getEntity() === this.entity) { const { filter: parentFilter } = this.parent.constructSelection(true, true, true); filter = (0, filter_1.combineFilters)(this.entity, this.schema, [filter, parentFilter]); } return filter; } } getIntrinsticFilters() { return this.getFilter(); } /** * getParentFilter不能假设一定已经有数据,只能根据当前filter的条件去构造 * @param childNode * @param disableOperation * @returns */ getParentFilter(childNode, ignoreNewParent) { const value = this.getFreshValue(); if (!value || (value && value.$$createAt$$ === 1 && ignoreNewParent)) { return; } for (const key in this.children) { if (childNode === this.children[key]) { const rel = this.judgeRelation(key); (0, assert_1.assert)(rel instanceof Array && !key.endsWith('$$aggr')); if (rel[1]) { // 基于普通外键的一对多 if (value) { return { [rel[1]]: value.id, }; } const filter = this.getFilter(); if (filter) { if (filter.id && Object.keys(filter).length === 1) { return { [rel[1]]: filter.id, }; } return { [rel[1].slice(0, rel[1].length - 2)]: filter, }; } } else { // 基于entity/entityId的一对多 if (value) { return { entity: this.entity, entityId: value.id, }; } const filter = this.getFilter(); if (filter) { if (filter.id && Object.keys(filter).length === 1) { return { entity: this.entity, entityId: filter.id, }; } return { [this.entity]: filter, }; } } } } return; } } class VirtualNode extends Node { parent; refreshing2 = false; children; constructor(fullPath, path, parent, stale) { super(fullPath); this.children = {}; this.stale = stale; if (parent) { parent.addChild(path, this); this.parent = parent; } } getModiOperations() { for (const c in this.children) { const child = this.children[c]; if (child instanceof SingleNode) { const result = child.getModiOperations(); if (result) { return result; } } } } setDirty() { this.dirty = true; this.publish(); } setFiltersAndSortedApplied() { for (const k in this.children) { this.children[k].setFiltersAndSortedApplied(); } } addChild(path, child) { // 规范virtualNode子结点的命名路径和类型,entity的singleNode必须被命名为entity或entity:number,ListNode必须被命名为entitys或entitys:number (0, assert_1.assert)(!this.children[path], `无法定义相同路径[${path}]下的子节点`); this.children[path] = child; if (child instanceof SingleNode || child instanceof ListNode) { const entity = child.getEntity(); if (child instanceof SingleNode) { (0, assert_1.assert)(path === entity || path.startsWith(`${entity}:`), `oakPath「${path}」不符合命名规范,请以「${entity}:」为命名起始标识`); } else { (0, assert_1.assert)(path === `${entity}s` || path.startsWith(`${entity}s:`), `oakPath「${path}」不符合命名规范,请以「${entity}s:」为命名起始标识`); } } } removeChild(path) { (0, lodash_1.unset)(this.children, path); } getChild(path) { return this.children[path]; } getParent() { return this.parent; } destroy() { for (const k in this.children) { this.children[k].destroy(); } } getFreshValue() { return undefined; } isLoading() { return this.refreshing2; } async refresh() { this.refreshing2 = true; const promises = Object.keys(this.children).map(ele => this.children[ele].refresh()); try { this.publish(); await Promise.all(promises); this.refreshing2 = false; this.publish(); } catch (err) { this.refreshing2 = false; this.publish(); throw err; } } composeOperations(paths) { /** * 当一个virtualNode有多个子结点,而这些子结点的前缀一致时,标识这些子结点其实是指向同一个对象,此时需要合并 */ const operationss = []; const operationDict = {}; const path = paths?.shift(); for (const ele in this.children) { if (path && path !== ele) { continue; } const operation = this.children[ele].composeOperations(paths?.length ? paths : undefined); if (operation) { const idx = ele.indexOf(':') !== -1 ? ele.slice(0, ele.indexOf(':')) : ele; if (operationDict[idx]) { (0, assert_1.assert)(false, '这种情况不应当再出现'); // 需要合并这两个子结点的动作 todo 两个子结点指向同一个对象,这种情况应当要消除 /* if (this.children[ele] instanceof SingleNode) { // mergeOperationOper(this.children[ele].getEntity(), this.children[ele].getSchema(), operation[0], operationDict[idx][0]); } else { console.warn('发生virtualNode上的list页面的动作merge,请查看'); operationDict[idx].push(...operation); } */ } else { operationDict[idx] = operation; } } } for (const k in operationDict) { operationss.push(...operationDict[k]); } return operationss; } clean(lsn, dontPublish) { for (const ele in this.children) { this.children[ele].clean(lsn, true); } if (!lsn) { this.dirty = false; } if (!dontPublish) { this.publish(); } } checkIfClean(node) { for (const k in this.children) { if (this.children[k] === node) { continue; } if (this.children[k].isDirty()) { return false; } } return true; } } function analyzePath(path) { const paths = path.split('.'); const length = paths.length; return { root: paths[0], parent: length > 1 ? paths.slice(0, length - 1).join('.') : '', path: paths[length - 1], }; } class RunningTree extends Feature_1.Feature { logSerailNumber; cache; schema; root; constructor(cache, schema) { super(); // this.aspectWrapper = aspectWrapper; this.cache = cache; this.schema = schema; this.root = {}; this.logSerailNumber = START_LSN; this.cache.bindOnSync((opRecords) => { opRecords.forEach((record) => { const { a } = record; switch (a) { case 'c': { const { e } = record; if (e === 'modi') { this.invalidateCachedModis(); } break; } case 'r': { const { e } = record; if (e === 'modi') { this.invalidateCachedModis(); } break; } case 's': { const { d } = record; for (const e in d) { if (e === 'modi') { this.invalidateCachedModis(); break; } } break; } default: { const { e } = record; if (e === 'modi') { this.invalidateCachedModis(); } break; } } }); }); } createNode(options, isPage) { const { entity, pagination, path: fullPath, filters, sorters, projection, isList, id, getTotal, zombie, stale, } = options; let node; const { parent, path } = analyzePath(fullPath); const parentNode = parent ? this.findNode(parent) : undefined; node = this.findNode(fullPath); // 找到了之前的node,需要检查是否需要重用 if (node) { if (node.getCount()) { // 有组件正在使用 // 页面组件已经被销毁,现在重新挂载,提示需要zombie (0, assert_1.assert)(!isPage || zombie, '暂时在路由上可能回归的页面必须设置为zombie'); // 是页面并且不是zombie // 这次非virtualNode if (entity) { if (node instanceof ListNode) { if (isList && node.getEntity() === entity) { // 如果之前未初始化,当前组件负责初始化 if (!node.getProjection() && projection) { node.setProjection(projection); if (filters) { node.setNamedFilters(filters); } if (sorters) { node.setNamedSorters(sorters); } if (pagination) { node.setPagination(pagination, true); // 创建成功后会统一refresh } } else if (projection) { // 这里有一个例外是queryPanel这种和父结点共用此结点的抽象组件 // assert(false, `创建node时发现path[${fullPath}]已经存在有效的ListNod结点,这种情况不应该存在`); } } } else { (0, assert_1.assert)(node instanceof SingleNode, `节点[${path}]应为Virtual节点,但现在是[${entity.toString()}]请检查oakPath是否重复`); (0, assert_1.assert)(!isList && node.getEntity() === entity, `节点${path}的entity为${node.getEntity().toString()},但现在为${entity.toString()},请检查oakPath是否重复`); if (!node.getProjection() && projection) { node.setProjection(projection); if (id) { const id2 = node.getId(); (0, assert_1.assert)(id === id2, `重用path[${fullPath}]上的singleNode时,其上没有置有效id,这种情况id应当由父结点设置`); } } else { // 目前只有一种情况合法,即parentNode是list,列表中的位置移动引起的重用 // assert(parentNode instanceof ListNode, `创建node时发现path[${fullPath}]已有有效的SingleNode结点,本情况不应当存在`); } } } // 上次不是virtualNode,这次变成virtual了 else { (0, assert_1.assert)(false, `创建Virtual节点时发现path[${fullPath}]已经存在有效的结点,请检查oakPath是否重复`); // assert(false, '这种情况暂时不考虑,跑出来再处理,by Xc 20240717'); } } else { // 这种情况是重用zombie留下的node,啥也别做 if (node instanceof SingleNode || node instanceof ListNode) { (0, assert_1.assert)(node.getEntity() === entity, 'zombie留下的node, entity不能变化'); } } } else { const rollback = this.begin(); if (parentNode) { this.redoBranchModis(fullPath); this.redoBranchOperations(fullPath); } if (entity) { if (isList) { (0, assert_1.assert)(!(parentNode instanceof ListNode)); // assert(projection, `页面没有定义投影「${path}」`); node = new ListNode(entity, this.schema, this.cache, fullPath, projection, parentNode, path, filters, sorters, getTotal, pagination, stale); } else { node = new SingleNode(entity, this.schema, this.cache, fullPath, path, projection, parentNode, // 过编译 id, filters, stale); } } else { (0, assert_1.assert)(!parentNode || parentNode instanceof VirtualNode); node = new VirtualNode(fullPath, path, parentNode, stale); } // 任何临时的修改都需要回滚,上面缓存了一些update操作 rollback(); if (!parentNode) { (0, assert_1.assert)(!parent && !this.root[path]); this.root[path] = node; } node.subscribe(() => { this.publish(fullPath); }); } node.increaseCount(); if (zombie) { // 像Pagination, FilterPanel, ExtrafileCommit这种要和某个Component共用路径的,按要求都应该是后创建,不能影响原来的zombie node.setZombie(zombie); } if (node instanceof SingleNode && !node.getId()) { node.create(this.logSerailNumber, {}); this.invalidateCachedOperations(fullPath); } return node; } findNode(path) { if (this.root[path]) { return this.root[path]; } const paths = path.split('.'); let node = this.root[paths[0]]; let iter = 1; while (iter < paths.length && node) { if (!node) { return; } const childPath = paths[iter]; iter++; node = node.getChild(childPath); } return node; } destroyNode(path, isPage) { const node = this.findNode(path); // 如果node不存在,说明父结点已经析构掉了,无视即可 if (!node) { return; } const cnt = node.decreaseCount(); if (cnt !== 0) { return; } if (node.isZombie()) { return; } // 不是声明为zombie结点就析构 const childPath = path.slice(path.lastIndexOf('.') + 1); const parent = node.getParent(); if (parent) { parent.removeChild(childPath); } else if (!parent) { (0, assert_1.assert)(this.root.hasOwnProperty(path)); (0, lodash_1.unset)(this.root, path); } this.invalidateCachedOperations(path); node.destroy(); node.clearSubscribes(); } begin() { return this.cache.begin(); } getFreshValue(path) { const node = this.findNode(path); if (node instanceof ListNode) { return node.getFreshValue(); } else if (node instanceof SingleNode) { return node.getFreshValue(); } } isDirty(path) { const node = this.findNode(path); return node ? node.isDirty() : false; } addItem(path, data) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用addItem'); return node.addItem(this.logSerailNumber, data); } addItems(path, data) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用addItems'); return node.addItems(this.logSerailNumber, data); } removeItem(path, id) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用removeItem'); node.removeItem(this.logSerailNumber, id); } removeItems(path, ids) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用removeItems'); node.removeItems(this.logSerailNumber, ids); } updateItem(path, data, id, action) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用updateItem'); node.updateItem(this.logSerailNumber, data, id, action); } updateItems(path, data, ids, action) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用updateItems'); node.updateItems(this.logSerailNumber, data, ids, action); } recoverItem(path, id) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用recoverItem'); node.recoverItem(id); } recoverItems(path, ids) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用recoverItems'); node.recoverItems(ids); } resetItem(path, id) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用resetItem'); node.resetItem(id); } create(path, data) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof SingleNode, '只能在SingleNode上调用create'); node.create(this.logSerailNumber, data); } update(path, data, action) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof SingleNode, '只能在SingleNode上调用update'); node.update(this.logSerailNumber, data, action); } remove(path) { this.invalidateCachedOperations(path); const node = this.findNode(path); (0, assert_1.assert)(node instanceof SingleNode, '只能在SingleNode上调用remove'); node.remove(this.logSerailNumber); } isLoading(path) { const node = this.findNode(path); return node?.isLoading(); } isLoadingMore(path) { const node = this.findNode(path); if (node instanceof ListNode) { return node?.isLoadingMore(); } return false; } isExecuting(path) { const node = this.findNode(path); return node ? node.isExecuting() : false; } isListChildOrStale(path) { const node = this.findNode(path); if (node?.isStale()) { return true; } let parent = node?.getParent(); if (parent instanceof ListNode) { return true; } return false; } // 判断当前结点是否在:next的路径上 // 当前判断规则是:在:next的分岔结点上,如果有前项结点,或者:next结点本身是一个create,则不再刷新 isInModiNextBranch(path) { if (!path.includes(exports.MODI_NEXT_PATH_SUFFIX)) { return false; } const paths = path.split('.'); if (paths[0].includes(exports.MODI_NEXT_PATH_SUFFIX)) { // 根结点就是:next,这时候还是要取的 return false; } let iter = 1; while (iter < paths.length) { if (paths[iter].includes(exports.MODI_NEXT_PATH_SUFFIX)) { const pathPrev = paths.slice(0, iter).join('.') + '.' + paths[iter].replace(exports.MODI_NEXT_PATH_SUFFIX, ''); const node = this.findNode(pathPrev); if (node) { return true; } // 还有一种情况,这个:next结点是创建出来的 const pathNext = paths.slice(0, iter + 1).join('.'); const nodeNext = this.findNode(pathNext); if (nodeNext instanceof SingleNode) { const id = nodeNext.getId(); const [modi] = this.cache.get('modi', { data: { id: 1, }, filter: { action: 'create', targetEntity: nodeNext.getEntity(), filter: { id, }, } }); if (modi) { return true; } } return false; } iter++; } (0, assert_1.assert)(false); return false; } async refresh(path, pageNumber, resetTotal) { if (this.isInModiNextBranch(path)) { this.publish(path); return; } const node = this.findNode(path); if (node instanceof ListNode) { await node.refresh(pageNumber, false, resetTotal); } else if (node) { await node.refresh(); } } async loadMore(path) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode); await node.loadMore(); } getPagination(path) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用getPagination'); const pn = node.getPagination(); return { ...pn, currentPage: pn.currentPage + 1, }; } setId(path, id) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof SingleNode, '只能在SingleNode上调用setId'); return node.setId(id); } unsetId(path) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof SingleNode, '只能在SingleNode上调用unsetId'); node.unsetId(); } getId(path) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof SingleNode, '只能在SingleNode上调用getId'); return node.getId(); } getEntity(path) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof EntityNode, '只能在EntityNode上调用getEntity'); return node.getEntity(); } setPageSize(path, pageSize) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用setPageSize'); // 切换分页pageSize就重新设置 return node.setPagination({ pageSize, currentPage: 0, total: undefined, }); } setCurrentPage(path, currentPage) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用setCurrentPage'); return node.setCurrentPage(currentPage - 1); } getNamedFilters(path) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用getNamedFilters'); return node.getNamedFilters(); } getNamedFilterByName(path, name) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用getNamedFilterByName'); return node.getNamedFilterByName(name); } setNamedFilters(path, filters, refresh = true) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用setNamedFilters'); node.setNamedFilters(filters, refresh); } addNamedFilter(path, filter, refresh = false) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用addNamedFilter'); return node.addNamedFilter(filter, refresh); } removeNamedFilter(path, filter, refresh = false) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用removeNamedFilter'); return node.removeNamedFilter(filter, refresh); } removeNamedFilterByName(path, name, refresh = false) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用removeNamedFilterByName'); return node.removeNamedFilterByName(name, refresh); } getNamedSorters(path) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用getNamedSorters'); return node.getNamedSorters(); } getNamedSorterByName(path, name) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用getNamedSorterByName'); return node.getNamedSorterByName(name); } setNamedSorters(path, sorters, refresh = true) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用setNamedSorters'); return node.setNamedSorters(sorters, refresh); } addNamedSorter(path, sorter, refresh = false) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用addNamedSorter'); return node.addNamedSorter(sorter, refresh); } removeNamedSorter(path, sorter, refresh = false) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用removeNamedSorter'); return node.removeNamedSorter(sorter, refresh); } removeNamedSorterByName(path, name, refresh = false) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode, '只能在ListNode上调用removeNamedSorterByName'); return node.removeNamedSorterByName(name, refresh); } getIntrinsticFilters(path) { const node = this.findNode(path); (0, assert_1.assert)(node instanceof ListNode || node instanceof SingleNode); return node.getIntrinsticFilters(); } getOperations(path) { const node = this.findNode(path); const operations = node?.composeOperations(); return operations; } cachedOperations = {}; cachedModis = {}; invalidateCachedOperations(path) { const invalidOnes = []; for (const k in this.cachedOperations) { if (path.startsWith(k) || k.startsWith(path)) { invalidOnes.push(k); } } invalidOnes.forEach((key) => (0, lodash_1.unset)(this.cachedOperations, key)); } invalidateCachedModis() { this.cachedModis = {}; } /** * 这个函数在reRender时可能会反复调用,composeOperations很消耗性能,在这里要做个缓存 * @param path */ redoBranchOperations(path) { const paths = path.split('.'); const root = paths.shift(); const opers = this.cachedOperations[path] || this.root[root].composeOperations(paths); if (opers) { if (!this.cachedOperations[path]) { this.cachedOperations[path] = opers; } this.cache.redoOperation(opers); } } /** * 这个函数在reRender时可能会反复调用,getModiOperations很消耗性能,在这里要做个缓存 * @param path */ redoBranchModis(path) { const { root } = analyzePath(path); const rootNode = this.root[root]; const includeModi = path.includes(exports.MODI_NEXT_PATH_SUFFIX); if (includeModi) { const modiOperations = this.cachedModis[root] || rootNode.getModiOperations(); if (!this.cachedModis[root]) { this.cachedModis[root] = modiOperations; } modiOperations && this.cache.redoOperation(modiOperations); } } async execute(path, action, opers) { const node = path && this.findNode(path); // assert(node.isDirty()); node && node.setExecuting(true); // let pollute = false; try { let operations = path && this.getOperations(path) || []; if (opers) { operations.push(...opers); } // 这里理论上virtualNode下面也可以有多个不同的entity的组件,但实际中不应当出现这样的设计 if (action) { if (operations.length > 0) { (0, assert_1.assert)(operations.length === 1); const [operation1] = operations; if (action !== operation1.operation.action) { (0, assert_1.assert)(operation1.operation.action === 'update'); // 如果execute时传action,前面update动作应该只可能是update operation1.operation.action = action; } } else if (node) { // 老的写法,直接对一个非脏的结点execute某个action,也可以支持 (0, assert_1.assert)(node instanceof SingleNode); // node.update(this.logSerailNumber, {}, action); // pollute = true; // operations = node.composeOperations() || []; // assert(operations.length === 1); // const [operation1] = operations; // if (action !== operation1.operation.action) { // assert(operation1.operation.action === 'update'); // 如果execute时传action,前面update动作应该只可能是update // operation1.operation.action = action; // } /** * 上述写法会触发publish行为,如:一个close动作会导致页面像下面这样渲染: * opened --> closed(上面的node.update) --> opened(执行成功回来setExecuting时) --> closed(执行成功回来再sync cache后) */ operations.push({ entity: node.getEntity(), operation: { id: await (0, uuid_1.generateNewIdAsync)(), action, data: { $$updateAt$$: Date.now(), }, filter: node.getFilter(true), } }); } } if (operations.length > 0) { const entities = (0, lodash_1.uniq)(operations.filter((ele) => !!ele).map((ele) => ele.entity)); (0, assert_1.assert)(entities.length === 1); const result = await this.cache.operate(entities[0], operations .filter((ele) => !!ele) .map((ele) => ele.operation), undefined, () => { // 清空缓存 path && this.clean(path, undefined, true); if (node && node instanceof SingleNode) { (0, assert_1.assert)(operations.length === 1); // 这逻辑有点扯,页面自己决定后续逻辑 by Xc 20231108 // if (operations[0].operation.action === 'create') { // // 如果是create动作,给结点赋上id,以保证页面数据的完整性 // const { id } = operations[0].operation.data as ED[keyof ED]['CreateSingle']['data']; // node.setId(id); // } } node && node.setExecuting(false); }); return result; } path && this.clean(path, undefined, true); node && node.setExecuting(false); return { message: 'No Operation' }; } catch (err) { node && node.setExecuting(false); /* if (pollute) { path && this.clean(path); } */ throw err; } } savePoint() { const lsn = this.logSerailNumber; this.logSerailNumber++; return lsn; } /** * 将path上的更新清除 * @param path * @param lsn 要清除到某个指定的savepoint,不设则完全清空 * @param dontPublish */ clean(path, lsn, dontPublish) { let node = this.findNode(path); const { root } = analyzePath(path); this.invalidateCachedOperations(root); while (true) { const parent = node?.getParent(); if (parent && node && parent.checkIfClean(node)) { node = parent; continue; } else { node?.clean(lsn, dontPublish); break; } } } getRoot() { return this.root; } saveExtraData(path, key, data) { const node = this.findNode(path); node.saveExtraData(key, data); } loadExtraData(path, key) { const node = this.findNode(path); return node.loadExtraData(key); } } exports.RunningTree = RunningTree;