oak-frontend-base/es/features/runningTree.js

2523 lines
90 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { assert } from 'oak-domain/lib/utils/assert';
import { cloneDeep, difference, unset, uniq, intersection } from "oak-domain/lib/utils/lodash";
import { combineFilters, getRelevantIds } from "oak-domain/lib/store/filter";
import { createOperationsFromModies } from 'oak-domain/lib/store/modi';
import { judgeRelation } from "oak-domain/lib/store/relation";
import { CreateAtAttribute, UpdateAtAttribute, DeleteAtAttribute } from "oak-domain/lib/types";
import { Feature } from '../types/Feature';
import { generateNewId } from 'oak-domain/lib/utils/uuid';
export const MODI_NEXT_PATH_SUFFIX = ':next';
const START_LSN = 100;
function mergeOperation(schema, entity, oper1, oper2) {
const { action, data } = oper2;
const operMerged = cloneDeep(oper1);
assert(action !== 'create');
if (action === 'remove') {
if (oper1.action == 'create') {
// 操作抵消
return null;
}
else {
operMerged.action = 'remove';
operMerged.data = cloneDeep(data);
}
}
else {
if (action !== 'update') {
if (oper1.action === 'update') {
oper1.action = action;
}
else {
assert(action === oper1.action, '不应当有冲突的update模式');
}
}
const { data: dataMerged } = operMerged;
// merge 两者的data要处理cascade部分
for (const attr in data) {
const rel = judgeRelation(schema, entity, attr);
if (rel === 1) {
dataMerged[attr] = cloneDeep(data[attr]);
}
else if (rel === 2) {
if (dataMerged[attr]) {
const merged = mergeOperation(schema, attr, dataMerged[attr], data[attr]);
if (!merged) {
unset(dataMerged, attr);
}
else {
dataMerged[attr] = merged;
}
}
else {
dataMerged[attr] = cloneDeep(data[attr]);
}
}
else if (typeof rel === 'string') {
if (dataMerged[attr]) {
const merged = mergeOperation(schema, rel, dataMerged[attr], data[attr]);
if (!merged) {
unset(dataMerged, attr);
}
else {
dataMerged[attr] = cloneDeep(merged);
}
}
else {
dataMerged[attr] = data[attr];
}
}
else {
assert(rel instanceof Array);
if (!dataMerged[attr]) {
dataMerged[attr] = cloneDeep(data[attr]);
}
else {
// 这条路径在前台跑出来的情况现在还不好断言,先赋值,再调试
process.env.NODE_ENV === 'development' && console.warn('need check by Xc');
dataMerged[attr] = 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] : getRelevantIds(filter);
const key = JSON.stringify(ids.length > 0 ? ids : filter);
if (this.logs.length > 0) {
const update = this.logs[this.logs.length - 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) {
unset(logs, key);
}
else {
logs[key] = newOper;
}
return;
}
logs[key] = {
id: generateNewId(),
action,
data,
filter,
};
return;
}
}
this.logs.push({
lsn,
operations: {
[key]: {
id: generateNewId(),
action,
data,
filter,
}
},
});
if (lsn > this.maxLsn) {
this.maxLsn = lsn;
}
}
undo(filter) {
const ids = getRelevantIds(filter);
const key = JSON.stringify(ids.length > 0 ? ids : filter);
this.logs.forEach((log) => {
const { operations } = log;
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 {
unset(opers, key);
}
}
else {
opers[key] = cloneDeep(operations[key]);
}
}
}
return Object.values(opers);
}
isEmpty() {
return this.logs.length === 0;
}
}
class Node extends 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(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<ED, T>;
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 && cloneDeep(this.projection));
return projection;
}
setProjection(projection) {
// assert(!this.projection);
this.projection = projection;
}
judgeRelation(attr) {
const attr2 = attr.split(':')[0]; // 处理attr:next
return 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 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 { filter } = this.constructSelection(true, true, true);
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
assert(f.id.$in instanceof Array);
f.id.$in.forEach((id) => {
if (this.sr.hasOwnProperty(id)) {
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)) {
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 { filter } = this.constructSelection(true, true, true);
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 = intersection(ids, currentIds);
const diffed = difference(ids, currentIds);
/** 检查原本就在的,现在还在不在,
* 以及原本不在的,现在是不是满足条件了
* 后一种情况也可能原本就满足但不在当前的ids中这时是判断不出来的total+1可能不对但是应该是较少的case*/
const filter = this.constructFilters(true, true, true);
if (intersected.length) {
const rows = this.cache.get(this.entity, {
data: {
id: 1,
},
filter: combineFilters(this.entity, this.schema, [
...(filter || []),
{
id: {
$in: intersected,
}
}
])
});
const rowIds = rows.map(ele => ele.id);
const missed = difference(intersected, rowIds);
missed.forEach((id) => {
unset(this.sr, id);
if (this.pagination.total) {
this.pagination.total--;
}
});
}
if (diffed.length) {
diffed.forEach((ele) => {
this.sr[ele] = {};
if (this.pagination.total) {
this.pagination.total++;
}
});
}
}
// 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) {
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) {
assert(!this.children[path]);
// assert(path.length > 10, 'List的path改成了id');
this.children[path] = node;
assert(this.sr); // listNode的子结点不可能在取得数据前就建立吧 by Xc
node.saveRefreshResult({
[path]: this.sr[path] || {},
});
}
removeChild(path) {
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);
const ids2 = ids.concat(createIds);
/**
* 在非modi状态下当前的逻辑是用当前filter加以过滤不然就处理不了在列表中操作改变了filter中外联的条件例如为order创建了一个ship但查询条件是没有ship列表不发生变化
* 所以在这里当前是要求用户在取数据的时候将可能影响的这种外联的数据也取到cache当中
* by Xc 20250315
*/
const filter2 = inModiNextBranch ? filter : (ids2.length > 0 ? (filter ? 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 || generateNewId();
const now = Date.now();
this.ulManager.push(lsn, {
action: 'create',
data: Object.assign(item, {
id,
[CreateAtAttribute]: now,
[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: {
[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 = [];
// 如果数据键值是一个空字符串则更新成nullundefined则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) => unset(data, attr));
}
updateItemInner(lsn, data, id, action) {
this.ulManager.push(lsn, {
action: action || 'update',
data: Object.assign({}, data, {
[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) {
const { filters: ownFilters } = this;
const filters = ownFilters.filter(ele => (!ignoreUnapplied || ele.applied === true || ele.applied === undefined) // 如果是undefined说明不可以移除构造时就存在也得返回
).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 cloneDeep(filters);
}
constructSelection(withParent, ignoreNewParent, ignoreUnapplied) {
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);
const filters2 = filters?.filter((ele) => !!ele);
const filter = filters2 ? 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;
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;
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来计算下一页
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 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) {
assert(path);
parent.addChild(path, this);
}
if (id) {
if (this.id) {
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 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();
assert(operations.length <= 1);
const [operation] = operations;
if (operation?.action === 'create') {
if (operation.data.id === id) {
// 如果本身是create 这里无视就行(因为框架原因会调用一次)
return;
}
else {
// 这种情况是oakId属性没有初始化完成
this.clean(0, true);
}
}
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();
assert(operations.length <= 1);
const [operation] = operations;
if (operation && operation?.action === 'create') {
return operation.data.id;
}
}
getChildren() {
return this.children;
}
addChild(path, node) {
assert(!this.children[path], `无法定义相同路径[${path}]下的子节点`);
this.children[path] = node;
this.passRsToChild(path);
}
removeChild(path) {
unset(this.children, path);
}
getFreshValue() {
const inModiNextBranch = this.isInModiNextBranch();
const projection = this.getProjection(false);
const id = this.getId();
if (projection && id) {
/**
* 这里在非modi状态下原来的代码是不会去刷新缺失的数据原因不明可能是认为页面应当自己负责数据的获取
* 在modi状态下有些外键指向的数据无法预先获取因此需要加上这个逻辑
*
* 先放回来,不知道有什么问题
* by Xc 20240229
*/
const result = this.cache.get(this.entity, {
data: projection,
filter: {
id,
},
}, 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 = generateNewId();
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,
[CreateAtAttribute]: now,
[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) => unset(data, attr));
this.ulManager.push(lsn, {
action: action || 'update',
data: Object.assign({}, data, {
[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();
assert(id, '当前节点的remove必须提供id');
this.ulManager.push(lsn, {
action: 'remove',
data: {
[DeleteAtAttribute]: Date.now(),
},
filter: {
id,
}
});
this.setDirty();
}
setDirty() {
const id = this.getId();
assert(id);
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) {
assert(childOperations.length === 1);
this.ulManager.push(lsnMax + 100, {
action: 'update',
data: {
[ele2]: childOperations[0].operation,
},
filter: {
id: this.getId(),
}
});
}
else {
assert(child instanceof 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);
assert(operations.length <= 1);
const [operation] = operations;
if (operation) {
const { filter, ...rest } = operation;
const intrinsticFilter = this.getIntrinsticFilters();
return [
{
entity: this.entity,
operation: {
...rest,
filter: intrinsticFilter ? combineFilters(this.entity, this.schema, [
{
id: this.getId(),
},
intrinsticFilter
]) : {
id: this.getId(),
}
},
}
];
}
}
}
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(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];
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) {
assert(child instanceof SingleNode);
assert(value.entity === child.getEntity());
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`]) {
assert(child instanceof SingleNode);
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 {
assert(rel instanceof Array);
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) {
assert(!this.id, '通过id查询数据结果为空');
}
// 这个情况不应该出现,查询数据的时候有判断,这里不可能出现>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();
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() {
// 如果是新建等于没有filter
const [operation] = this.ulManager.makeOperations();
if (operation?.action === 'create') {
return;
}
// singleNode增加一些限定的filter可以优化后台权限的判断范围和一些trigger的条件
// 如果没有this.id则不返回避免一些奇怪的边界比如execute以后refresh
if (this.id) {
let filter = {
id: this.id,
};
if (this.filters) {
filter = combineFilters(this.entity, this.schema, this.filters.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 = 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);
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:numberListNode必须被命名为entitys或entitys:number
assert(!this.children[path], `无法定义相同路径[${path}]下的子节点`);
this.children[path] = child;
if (child instanceof SingleNode || child instanceof ListNode) {
const entity = child.getEntity();
if (child instanceof SingleNode) {
assert(path === entity || path.startsWith(`${entity}:`), `oakPath「${path}」不符合命名规范,请以「${entity}:」为命名起始标识`);
}
else {
assert(path === `${entity}s` || path.startsWith(`${entity}s:`), `oakPath「${path}」不符合命名规范,请以「${entity}s:」为命名起始标识`);
}
}
}
removeChild(path) {
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]) {
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],
};
}
export class RunningTree extends 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
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 {
assert(node instanceof SingleNode, `节点[${path}]应为Virtual节点但现在是[${entity.toString()}]请检查oakPath是否重复`);
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();
assert(id === id2, `重用path[${fullPath}]上的singleNode时其上没有置有效id这种情况id应当由父结点设置`);
}
}
else {
// 目前只有一种情况合法即parentNode是list列表中的位置移动引起的重用
// assert(parentNode instanceof ListNode, `创建node时发现path[${fullPath}]已有有效的SingleNode结点本情况不应当存在`);
}
}
}
// 上次不是virtualNode这次变成virtual了
else {
assert(false, `创建Virtual节点时发现path[${fullPath}]已经存在有效的结点请检查oakPath是否重复`);
// assert(false, '这种情况暂时不考虑跑出来再处理by Xc 20240717');
}
}
else {
// 这种情况是重用zombie留下的node啥也别做
if (node instanceof SingleNode || node instanceof ListNode) {
assert(node.getEntity() === entity, 'zombie留下的node entity不能变化');
}
}
}
else {
const rollback = this.begin();
if (parentNode) {
this.redoBranchModis(fullPath);
this.redoBranchOperations(fullPath);
}
if (entity) {
if (isList) {
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 {
assert(!parentNode || parentNode instanceof VirtualNode);
node = new VirtualNode(fullPath, path, parentNode, stale);
}
rollback();
if (!parentNode) {
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) {
assert(this.root.hasOwnProperty(path));
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);
assert(node instanceof ListNode, '只能在ListNode上调用addItem');
return node.addItem(this.logSerailNumber, data);
}
addItems(path, data) {
this.invalidateCachedOperations(path);
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用addItems');
return node.addItems(this.logSerailNumber, data);
}
removeItem(path, id) {
this.invalidateCachedOperations(path);
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用removeItem');
node.removeItem(this.logSerailNumber, id);
}
removeItems(path, ids) {
this.invalidateCachedOperations(path);
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用removeItems');
node.removeItems(this.logSerailNumber, ids);
}
updateItem(path, data, id, action) {
this.invalidateCachedOperations(path);
const node = this.findNode(path);
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);
assert(node instanceof ListNode, '只能在ListNode上调用updateItems');
node.updateItems(this.logSerailNumber, data, ids, action);
}
recoverItem(path, id) {
this.invalidateCachedOperations(path);
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用recoverItem');
node.recoverItem(id);
}
recoverItems(path, ids) {
this.invalidateCachedOperations(path);
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用recoverItems');
node.recoverItems(ids);
}
resetItem(path, id) {
this.invalidateCachedOperations(path);
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用resetItem');
node.resetItem(id);
}
create(path, data) {
this.invalidateCachedOperations(path);
const node = this.findNode(path);
assert(node instanceof SingleNode, '只能在SingleNode上调用create');
node.create(this.logSerailNumber, data);
}
update(path, data, action) {
this.invalidateCachedOperations(path);
const node = this.findNode(path);
assert(node instanceof SingleNode, '只能在SingleNode上调用update');
node.update(this.logSerailNumber, data, action);
}
remove(path) {
this.invalidateCachedOperations(path);
const node = this.findNode(path);
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(MODI_NEXT_PATH_SUFFIX)) {
return false;
}
const paths = path.split('.');
if (paths[0].includes(MODI_NEXT_PATH_SUFFIX)) {
// 根结点就是:next这时候还是要取的
return false;
}
let iter = 1;
while (iter < paths.length) {
if (paths[iter].includes(MODI_NEXT_PATH_SUFFIX)) {
const pathPrev = paths.slice(0, iter).join('.') + '.' + paths[iter].replace(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++;
}
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);
assert(node instanceof ListNode);
await node.loadMore();
}
getPagination(path) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用getPagination');
const pn = node.getPagination();
return {
...pn,
currentPage: pn.currentPage + 1,
};
}
setId(path, id) {
const node = this.findNode(path);
assert(node instanceof SingleNode, '只能在SingleNode上调用setId');
return node.setId(id);
}
unsetId(path) {
const node = this.findNode(path);
assert(node instanceof SingleNode, '只能在SingleNode上调用unsetId');
node.unsetId();
}
getId(path) {
const node = this.findNode(path);
assert(node instanceof SingleNode, '只能在SingleNode上调用getId');
return node.getId();
}
getEntity(path) {
const node = this.findNode(path);
assert(node instanceof EntityNode, '只能在EntityNode上调用getEntity');
return node.getEntity();
}
setPageSize(path, pageSize) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用setPageSize');
// 切换分页pageSize就重新设置
return node.setPagination({
pageSize,
currentPage: 0,
total: undefined,
});
}
setCurrentPage(path, currentPage) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用setCurrentPage');
return node.setCurrentPage(currentPage - 1);
}
getNamedFilters(path) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用getNamedFilters');
return node.getNamedFilters();
}
getNamedFilterByName(path, name) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用getNamedFilterByName');
return node.getNamedFilterByName(name);
}
setNamedFilters(path, filters, refresh = true) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用setNamedFilters');
node.setNamedFilters(filters, refresh);
}
addNamedFilter(path, filter, refresh = false) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用addNamedFilter');
return node.addNamedFilter(filter, refresh);
}
removeNamedFilter(path, filter, refresh = false) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用removeNamedFilter');
return node.removeNamedFilter(filter, refresh);
}
removeNamedFilterByName(path, name, refresh = false) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用removeNamedFilterByName');
return node.removeNamedFilterByName(name, refresh);
}
getNamedSorters(path) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用getNamedSorters');
return node.getNamedSorters();
}
getNamedSorterByName(path, name) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用getNamedSorterByName');
return node.getNamedSorterByName(name);
}
setNamedSorters(path, sorters, refresh = true) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用setNamedSorters');
return node.setNamedSorters(sorters, refresh);
}
addNamedSorter(path, sorter, refresh = false) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用addNamedSorter');
return node.addNamedSorter(sorter, refresh);
}
removeNamedSorter(path, sorter, refresh = false) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用removeNamedSorter');
return node.removeNamedSorter(sorter, refresh);
}
removeNamedSorterByName(path, name, refresh = false) {
const node = this.findNode(path);
assert(node instanceof ListNode, '只能在ListNode上调用removeNamedSorterByName');
return node.removeNamedSorterByName(name, refresh);
}
getIntrinsticFilters(path) {
const node = this.findNode(path);
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) => 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(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) {
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;
}
}
else if (node) {
// 老的写法直接对一个非脏的结点execute某个action也可以支持
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;
}
}
}
if (operations.length > 0) {
const entities = uniq(operations.filter((ele) => !!ele).map((ele) => ele.entity));
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) {
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);
}
}