initialize
This commit is contained in:
commit
4a330123c2
|
|
@ -0,0 +1,67 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
|
||||
.idea/
|
||||
|
||||
|
||||
.vscode
|
||||
|
||||
build
|
||||
package-lock.json
|
||||
dist
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extension": ["ts"],
|
||||
"spec": "test/test*.ts",
|
||||
"require": "ts-node/register"
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { Context as ContextInterface } from 'oak-domain/lib/types/Context';
|
||||
import { EntityDef, EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
import TreeStore from './store';
|
||||
export declare class Context<E extends string, ED extends {
|
||||
[K in E]: EntityDef<E, ED, K, SH>;
|
||||
}, SH extends EntityShape = EntityShape> implements ContextInterface<E, ED, SH> {
|
||||
rowStore: TreeStore<E, ED, SH>;
|
||||
uuid?: string;
|
||||
constructor(store: TreeStore<E, ED, SH>);
|
||||
on(event: 'commit' | 'rollback', callback: (context: ContextInterface<E, ED, SH>) => Promise<void>): void;
|
||||
begin(options?: object): Promise<void>;
|
||||
commit(): Promise<void>;
|
||||
rollback(): Promise<void>;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Context = void 0;
|
||||
const assert_1 = __importDefault(require("assert"));
|
||||
const uuid_1 = require("uuid");
|
||||
class Context {
|
||||
rowStore;
|
||||
uuid;
|
||||
constructor(store) {
|
||||
this.rowStore = store;
|
||||
}
|
||||
on(event, callback) {
|
||||
}
|
||||
async begin(options) {
|
||||
(0, assert_1.default)(!this.uuid);
|
||||
this.uuid = (0, uuid_1.v4)();
|
||||
this.rowStore.begin(this.uuid);
|
||||
}
|
||||
async commit() {
|
||||
(0, assert_1.default)(this.uuid);
|
||||
this.rowStore.commit(this.uuid);
|
||||
this.uuid = undefined;
|
||||
}
|
||||
async rollback() {
|
||||
(0, assert_1.default)(this.uuid);
|
||||
this.rowStore.rollback(this.uuid);
|
||||
this.uuid = undefined;
|
||||
}
|
||||
}
|
||||
exports.Context = Context;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import TreeStore from "./store";
|
||||
import { Context } from './context';
|
||||
export { TreeStore, Context, };
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Context = exports.TreeStore = void 0;
|
||||
const store_1 = __importDefault(require("./store"));
|
||||
exports.TreeStore = store_1.default;
|
||||
const context_1 = require("./context");
|
||||
Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return context_1.Context; } });
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import { EntityDef, SelectionResult, DeduceCreateSingleOperation, DeduceSelection, EntityShape, DeduceRemoveOperation, DeduceUpdateOperation } from "oak-domain/lib/types/Entity";
|
||||
import { CascadeStore } from 'oak-domain/lib/schema/CascadeStore';
|
||||
import { StorageSchema } from 'oak-domain/lib/types/Storage';
|
||||
import { Context } from "./context";
|
||||
import { NodeDict, RowNode } from "./types/type";
|
||||
export default class TreeStore<E extends string, ED extends {
|
||||
[K in E]: EntityDef<E, ED, K, SH>;
|
||||
}, SH extends EntityShape = EntityShape> extends CascadeStore<E, ED, SH> {
|
||||
store: {
|
||||
[T in E]?: {
|
||||
[ID: string]: RowNode<SH>;
|
||||
};
|
||||
};
|
||||
immutable: boolean;
|
||||
activeTxnDict: {
|
||||
[T: string]: {
|
||||
nodeHeader?: RowNode<SH>;
|
||||
create: number;
|
||||
update: number;
|
||||
remove: number;
|
||||
};
|
||||
};
|
||||
constructor(storageSchema: StorageSchema, immutable?: boolean);
|
||||
private constructRow;
|
||||
private translateLogicFilter;
|
||||
/**
|
||||
* 对表达式中某个结点的翻译,有三种情况:
|
||||
* 1、结点是一个表达式,此时递归翻译其子结点
|
||||
* 2、结点是一个常量,直接返回
|
||||
* 3、结点引用了某个属性,此时返回一个函数(ExprNodeTranslator),该函数在实际执行时对某行进行处理,又可能有两种case:
|
||||
* 3.1、得到结果,此时返回结果的值(常量)
|
||||
* 3.2、还欠缺某些外部结点的值才能得到结果,此时返回一个函数(ExprLaterCheckFn),此函数可以在执行中获得更多结点之后再调用并得到结果的值
|
||||
* @param entity
|
||||
* @param expression
|
||||
* @param context
|
||||
* @returns
|
||||
*/
|
||||
private translateExpressionNode;
|
||||
private translateExpression;
|
||||
private translateFulltext;
|
||||
private translateAttribute;
|
||||
private translateFilter;
|
||||
private translateSorter;
|
||||
protected selectAbjointRow<T extends E>(entity: T, selection: Omit<ED[T]['Selection'], 'indexFrom' | 'count' | 'data' | 'sorter'>, context: Context<E, ED, SH>, params?: Object): Promise<SelectionResult<E, ED, T, SH>>;
|
||||
protected updateAbjointRow<T extends E>(entity: T, operation: DeduceCreateSingleOperation<E, ED, T, SH> | DeduceUpdateOperation<E, ED, T, SH> | DeduceRemoveOperation<E, ED, T, SH>, context: Context<E, ED, SH>, params?: Object): Promise<void>;
|
||||
private doOperation;
|
||||
operate<T extends E>(entity: T, operation: ED[T]['Operation'], context: Context<E, ED, SH>, params?: Object): Promise<void>;
|
||||
protected formProjection<T extends E>(entity: T, row: ED[T]['Schema'], data: DeduceSelection<E, ED, T, SH>['data'], result: Partial<ED[T]['Schema']>, nodeDict: NodeDict<SH>, context: Context<E, ED, SH>): Promise<void>;
|
||||
private formResult;
|
||||
select<T extends E>(entity: T, selection: ED[T]['Selection'], context: Context<E, ED, SH>, params?: Object): Promise<SelectionResult<E, ED, T, SH>>;
|
||||
countRow<T extends E>(entity: T, selection: Omit<ED[T]['Selection'], "action" | "data" | "sorter">, context: Context<E, ED, SH>, params?: Object): Promise<number>;
|
||||
private addToTxnNode;
|
||||
begin(uuid: string): void;
|
||||
commit(uuid: string): void;
|
||||
rollback(uuid: string): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,799 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const lodash_1 = require("lodash");
|
||||
const assert_1 = __importDefault(require("assert"));
|
||||
const Demand_1 = require("oak-domain/lib/types/Demand");
|
||||
const CascadeStore_1 = require("oak-domain/lib/schema/CascadeStore");
|
||||
const OakError_1 = require("oak-domain/lib/OakError");
|
||||
const RowStore_1 = require("oak-domain/lib/types/RowStore");
|
||||
const Demand_2 = require("oak-domain/lib/types/Demand");
|
||||
const relation_1 = require("oak-domain/lib/schema/relation");
|
||||
const Expression_1 = require("oak-domain/lib/types/Expression");
|
||||
;
|
||||
;
|
||||
class TreeStore extends CascadeStore_1.CascadeStore {
|
||||
store;
|
||||
immutable;
|
||||
activeTxnDict;
|
||||
constructor(storageSchema, immutable = false) {
|
||||
super(storageSchema);
|
||||
this.immutable = immutable;
|
||||
this.store = {};
|
||||
this.activeTxnDict = {};
|
||||
}
|
||||
constructRow(node, context) {
|
||||
let data = node.$current;
|
||||
if (context.uuid && node.$uuid === context.uuid) {
|
||||
if (!node.$next) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
data = (0, lodash_1.assign)({}, node.$current, node.$next);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
translateLogicFilter(entity, filter, attr, context, params) {
|
||||
switch (attr) {
|
||||
case '$and': {
|
||||
const filters = filter[attr];
|
||||
const fns = filters.map(ele => this.translateFilter(entity, ele, context, params));
|
||||
return async (node, nodeDict, exprResolveFns) => {
|
||||
for (const fn of fns) {
|
||||
if (!await (await fn)(node, nodeDict, exprResolveFns)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
case '$or': {
|
||||
const filters = filter[attr];
|
||||
const fns = filters.map(ele => this.translateFilter(entity, ele, context, params));
|
||||
return async (node, nodeDict, exprResolveFns) => {
|
||||
for (const fn of fns) {
|
||||
if (await (await fn)(node, nodeDict, exprResolveFns)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
default: {
|
||||
(0, assert_1.default)(false, `${attr}算子暂不支持`);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 对表达式中某个结点的翻译,有三种情况:
|
||||
* 1、结点是一个表达式,此时递归翻译其子结点
|
||||
* 2、结点是一个常量,直接返回
|
||||
* 3、结点引用了某个属性,此时返回一个函数(ExprNodeTranslator),该函数在实际执行时对某行进行处理,又可能有两种case:
|
||||
* 3.1、得到结果,此时返回结果的值(常量)
|
||||
* 3.2、还欠缺某些外部结点的值才能得到结果,此时返回一个函数(ExprLaterCheckFn),此函数可以在执行中获得更多结点之后再调用并得到结果的值
|
||||
* @param entity
|
||||
* @param expression
|
||||
* @param context
|
||||
* @returns
|
||||
*/
|
||||
translateExpressionNode(entity, expression, context) {
|
||||
if ((0, Expression_1.isExpression)(expression)) {
|
||||
const op = Object.keys(expression)[0];
|
||||
const params = expression[op];
|
||||
if ((0, Expression_1.opMultipleParams)(op)) {
|
||||
const paramsTranslated = params.map(ele => this.translateExpressionNode(entity, ele, context));
|
||||
return (row, nodeDict) => {
|
||||
let later = false;
|
||||
let results = paramsTranslated.map((ele) => {
|
||||
if (typeof ele === 'function') {
|
||||
const r = ele(row, nodeDict);
|
||||
if (typeof r === 'function') {
|
||||
later = true;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
return ele;
|
||||
});
|
||||
if (!later) {
|
||||
return (0, Expression_1.execOp)(op, results);
|
||||
}
|
||||
const laterCheckFn = (nodeDict2) => {
|
||||
results = results.map((ele) => {
|
||||
if (typeof ele === 'function') {
|
||||
const r = ele(nodeDict2);
|
||||
return r;
|
||||
}
|
||||
return ele;
|
||||
});
|
||||
if (results.find(ele => typeof ele === 'function')) {
|
||||
return laterCheckFn;
|
||||
}
|
||||
return (0, Expression_1.execOp)(op, results);
|
||||
};
|
||||
return laterCheckFn;
|
||||
};
|
||||
}
|
||||
else {
|
||||
const paramsTranslated = this.translateExpressionNode(entity, params, context);
|
||||
if (typeof paramsTranslated === 'function') {
|
||||
return (row, nodeDict) => {
|
||||
let result = paramsTranslated(row, nodeDict);
|
||||
if (typeof result === 'function') {
|
||||
const laterCheckFn = (nodeDict2) => {
|
||||
result = result(nodeDict2);
|
||||
if (typeof result === 'function') {
|
||||
return laterCheckFn;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
return laterCheckFn;
|
||||
}
|
||||
return (0, Expression_1.execOp)(op, result);
|
||||
};
|
||||
}
|
||||
else {
|
||||
return () => {
|
||||
return (0, Expression_1.execOp)(op, paramsTranslated);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((0, Demand_2.isRefAttrNode)(expression)) {
|
||||
// 是RefAttr结点
|
||||
return (row, nodeDict) => {
|
||||
if (expression.hasOwnProperty('#attr')) {
|
||||
// 说明是本结点的属性;
|
||||
return row[expression['#attr']];
|
||||
}
|
||||
else {
|
||||
(0, assert_1.default)(expression.hasOwnProperty('#refId'));
|
||||
const { ['#refId']: refId, ['#refAttr']: refAttr } = expression;
|
||||
if (nodeDict.hasOwnProperty(refId)) {
|
||||
return nodeDict[refId][refAttr];
|
||||
}
|
||||
// 引用的结点还没有取到,此时需要在未来的某个时刻再检查
|
||||
const laterCheckFn = (nodeDict2) => {
|
||||
if (nodeDict2.hasOwnProperty(refId)) {
|
||||
return nodeDict2[refId][refAttr];
|
||||
}
|
||||
return laterCheckFn;
|
||||
};
|
||||
return laterCheckFn;
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
// 是常量结点
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
translateExpression(entity, expression, context) {
|
||||
const expr = this.translateExpressionNode(entity, expression, context);
|
||||
return async (row, nodeDict) => {
|
||||
if (typeof expr !== 'function') {
|
||||
return expr;
|
||||
}
|
||||
const result = expr(row, nodeDict);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
translateFulltext(entity, filter, context) {
|
||||
// 全文索引查找
|
||||
const { [entity]: { indexes } } = this.storageSchema;
|
||||
const fulltextIndex = indexes.find(ele => ele.config && ele.config.type === 'fulltext');
|
||||
const { attributes } = fulltextIndex;
|
||||
const { $search } = filter;
|
||||
return async (node) => {
|
||||
const row = this.constructRow(node, context);
|
||||
for (const attr of attributes) {
|
||||
const { name } = attr;
|
||||
if (row && row[name] && typeof row[name] === 'string' && row[name].contains($search)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
async translateAttribute(filter, attr, context, params) {
|
||||
if (typeof filter !== 'object') {
|
||||
return async (node) => {
|
||||
const row = this.constructRow(node, context);
|
||||
return row ? row[attr] === filter : false;
|
||||
};
|
||||
}
|
||||
const fns = [];
|
||||
for (const op in filter) {
|
||||
switch (op) {
|
||||
case '$gt': {
|
||||
fns.push(async (row) => row[attr] > filter[op]);
|
||||
break;
|
||||
}
|
||||
case '$lt': {
|
||||
fns.push(async (row) => row[attr] < filter[op]);
|
||||
break;
|
||||
}
|
||||
case '$gte': {
|
||||
fns.push(async (row) => row[attr] >= filter[op]);
|
||||
break;
|
||||
}
|
||||
case '$lte': {
|
||||
fns.push(async (row) => row[attr] <= filter[op]);
|
||||
break;
|
||||
}
|
||||
case '$eq': {
|
||||
fns.push(async (row) => row[attr] === filter[op]);
|
||||
break;
|
||||
}
|
||||
case '$ne': {
|
||||
fns.push(async (row) => row[attr] !== filter[op]);
|
||||
break;
|
||||
}
|
||||
case '$between': {
|
||||
fns.push(async (row) => {
|
||||
return row[attr] >= filter[op][0] && row[attr] <= filter[op][1];
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '$startsWith': {
|
||||
fns.push(async (row) => {
|
||||
(0, assert_1.default)(typeof row[attr] === 'string');
|
||||
return row[attr].startsWith(filter[op]);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '$endsWith': {
|
||||
fns.push(async (row) => {
|
||||
(0, assert_1.default)(typeof row[attr] === 'string');
|
||||
return row[attr].$endsWith(filter[op]);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '$includes': {
|
||||
fns.push(async (row) => {
|
||||
(0, assert_1.default)(typeof row[attr] === 'string');
|
||||
return row[attr].includes(filter[op]);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '$exists': {
|
||||
const exists = filter[op];
|
||||
(0, assert_1.default)(typeof exists === 'boolean');
|
||||
fns.push(async (row) => {
|
||||
if (exists) {
|
||||
return [null, undefined].includes(row[attr]);
|
||||
}
|
||||
else {
|
||||
return ![null, undefined].includes(row[attr]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '$in': {
|
||||
const inData = filter[op];
|
||||
(0, assert_1.default)(typeof inData === 'object');
|
||||
if (inData instanceof Array) {
|
||||
fns.push(async (row) => inData.includes(row[attr]));
|
||||
}
|
||||
else {
|
||||
// 这里只有当子查询中的filter不包含引用外部的子查询时才可以提前计算,否则必须等到执行时再计算
|
||||
try {
|
||||
const legalSets = (await this.selectAbjointRow(inData.entity, inData, context, params)).map((ele) => {
|
||||
const { data } = inData;
|
||||
const key = Object.keys(data)[0];
|
||||
return ele[key];
|
||||
});
|
||||
fns.push(async (row) => legalSets.includes(row[attr]));
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof OakError_1.OakError && err.$$code === RowStore_1.RowStore.$$CODES.expressionUnresolved[0]) {
|
||||
fns.push(async (row, nodeDict) => {
|
||||
(0, lodash_1.assign)(params, {
|
||||
nodeDict,
|
||||
});
|
||||
const legalSets = (await this.selectAbjointRow(inData.entity, inData, context, params)).map((ele) => {
|
||||
const { data } = inData;
|
||||
const key = Object.keys(data)[0];
|
||||
return ele[key];
|
||||
});
|
||||
(0, lodash_1.unset)(params, 'nodeDict');
|
||||
return legalSets.includes(row[attr]);
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '$nin': {
|
||||
const inData = filter[op];
|
||||
(0, assert_1.default)(typeof inData === 'object');
|
||||
if (inData instanceof Array) {
|
||||
fns.push(async (row) => !inData.includes(row[attr]));
|
||||
}
|
||||
else {
|
||||
// 这里只有当子查询中的filter不包含引用外部的子查询时才可以提前计算,否则必须等到执行时再计算
|
||||
try {
|
||||
const legalSets = (await this.selectAbjointRow(inData.entity, inData, context, params)).map((ele) => {
|
||||
const { data } = inData;
|
||||
const key = Object.keys(data)[0];
|
||||
return ele[key];
|
||||
});
|
||||
fns.push(async (row) => !legalSets.includes(row[attr]));
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof OakError_1.OakError && err.$$code === RowStore_1.RowStore.$$CODES.expressionUnresolved[0]) {
|
||||
fns.push(async (row, nodeDict) => {
|
||||
(0, lodash_1.assign)(params, {
|
||||
nodeDict,
|
||||
});
|
||||
const legalSets = (await this.selectAbjointRow(inData.entity, inData, context, params)).map((ele) => {
|
||||
const { data } = inData;
|
||||
const key = Object.keys(data)[0];
|
||||
return ele[key];
|
||||
});
|
||||
(0, lodash_1.unset)(params, 'nodeDict');
|
||||
return !legalSets.includes(row[attr]);
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return async (node, nodeDict, exprResolveFns) => {
|
||||
const row = this.constructRow(node, context);
|
||||
if (!row) {
|
||||
return false;
|
||||
}
|
||||
for (const fn of fns) {
|
||||
if (await fn(row, nodeDict, exprResolveFns) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
async translateFilter(entity, filter, context, params) {
|
||||
const fns = [];
|
||||
let nodeId;
|
||||
for (const attr in filter) {
|
||||
if (attr === '#id') {
|
||||
nodeId = filter['#id'];
|
||||
}
|
||||
else if (['$and', '$or', '$xor', '$not'].includes(attr)) {
|
||||
fns.push(this.translateLogicFilter(entity, filter, attr, context, params));
|
||||
}
|
||||
else if (attr.toLowerCase().startsWith(Demand_1.EXPRESSION_PREFIX)) {
|
||||
const fn = this.translateExpression(entity, filter[attr], context);
|
||||
fns.push(async (node, nodeDict, exprResolveFns) => {
|
||||
const row = this.constructRow(node, context);
|
||||
if (!row) {
|
||||
return false;
|
||||
}
|
||||
const result = await fn(row, nodeDict);
|
||||
if (typeof result === 'function') {
|
||||
exprResolveFns.push(result);
|
||||
}
|
||||
return !!result;
|
||||
});
|
||||
}
|
||||
else if (attr.toLowerCase() === '$text') {
|
||||
fns.push(this.translateFulltext(entity, filter[attr], context));
|
||||
}
|
||||
else {
|
||||
// 属性级过滤
|
||||
const relation = (0, relation_1.judgeRelation)(this.storageSchema, entity, attr);
|
||||
if (relation === 1) {
|
||||
// 行本身的属性
|
||||
fns.push(await this.translateAttribute(filter[attr], attr, context, params));
|
||||
}
|
||||
else if (relation === 2) {
|
||||
// 基于entity/entityId的指针
|
||||
const fn = await this.translateFilter(attr, filter[attr], context, params);
|
||||
fns.push(async (node, nodeDict, exprResolveFns) => {
|
||||
const row = this.constructRow(node, context);
|
||||
if (row.entity !== attr) {
|
||||
return false;
|
||||
}
|
||||
const node2 = (0, lodash_1.get)(this.store, `${attr}.${row.entityId}`);
|
||||
(0, assert_1.default)(node2);
|
||||
return fn(node2, nodeDict, exprResolveFns);
|
||||
});
|
||||
}
|
||||
else {
|
||||
(0, assert_1.default)(typeof relation === 'string');
|
||||
// 只能是基于普通属性的外键
|
||||
const fn = await this.translateFilter(relation, filter[attr], context, params);
|
||||
fns.push(async (node, nodeDict, exprResolveFns) => {
|
||||
const row = this.constructRow(node, context);
|
||||
const node2 = (0, lodash_1.get)(this.store, `${relation}.${row[`${attr}Id`]}`);
|
||||
(0, assert_1.default)(node2);
|
||||
return fn(node2, nodeDict, exprResolveFns);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return async (node, nodeDict, exprResolveFns) => {
|
||||
if (nodeId) {
|
||||
(0, assert_1.default)(!nodeDict.hasOwnProperty(nodeId), new OakError_1.OakError(RowStore_1.RowStore.$$LEVEL, RowStore_1.RowStore.$$CODES.nodeIdRepeated, `Filter中的nodeId「${nodeId}」出现了多次`));
|
||||
(0, lodash_1.assign)(nodeDict, {
|
||||
[nodeId]: this.constructRow(node, context),
|
||||
});
|
||||
}
|
||||
const row = this.constructRow(node, context);
|
||||
if (!row) {
|
||||
return false;
|
||||
}
|
||||
for (const fn of fns) {
|
||||
if (!await fn(node, nodeDict, exprResolveFns)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
translateSorter(entity, sorter, context) {
|
||||
const compare = (row1, row2, entity2, sortAttr, direction) => {
|
||||
const row11 = row1;
|
||||
const row22 = row2;
|
||||
(0, assert_1.default)(Object.keys(sortAttr).length === 1);
|
||||
const attr = Object.keys(sortAttr)[0];
|
||||
const relation = (0, relation_1.judgeRelation)(this.storageSchema, entity2, attr);
|
||||
if (relation === 1) {
|
||||
const v1 = row1 && row11[attr];
|
||||
const v2 = row2 && row22[attr];
|
||||
if ([null, undefined].includes(v1) || [null, undefined].includes(v2)) {
|
||||
if ([null, undefined].includes(v1) && [null, undefined].includes(v2)) {
|
||||
return 0;
|
||||
}
|
||||
if ([null, undefined].includes(v1)) {
|
||||
if (direction === 'asc') {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (direction === 'desc') {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (v1 > v2) {
|
||||
if (direction === 'desc') {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (v1 < v2) {
|
||||
if (direction === 'desc') {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (relation === 2) {
|
||||
(0, assert_1.default)(row11['entity'] === row22['entity']);
|
||||
(0, assert_1.default)(row11.entity === attr);
|
||||
const node1 = (0, lodash_1.get)(this.store, `${row11.entity}.${row11.entityId}`);
|
||||
const node2 = (0, lodash_1.get)(this.store, `${row22.entity}.${row22.entityId}`);
|
||||
;
|
||||
const row111 = this.constructRow(node1, context);
|
||||
const row222 = this.constructRow(node2, context);
|
||||
return compare(row111, row222, row11['entity'], sortAttr[attr], direction);
|
||||
}
|
||||
else {
|
||||
(0, assert_1.default)(typeof relation === 'string');
|
||||
const node1 = (0, lodash_1.get)(this.store, `${relation}.${row11[`${attr}Id`]}`);
|
||||
const node2 = (0, lodash_1.get)(this.store, `${relation}.${row22[`${attr}Id`]}`);
|
||||
const row111 = this.constructRow(node1, context);
|
||||
const row222 = this.constructRow(node2, context);
|
||||
return compare(row111, row222, relation, sortAttr[attr], direction);
|
||||
}
|
||||
}
|
||||
};
|
||||
return (row1, row2) => {
|
||||
for (const sorterElement of sorter) {
|
||||
const { $attr, $direction } = sorterElement;
|
||||
const result = compare(row1, row2, entity, $attr, $direction);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
async selectAbjointRow(entity, selection, context, params = {}) {
|
||||
const { filter } = selection;
|
||||
const { nodeDict } = params;
|
||||
const filterFn = filter && this.translateFilter(entity, filter, context, params);
|
||||
const entityNodes = this.store[entity] ? Object.values(this.store[entity]) : [];
|
||||
const nodes = [];
|
||||
for (const n of entityNodes) {
|
||||
const nodeDict2 = {};
|
||||
if (nodeDict) {
|
||||
(0, lodash_1.assign)(nodeDict2, nodeDict);
|
||||
}
|
||||
const exprResolveFns = [];
|
||||
if (!filterFn || await (await filterFn)(n, nodeDict2, exprResolveFns)) {
|
||||
// 如果有延时处理的expression,在这里加以判断,此时所有在filter中的node应该都已经加以遍历了
|
||||
let exprResult = true;
|
||||
if (exprResolveFns.length > 0) {
|
||||
for (const fn of exprResolveFns) {
|
||||
const result = fn(nodeDict2);
|
||||
if (typeof result === 'function') {
|
||||
throw new OakError_1.OakError(RowStore_1.RowStore.$$LEVEL, RowStore_1.RowStore.$$CODES.expressionUnresolved, `表达式计算失败,请检查Filter中的结点编号和引用是否一致`);
|
||||
}
|
||||
if (!!!result) {
|
||||
exprResult = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exprResult) {
|
||||
nodes.push(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
const rows = nodes.map((node) => this.constructRow(node, context));
|
||||
return rows;
|
||||
}
|
||||
async updateAbjointRow(entity, operation, context, params = {}) {
|
||||
const { data, action } = operation;
|
||||
switch (action) {
|
||||
case 'create': {
|
||||
const { id } = data;
|
||||
const row = (0, lodash_1.get)(this.store, `${entity}.${id}`);
|
||||
if (row) {
|
||||
throw new OakError_1.OakError(RowStore_1.RowStore.$$LEVEL, RowStore_1.RowStore.$$CODES.primaryKeyConfilict);
|
||||
}
|
||||
const node = {
|
||||
$uuid: context.uuid,
|
||||
$current: null,
|
||||
$next: data,
|
||||
$path: `${entity}.${id}`,
|
||||
};
|
||||
(0, lodash_1.set)(this.store, `${entity}.${id}`, node);
|
||||
this.addToTxnNode(node, context, 'create');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const selection = (0, lodash_1.assign)({}, operation, {
|
||||
data: {
|
||||
id: 1,
|
||||
},
|
||||
action: 'select',
|
||||
});
|
||||
const rows = await this.selectAbjointRow(entity, selection, context, params);
|
||||
const ids = rows.map(ele => ele.id);
|
||||
ids.forEach((id) => {
|
||||
const node = (this.store[entity])[id];
|
||||
(0, assert_1.default)(node && !node.$uuid && node.$next === undefined);
|
||||
node.$uuid = context.uuid;
|
||||
if (action === 'remove') {
|
||||
node.$next = null;
|
||||
node.$path = `${entity}.${id}`,
|
||||
this.addToTxnNode(node, context, 'remove');
|
||||
}
|
||||
else {
|
||||
node.$next = data;
|
||||
this.addToTxnNode(node, context, 'update');
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
async doOperation(entity, operation, context, params) {
|
||||
const { action } = operation;
|
||||
if (action === 'select') {
|
||||
// return this.cascadeSelect(entity, operation as any, context, params);
|
||||
(0, assert_1.default)(false);
|
||||
}
|
||||
else {
|
||||
return this.cascadeUpdate(entity, operation, context, params);
|
||||
}
|
||||
}
|
||||
async operate(entity, operation, context, params) {
|
||||
let autoCommit = false;
|
||||
if (!context.uuid) {
|
||||
autoCommit = true;
|
||||
await context.begin();
|
||||
}
|
||||
try {
|
||||
await this.doOperation(entity, operation, context, params);
|
||||
}
|
||||
catch (err) {
|
||||
if (autoCommit) {
|
||||
await context.rollback();
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
if (autoCommit) {
|
||||
await context.commit();
|
||||
}
|
||||
}
|
||||
async formProjection(entity, row, data, result, nodeDict, context) {
|
||||
const row2 = row;
|
||||
const data2 = data;
|
||||
const laterExprDict = {};
|
||||
for (const attr in data) {
|
||||
if (attr.startsWith(Demand_1.EXPRESSION_PREFIX)) {
|
||||
const ExprNodeTranslator = this.translateExpression(entity, data2[attr], context);
|
||||
const exprResult = await ExprNodeTranslator(row, nodeDict);
|
||||
if (typeof exprResult === 'function') {
|
||||
(0, lodash_1.assign)(laterExprDict, {
|
||||
[attr]: exprResult,
|
||||
});
|
||||
}
|
||||
else {
|
||||
(0, lodash_1.assign)(result, {
|
||||
[attr]: exprResult,
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (attr === '#id') {
|
||||
const nodeId = data[attr];
|
||||
(0, assert_1.default)(!nodeDict.hasOwnProperty(nodeId), new OakError_1.OakError(RowStore_1.RowStore.$$LEVEL, RowStore_1.RowStore.$$CODES.nodeIdRepeated, `Filter中的nodeId「${nodeId}」出现了多次`));
|
||||
(0, lodash_1.assign)(nodeDict, {
|
||||
[nodeId]: row,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const attr in data) {
|
||||
if (!attr.startsWith(Demand_1.EXPRESSION_PREFIX) && attr !== '#id') {
|
||||
const relation = (0, relation_1.judgeRelation)(this.storageSchema, entity, attr);
|
||||
if (relation === 1) {
|
||||
(0, lodash_1.assign)(result, {
|
||||
[attr]: row2[attr],
|
||||
});
|
||||
}
|
||||
else if (relation === 2) {
|
||||
const result2 = {};
|
||||
await this.formProjection(attr, row2[attr], data2[attr], result2, nodeDict, context);
|
||||
(0, lodash_1.assign)(result, {
|
||||
[attr]: result2,
|
||||
entity: row2.entity,
|
||||
entityId: row2.entityId,
|
||||
});
|
||||
}
|
||||
else if (typeof relation === 'string') {
|
||||
const result2 = {};
|
||||
await this.formProjection(relation, row2[attr], data2[attr], result2, nodeDict, context);
|
||||
(0, lodash_1.assign)(result, {
|
||||
[attr]: result2,
|
||||
[`${attr}Id`]: row2[`${attr}Id`],
|
||||
});
|
||||
}
|
||||
else {
|
||||
(0, assert_1.default)(relation instanceof Array);
|
||||
(0, assert_1.default)(row2[attr] instanceof Array);
|
||||
const result2 = await this.formResult(relation[0], row2[attr], data2[attr], context, nodeDict);
|
||||
(0, lodash_1.assign)(result, {
|
||||
[attr]: result2,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const attr in laterExprDict) {
|
||||
const exprResult = laterExprDict[attr](nodeDict);
|
||||
// projection是不应出现计算不出来的情况
|
||||
(0, assert_1.default)(typeof exprResult !== 'function', new OakError_1.OakError(RowStore_1.RowStore.$$LEVEL, RowStore_1.RowStore.$$CODES.expressionUnresolved, 'data中的expr无法计算,请检查命名与引用的一致性'));
|
||||
(0, lodash_1.assign)(result, {
|
||||
[attr]: exprResult,
|
||||
});
|
||||
}
|
||||
}
|
||||
async formResult(entity, rows, selection, context, nodeDict) {
|
||||
const { data, sorter, indexFrom, count } = selection;
|
||||
// 先计算projection
|
||||
const rows2 = await Promise.all(rows.map(async (row) => {
|
||||
const result = {};
|
||||
const nodeDict2 = {};
|
||||
if (nodeDict) {
|
||||
(0, lodash_1.assign)(nodeDict2, nodeDict);
|
||||
}
|
||||
await this.formProjection(entity, row, data, result, nodeDict2, context);
|
||||
return result;
|
||||
}));
|
||||
return rows2;
|
||||
}
|
||||
async select(entity, selection, context, params) {
|
||||
const rows = await this.cascadeSelect(entity, selection, context, params);
|
||||
return await this.formResult(entity, rows, selection, context);
|
||||
}
|
||||
async countRow(entity, selection, context, params) {
|
||||
const rows = await this.cascadeSelect(entity, (0, lodash_1.assign)({}, selection, {
|
||||
data: {
|
||||
id: 1,
|
||||
}
|
||||
}), context, params);
|
||||
return rows.length;
|
||||
}
|
||||
addToTxnNode(node, context, action) {
|
||||
const txnNode = this.activeTxnDict[context.uuid];
|
||||
(0, assert_1.default)(txnNode);
|
||||
(0, assert_1.default)(!node.$nextNode);
|
||||
if (txnNode.nodeHeader) {
|
||||
node.$nextNode = txnNode.nodeHeader;
|
||||
txnNode.nodeHeader = node;
|
||||
}
|
||||
else {
|
||||
txnNode.nodeHeader = node;
|
||||
}
|
||||
txnNode[action]++;
|
||||
}
|
||||
begin(uuid) {
|
||||
(0, assert_1.default)(!this.activeTxnDict.hasOwnProperty(uuid));
|
||||
(0, lodash_1.assign)(this.activeTxnDict, {
|
||||
[uuid]: {
|
||||
create: 0,
|
||||
update: 0,
|
||||
remove: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
commit(uuid) {
|
||||
(0, assert_1.default)(this.activeTxnDict.hasOwnProperty(uuid));
|
||||
let node = this.activeTxnDict[uuid].nodeHeader;
|
||||
while (node) {
|
||||
const node2 = node.$nextNode;
|
||||
(0, assert_1.default)(node.$uuid === uuid);
|
||||
if (node.$next) {
|
||||
// create/update
|
||||
node.$current = (0, lodash_1.assign)(node.$current, node.$next);
|
||||
(0, lodash_1.unset)(node, '$uuid');
|
||||
(0, lodash_1.unset)(node, '$next');
|
||||
(0, lodash_1.unset)(node, '$path');
|
||||
(0, lodash_1.unset)(node, '$nextNode');
|
||||
}
|
||||
else {
|
||||
// remove
|
||||
(0, assert_1.default)(node.$path);
|
||||
(0, lodash_1.unset)(this.store, node.$path);
|
||||
}
|
||||
node = node2;
|
||||
}
|
||||
(0, lodash_1.unset)(this.activeTxnDict, uuid);
|
||||
}
|
||||
rollback(uuid) {
|
||||
(0, assert_1.default)(this.activeTxnDict.hasOwnProperty(uuid));
|
||||
let node = this.activeTxnDict[uuid].nodeHeader;
|
||||
while (node) {
|
||||
const node2 = node.$nextNode;
|
||||
(0, assert_1.default)(node.$uuid === uuid);
|
||||
if (node.$current) {
|
||||
// update/remove
|
||||
(0, lodash_1.unset)(node, '$uuid');
|
||||
(0, lodash_1.unset)(node, '$next');
|
||||
(0, lodash_1.unset)(node, '$path');
|
||||
(0, lodash_1.unset)(node, '$nextNode');
|
||||
}
|
||||
else {
|
||||
// create
|
||||
(0, assert_1.default)(node.$path);
|
||||
(0, lodash_1.unset)(this.store, node.$path);
|
||||
}
|
||||
node = node2;
|
||||
}
|
||||
(0, lodash_1.unset)(this.activeTxnDict, uuid);
|
||||
}
|
||||
}
|
||||
exports.default = TreeStore;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { NodeId } from "oak-domain/lib/types/Demand";
|
||||
import { EntityShape } from "oak-domain/src/types/Entity";
|
||||
export declare type RowNode<SH extends EntityShape = EntityShape> = {
|
||||
$uuid?: string;
|
||||
$next?: Partial<SH> | null;
|
||||
$current?: SH | null;
|
||||
$nextNode?: RowNode<SH>;
|
||||
$path?: string;
|
||||
};
|
||||
export declare type NodeDict<SH extends EntityShape = EntityShape> = {
|
||||
[K in NodeId]: SH;
|
||||
};
|
||||
export declare type ExprResolveFn<SH extends EntityShape = EntityShape> = (nodeDict: NodeDict<SH>) => ExprResolveFn<SH> | any;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "oak-memory-tree-store",
|
||||
"version": "1.0.0",
|
||||
"description": "oak框架中内存级store的实现",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^2.3.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
"pretest": "ts-node test/build-app-domain",
|
||||
"test": "ts-node test/test.ts",
|
||||
"build": "tsc"
|
||||
},
|
||||
"main": "src/index",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.12.13",
|
||||
"@babel/core": "^7.12.13",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.13",
|
||||
"@babel/preset-env": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.12.13",
|
||||
"@types/assert": "^1.5.6",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/mocha": "^8.2.0",
|
||||
"@types/node": "^14.14.25",
|
||||
"@types/react": "^17.0.2",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"assert": "^2.0.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"fs-extra": "^10.0.0",
|
||||
"mocha": "^8.2.1",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.5.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import assert from 'assert';
|
||||
import { v4 } from 'uuid';
|
||||
import { Context as ContextInterface } from 'oak-domain/lib/types/Context';
|
||||
import { EntityDef, EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
import TreeStore from './store';
|
||||
|
||||
export class Context<E extends string, ED extends {
|
||||
[K in E]: EntityDef<E, ED, K, SH>;
|
||||
}, SH extends EntityShape = EntityShape> implements ContextInterface<E, ED, SH> {
|
||||
rowStore: TreeStore<E, ED, SH>;
|
||||
uuid?: string;
|
||||
|
||||
constructor(store: TreeStore<E, ED, SH>) {
|
||||
this.rowStore = store;
|
||||
}
|
||||
|
||||
on(event: 'commit' | 'rollback', callback: (context: ContextInterface<E, ED, SH>) => Promise<void>): void {
|
||||
}
|
||||
|
||||
async begin(options?: object): Promise<void> {
|
||||
assert(!this.uuid);
|
||||
this.uuid = v4();
|
||||
this.rowStore.begin(this.uuid);
|
||||
}
|
||||
async commit(): Promise<void> {
|
||||
assert(this.uuid);
|
||||
this.rowStore.commit(this.uuid!);
|
||||
this.uuid = undefined;
|
||||
}
|
||||
async rollback(): Promise<void> {
|
||||
assert(this.uuid);
|
||||
this.rowStore.rollback(this.uuid!);
|
||||
this.uuid = undefined;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import TreeStore from "./store";
|
||||
import { Context } from './context';
|
||||
|
||||
export {
|
||||
TreeStore,
|
||||
Context,
|
||||
};
|
||||
|
|
@ -0,0 +1,991 @@
|
|||
import { assign, get, set, unset } from 'lodash';
|
||||
import assert from 'assert';
|
||||
import { EntityDef, SelectionResult, DeduceCreateSingleOperation, DeduceFilter, DeduceSelection, EntityShape, DeduceRemoveOperation, DeduceUpdateOperation, DeduceSorter, DeduceSorterAttr } from "oak-domain/lib/types/Entity";
|
||||
import { ExpressionKey, EXPRESSION_PREFIX, NodeId, RefAttr } from 'oak-domain/lib/types/Demand';
|
||||
import { CascadeStore } from 'oak-domain/lib/schema/CascadeStore';
|
||||
import { StorageSchema } from 'oak-domain/lib/types/Storage';
|
||||
import { OakError } from 'oak-domain/lib/OakError';
|
||||
import { Context } from "./context";
|
||||
import { ExprResolveFn, NodeDict, RowNode } from "./types/type";
|
||||
import { RowStore } from 'oak-domain/lib/types/RowStore';
|
||||
import { isRefAttrNode, Q_BooleanValue, Q_FullTextValue, Q_NumberValue, Q_StringValue } from 'oak-domain/lib/types/Demand';
|
||||
import { judgeRelation } from 'oak-domain/lib/schema/relation';
|
||||
import { execOp, Expression, ExpressionConstant, isExpression, opMultipleParams } from 'oak-domain/lib/types/Expression';
|
||||
|
||||
|
||||
interface ExprLaterCheckFn {
|
||||
(nodeDict: NodeDict): ExpressionConstant | ExprLaterCheckFn;
|
||||
};
|
||||
|
||||
interface ExprNodeTranslator {
|
||||
(row: any, nodeDict: NodeDict): ExpressionConstant | ExprLaterCheckFn;
|
||||
};
|
||||
|
||||
export default class TreeStore<E extends string, ED extends {
|
||||
[K in E]: EntityDef<E, ED, K, SH>;
|
||||
}, SH extends EntityShape = EntityShape> extends CascadeStore<E, ED, SH> {
|
||||
countextends: any;
|
||||
store: {
|
||||
[T in E]?: {
|
||||
[ID: string]: RowNode<SH>;
|
||||
};
|
||||
};
|
||||
immutable: boolean;
|
||||
activeTxnDict: {
|
||||
[T: string]: {
|
||||
nodeHeader?: RowNode<SH>;
|
||||
create: number;
|
||||
update: number;
|
||||
remove: number;
|
||||
};
|
||||
};
|
||||
|
||||
setInitialData(data: {
|
||||
[T in E]?: {
|
||||
[ID: string]: ED[T]['OpSchema'];
|
||||
};
|
||||
}) {
|
||||
for (const entity in data) {
|
||||
for (const rowId in data[entity]) {
|
||||
set(this.store, `${entity}.${rowId}.#current`, data[entity]![rowId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentData(): {
|
||||
[T in E]?: {
|
||||
[ID: string]: ED[T]['OpSchema'];
|
||||
};
|
||||
} {
|
||||
const result = {};
|
||||
for (const entity in this.store) {
|
||||
for (const rowId in this.store[entity]) {
|
||||
set(result, `${entity}.${rowId}`, this.store[entity]![rowId]!['$current']);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
constructor(storageSchema: StorageSchema, immutable: boolean = false, initialData?: {
|
||||
[T in E]?: {
|
||||
[ID: string]: ED[T]['OpSchema'];
|
||||
};
|
||||
}) {
|
||||
super(storageSchema);
|
||||
this.immutable = immutable;
|
||||
this.store = {};
|
||||
if (initialData) {
|
||||
this.setInitialData(initialData);
|
||||
}
|
||||
this.activeTxnDict = {};
|
||||
}
|
||||
|
||||
private constructRow(node: RowNode<SH>, context: Context<E, ED, SH>) {
|
||||
let data = node.$current;
|
||||
if (context.uuid && node.$uuid === context.uuid) {
|
||||
if (!node.$next) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
data = assign({}, node.$current, node.$next);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private translateLogicFilter<T extends E>(
|
||||
entity: T,
|
||||
filter: DeduceFilter<E, ED, T, SH>,
|
||||
attr: string,
|
||||
context: Context<E, ED, SH>,
|
||||
params: Object): (node: RowNode<SH>, nodeDict: NodeDict<SH>, exprResolveFns: Array<ExprResolveFn<SH>>) => Promise<boolean> {
|
||||
switch (attr) {
|
||||
case '$and': {
|
||||
const filters = filter[attr];
|
||||
const fns = filters!.map(
|
||||
ele => this.translateFilter(entity, ele, context, params)
|
||||
);
|
||||
return async (node, nodeDict, exprResolveFns) => {
|
||||
for (const fn of fns) {
|
||||
if (!await (await fn)(node, nodeDict, exprResolveFns)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
case '$or': {
|
||||
const filters = filter[attr];
|
||||
const fns = filters!.map(
|
||||
ele => this.translateFilter(entity, ele, context, params)
|
||||
);
|
||||
return async (node, nodeDict, exprResolveFns) => {
|
||||
for (const fn of fns) {
|
||||
if (await (await fn)(node, nodeDict, exprResolveFns)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
}
|
||||
default: {
|
||||
assert(false, `${attr}算子暂不支持`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对表达式中某个结点的翻译,有三种情况:
|
||||
* 1、结点是一个表达式,此时递归翻译其子结点
|
||||
* 2、结点是一个常量,直接返回
|
||||
* 3、结点引用了某个属性,此时返回一个函数(ExprNodeTranslator),该函数在实际执行时对某行进行处理,又可能有两种case:
|
||||
* 3.1、得到结果,此时返回结果的值(常量)
|
||||
* 3.2、还欠缺某些外部结点的值才能得到结果,此时返回一个函数(ExprLaterCheckFn),此函数可以在执行中获得更多结点之后再调用并得到结果的值
|
||||
* @param entity
|
||||
* @param expression
|
||||
* @param context
|
||||
* @returns
|
||||
*/
|
||||
private translateExpressionNode<T extends E>(
|
||||
entity: T,
|
||||
expression: Expression<keyof SH> | RefAttr<keyof SH> | ExpressionConstant,
|
||||
context: Context<E, ED, SH>): ExprNodeTranslator | ExpressionConstant {
|
||||
|
||||
if (isExpression(expression)) {
|
||||
const op = Object.keys(expression)[0];
|
||||
const params = (expression as any)[op];
|
||||
|
||||
if (opMultipleParams(op)) {
|
||||
const paramsTranslated = (params as (Expression<keyof SH> | RefAttr<keyof SH>)[]).map(
|
||||
ele => this.translateExpressionNode(entity, ele, context)
|
||||
);
|
||||
|
||||
return (row, nodeDict) => {
|
||||
let later = false;
|
||||
let results = paramsTranslated.map(
|
||||
(ele) => {
|
||||
if (typeof ele ==='function') {
|
||||
const r = ele(row, nodeDict);
|
||||
if (typeof r === 'function') {
|
||||
later = true;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
return ele;
|
||||
}
|
||||
);
|
||||
|
||||
if (!later) {
|
||||
return execOp(op, results);
|
||||
}
|
||||
const laterCheckFn = (nodeDict2: NodeDict) => {
|
||||
results = results.map(
|
||||
(ele) => {
|
||||
if (typeof ele === 'function') {
|
||||
const r = ele(nodeDict2);
|
||||
return r;
|
||||
}
|
||||
return ele;
|
||||
}
|
||||
);
|
||||
if (results.find(ele => typeof ele === 'function')) {
|
||||
return laterCheckFn;
|
||||
}
|
||||
return execOp(op, results);
|
||||
};
|
||||
return laterCheckFn;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const paramsTranslated = this.translateExpressionNode(entity, params, context);
|
||||
if (typeof paramsTranslated === 'function') {
|
||||
return (row, nodeDict) => {
|
||||
let result = paramsTranslated(row, nodeDict);
|
||||
if (typeof result === 'function') {
|
||||
const laterCheckFn = (nodeDict2: NodeDict) => {
|
||||
result = (result as ExprLaterCheckFn)(nodeDict2);
|
||||
if (typeof result === 'function') {
|
||||
return laterCheckFn;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return laterCheckFn;
|
||||
}
|
||||
return execOp(op, result);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return () => {
|
||||
return execOp(op, paramsTranslated);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isRefAttrNode(expression)) {
|
||||
// 是RefAttr结点
|
||||
return (row, nodeDict) => {
|
||||
if (expression.hasOwnProperty('#attr')) {
|
||||
// 说明是本结点的属性;
|
||||
return row[(expression as {
|
||||
'#attr': keyof SH;
|
||||
})['#attr']] as ExpressionConstant;
|
||||
}
|
||||
else {
|
||||
assert(expression.hasOwnProperty('#refId'));
|
||||
const { ['#refId']: refId, ['#refAttr']: refAttr } = expression as {
|
||||
'#refId': NodeId;
|
||||
'#refAttr': string;
|
||||
};
|
||||
if (nodeDict.hasOwnProperty(refId)) {
|
||||
return (nodeDict[refId] as any)[refAttr] as ExpressionConstant;
|
||||
}
|
||||
// 引用的结点还没有取到,此时需要在未来的某个时刻再检查
|
||||
const laterCheckFn = (nodeDict2: NodeDict) => {
|
||||
if (nodeDict2.hasOwnProperty(refId)) {
|
||||
return (nodeDict2[refId] as any)[refAttr] as ExpressionConstant;
|
||||
}
|
||||
return laterCheckFn;
|
||||
};
|
||||
return laterCheckFn;
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
// 是常量结点
|
||||
return expression as ExpressionConstant;
|
||||
}
|
||||
}
|
||||
|
||||
private translateExpression<T extends E>(
|
||||
entity: T,
|
||||
expression: Expression<keyof SH>,
|
||||
context: Context<E, ED, SH>): (row: ED[T]['Schema'], nodeDict: NodeDict<SH>) => Promise<ExpressionConstant | ExprLaterCheckFn> {
|
||||
const expr = this.translateExpressionNode(entity, expression, context);
|
||||
|
||||
return async (row, nodeDict) => {
|
||||
if (typeof expr !== 'function') {
|
||||
return expr;
|
||||
}
|
||||
const result = expr(row, nodeDict);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
private translateFulltext<T extends E>(
|
||||
entity: T,
|
||||
filter: Q_FullTextValue,
|
||||
context: Context<E, ED, SH>): (node: RowNode<SH>) => Promise<boolean> {
|
||||
// 全文索引查找
|
||||
const { [entity]: { indexes } } = this.storageSchema;
|
||||
const fulltextIndex = indexes!.find(
|
||||
ele => ele.config && ele.config.type === 'fulltext'
|
||||
);
|
||||
|
||||
const { attributes } = fulltextIndex!;
|
||||
const { $search } = filter;
|
||||
|
||||
return async (node) => {
|
||||
const row = this.constructRow(node, context) as any;
|
||||
for (const attr of attributes) {
|
||||
const { name } = attr;
|
||||
if (row && row[name] && typeof row[name] === 'string' && row[name].contains($search)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
private async translateAttribute<T extends E, U extends E>(filter: Q_NumberValue | Q_StringValue | Q_BooleanValue | ED[U]['Selection'] & {
|
||||
entity: U;
|
||||
}, attr: string, context: Context<E, ED, SH>, params: Object): Promise<(node: RowNode<SH>, nodeDict: NodeDict<SH>, exprResolveFns: Array<ExprResolveFn<SH>>) => Promise<boolean>> {
|
||||
if (typeof filter !== 'object') {
|
||||
return async (node) => {
|
||||
const row = this.constructRow(node, context);
|
||||
return row ? (row as any)[attr] === filter : false;
|
||||
};
|
||||
}
|
||||
const fns: Array<(row: any, nodeDict: NodeDict<SH>, exprResolveFns: Array<ExprResolveFn<SH>>) => Promise<boolean>> = [];
|
||||
for (const op in filter) {
|
||||
switch (op) {
|
||||
case '$gt': {
|
||||
fns.push(async (row) => row[attr] > (filter as any)[op]);
|
||||
break;
|
||||
}
|
||||
case '$lt': {
|
||||
fns.push(async (row) => row[attr] < (filter as any)[op]);
|
||||
break;
|
||||
}
|
||||
case '$gte': {
|
||||
fns.push(async (row) => row[attr] >= (filter as any)[op]);
|
||||
break;
|
||||
}
|
||||
case '$lte': {
|
||||
fns.push(async (row) => row[attr] <= (filter as any)[op]);
|
||||
break;
|
||||
}
|
||||
case '$eq': {
|
||||
fns.push(async (row) => row[attr] === (filter as any)[op]);
|
||||
break;
|
||||
}
|
||||
case '$ne': {
|
||||
fns.push(async (row) => row[attr] !== (filter as any)[op]);
|
||||
break;
|
||||
}
|
||||
case '$between': {
|
||||
fns.push(async (row) => {
|
||||
return row[attr] >= (filter as any)[op][0] && row[attr] <= (filter as any)[op][1];
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '$startsWith': {
|
||||
fns.push(async (row) => {
|
||||
assert(typeof row[attr] === 'string');
|
||||
return row[attr].startsWith((filter as any)[op]);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '$endsWith': {
|
||||
fns.push(async (row) => {
|
||||
assert(typeof row[attr] === 'string');
|
||||
return row[attr].$endsWith((filter as any)[op]);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '$includes': {
|
||||
fns.push(async (row) => {
|
||||
assert(typeof row[attr] === 'string');
|
||||
return row[attr].includes((filter as any)[op]);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '$exists': {
|
||||
const exists = (filter as any)[op];
|
||||
assert(typeof exists === 'boolean');
|
||||
fns.push(async (row) => {
|
||||
if (exists) {
|
||||
return [null, undefined].includes(row[attr]);
|
||||
}
|
||||
else {
|
||||
return ![null, undefined].includes(row[attr]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '$in': {
|
||||
const inData = (filter as any)[op];
|
||||
assert(typeof inData === 'object');
|
||||
if (inData instanceof Array) {
|
||||
fns.push(async (row) => inData.includes(row[attr]));
|
||||
}
|
||||
else {
|
||||
// 这里只有当子查询中的filter不包含引用外部的子查询时才可以提前计算,否则必须等到执行时再计算
|
||||
try {
|
||||
const legalSets = (await this.selectAbjointRow(inData.entity, inData, context, params)).map(
|
||||
(ele) => {
|
||||
const { data } = inData as DeduceSelection<E, ED, E, SH>;
|
||||
const key = Object.keys(data)[0];
|
||||
return (ele as any)[key];
|
||||
}
|
||||
);
|
||||
|
||||
fns.push(
|
||||
async (row) => legalSets.includes(row[attr])
|
||||
);
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof OakError && err.$$code === RowStore.$$CODES.expressionUnresolved[0]) {
|
||||
fns.push(
|
||||
async (row, nodeDict) => {
|
||||
assign(params, {
|
||||
nodeDict,
|
||||
});
|
||||
const legalSets = (await this.selectAbjointRow(inData.entity, inData, context, params)).map(
|
||||
(ele) => {
|
||||
const { data } = inData as DeduceSelection<E, ED, E, SH>;
|
||||
const key = Object.keys(data)[0];
|
||||
return (ele as any)[key];
|
||||
}
|
||||
);
|
||||
unset(params, 'nodeDict');
|
||||
return legalSets.includes(row[attr]);
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '$nin': {
|
||||
const inData = (filter as any)[op];
|
||||
assert(typeof inData === 'object');
|
||||
if (inData instanceof Array) {
|
||||
fns.push(async (row) => !inData.includes(row[attr]));
|
||||
}
|
||||
else {
|
||||
// 这里只有当子查询中的filter不包含引用外部的子查询时才可以提前计算,否则必须等到执行时再计算
|
||||
try {
|
||||
const legalSets = (await this.selectAbjointRow(inData.entity, inData, context, params)).map(
|
||||
(ele) => {
|
||||
const { data } = inData as DeduceSelection<E, ED, E, SH>;
|
||||
const key = Object.keys(data)[0];
|
||||
return (ele as any)[key];
|
||||
}
|
||||
);
|
||||
|
||||
fns.push(
|
||||
async (row) => !legalSets.includes(row[attr])
|
||||
);
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof OakError && err.$$code === RowStore.$$CODES.expressionUnresolved[0]) {
|
||||
fns.push(
|
||||
async (row, nodeDict) => {
|
||||
assign(params, {
|
||||
nodeDict,
|
||||
});
|
||||
const legalSets = (await this.selectAbjointRow(inData.entity, inData, context, params)).map(
|
||||
(ele) => {
|
||||
const { data } = inData as DeduceSelection<E, ED, E, SH>;
|
||||
const key = Object.keys(data)[0];
|
||||
return (ele as any)[key];
|
||||
}
|
||||
);
|
||||
unset(params, 'nodeDict');
|
||||
return !legalSets.includes(row[attr]);
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return async (node, nodeDict, exprResolveFns) => {
|
||||
const row = this.constructRow(node, context);
|
||||
if (!row) {
|
||||
return false;
|
||||
}
|
||||
for (const fn of fns) {
|
||||
if (await fn(row, nodeDict, exprResolveFns) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private async translateFilter<T extends E>(
|
||||
entity: T,
|
||||
filter: DeduceFilter<E, ED, T, SH>,
|
||||
context: Context<E, ED, SH>,
|
||||
params: Object): Promise<(node: RowNode<SH>, nodeDict: NodeDict<SH>, exprResolveFns: Array<ExprResolveFn<SH>>) => Promise<boolean>> {
|
||||
const fns: Array<(node: RowNode<SH>, nodeDict: NodeDict<SH>, exprResolveFns: Array<ExprResolveFn<SH>>) => Promise<boolean>> = [];
|
||||
|
||||
let nodeId: NodeId;
|
||||
for (const attr in filter) {
|
||||
if (attr === '#id') {
|
||||
nodeId = (filter as {
|
||||
['#id']: NodeId;
|
||||
})['#id'];
|
||||
}
|
||||
else if (['$and', '$or', '$xor', '$not'].includes(attr)) {
|
||||
fns.push(this.translateLogicFilter(entity, filter, attr, context, params));
|
||||
}
|
||||
else if (attr.toLowerCase().startsWith(EXPRESSION_PREFIX)) {
|
||||
const fn = this.translateExpression(entity, (filter as any)[attr], context);
|
||||
fns.push(
|
||||
async (node, nodeDict, exprResolveFns) => {
|
||||
const row = this.constructRow(node, context);
|
||||
if (!row) {
|
||||
return false;
|
||||
}
|
||||
const result = await fn(row, nodeDict);
|
||||
if (typeof result === 'function') {
|
||||
exprResolveFns.push(result);
|
||||
}
|
||||
return !!result;
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (attr.toLowerCase() === '$text') {
|
||||
fns.push(this.translateFulltext(entity, (filter as any)[attr], context));
|
||||
}
|
||||
else {
|
||||
// 属性级过滤
|
||||
const relation = judgeRelation(this.storageSchema, entity, attr);
|
||||
|
||||
if (relation === 1) {
|
||||
// 行本身的属性
|
||||
fns.push(await this.translateAttribute((filter as any)[attr], attr, context, params));
|
||||
}
|
||||
else if (relation === 2) {
|
||||
// 基于entity/entityId的指针
|
||||
const fn = await this.translateFilter(attr as E, (filter as any)[attr], context, params);
|
||||
fns.push(
|
||||
async (node, nodeDict, exprResolveFns) => {
|
||||
const row = this.constructRow(node, context);
|
||||
if ((row as any).entity !== attr) {
|
||||
return false;
|
||||
}
|
||||
const node2 = get(this.store, `${attr}.${(row as any).entityId}`);
|
||||
assert(node2);
|
||||
return fn(node2, nodeDict, exprResolveFns);
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
assert(typeof relation === 'string');
|
||||
// 只能是基于普通属性的外键
|
||||
const fn = await this.translateFilter(relation as E, (filter as any)[attr], context, params);
|
||||
fns.push(
|
||||
async (node, nodeDict, exprResolveFns) => {
|
||||
const row = this.constructRow(node, context);
|
||||
const node2 = get(this.store, `${relation}.${(row as any)[`${attr}Id`]}`);
|
||||
assert(node2);
|
||||
return fn(node2, nodeDict, exprResolveFns);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return async (node, nodeDict, exprResolveFns) => {
|
||||
if (nodeId) {
|
||||
assert(!nodeDict.hasOwnProperty(nodeId), new OakError(RowStore.$$LEVEL, RowStore.$$CODES.nodeIdRepeated, `Filter中的nodeId「${nodeId}」出现了多次`));
|
||||
assign(nodeDict, {
|
||||
[nodeId]: this.constructRow(node, context),
|
||||
});
|
||||
}
|
||||
const row = this.constructRow(node, context);
|
||||
if (!row) {
|
||||
return false;
|
||||
}
|
||||
for (const fn of fns) {
|
||||
if (!await fn(node, nodeDict, exprResolveFns)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
private translateSorter<T extends E>(entity: T, sorter: DeduceSorter<E, ED, T, SH>, context: Context<E, ED, SH>):
|
||||
(row1: Partial<SH>, row2: Partial<SH>) => number {
|
||||
const compare = (row1: Partial<SH> | null | undefined, row2: Partial<SH> | null | undefined, entity2: E, sortAttr: DeduceSorterAttr<SH>, direction?: 'asc' | 'desc'): number => {
|
||||
const row11 = row1 as any;
|
||||
const row22 = row2 as any;
|
||||
assert(Object.keys(sortAttr).length === 1);
|
||||
const attr = Object.keys(sortAttr)[0];
|
||||
|
||||
const relation = judgeRelation(this.storageSchema, entity2, attr);
|
||||
if (relation === 1) {
|
||||
const v1 = row1 && row11[attr];
|
||||
const v2 = row2 && row22[attr];
|
||||
if ([null, undefined].includes(v1) || [null, undefined].includes(v2)) {
|
||||
if ([null, undefined].includes(v1) && [null, undefined].includes(v2)) {
|
||||
return 0;
|
||||
}
|
||||
if ([null, undefined].includes(v1)) {
|
||||
if (direction === 'asc') {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (direction === 'desc') {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (v1 > v2) {
|
||||
if (direction === 'desc') {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (v1 < v2) {
|
||||
if (direction === 'desc') {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (relation === 2) {
|
||||
assert(row11['entity'] === row22['entity']);
|
||||
assert(row11.entity === attr);
|
||||
const node1 = get(this.store, `${row11.entity}.${row11.entityId}`);
|
||||
const node2 = get(this.store, `${row22.entity}.${row22.entityId}`);;
|
||||
const row111 = this.constructRow(node1, context);
|
||||
const row222 = this.constructRow(node2, context);
|
||||
|
||||
return compare(row111, row222, row11['entity'], (sortAttr as any)[attr], direction);
|
||||
}
|
||||
else {
|
||||
assert(typeof relation === 'string');
|
||||
const node1 = get(this.store, `${relation}.${row11[`${attr}Id`]}`);
|
||||
const node2 = get(this.store, `${relation}.${row22[`${attr}Id`]}`);
|
||||
const row111 = this.constructRow(node1, context);
|
||||
const row222 = this.constructRow(node2, context);
|
||||
|
||||
return compare(row111, row222, relation as E, (sortAttr as any)[attr], direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (row1, row2) => {
|
||||
for (const sorterElement of sorter) {
|
||||
const { $attr, $direction } = sorterElement;
|
||||
const result = compare(row1, row2, entity, $attr, $direction);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected async selectAbjointRow<T extends E>(
|
||||
entity: T,
|
||||
selection: Omit<ED[T]['Selection'], 'indexFrom' | 'count' | 'data' | 'sorter'>,
|
||||
context: Context<E, ED, SH>,
|
||||
params: Object = {}): Promise<SelectionResult<E, ED, T, SH>> {
|
||||
const { filter } = selection;
|
||||
const { nodeDict } = params as {
|
||||
nodeDict: NodeDict<SH>;
|
||||
};
|
||||
|
||||
const filterFn = filter && this.translateFilter(entity, filter, context, params);
|
||||
const entityNodes = this.store[entity] ? Object.values(this.store[entity]!) : [];
|
||||
const nodes = [];
|
||||
for (const n of entityNodes) {
|
||||
const nodeDict2: NodeDict<SH> = {};
|
||||
if (nodeDict) {
|
||||
assign(nodeDict2, nodeDict);
|
||||
}
|
||||
const exprResolveFns: Array<ExprResolveFn<SH>> = [];
|
||||
if (!filterFn || await (await filterFn)(n, nodeDict2, exprResolveFns)) {
|
||||
// 如果有延时处理的expression,在这里加以判断,此时所有在filter中的node应该都已经加以遍历了
|
||||
let exprResult = true;
|
||||
if (exprResolveFns.length > 0) {
|
||||
for (const fn of exprResolveFns) {
|
||||
const result = fn(nodeDict2);
|
||||
if (typeof result === 'function') {
|
||||
throw new OakError(RowStore.$$LEVEL, RowStore.$$CODES.expressionUnresolved, `表达式计算失败,请检查Filter中的结点编号和引用是否一致`);
|
||||
}
|
||||
if (!!!result) {
|
||||
exprResult = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exprResult) {
|
||||
nodes.push(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
const rows = nodes.map(
|
||||
(node) => this.constructRow(node, context) as SH
|
||||
);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
protected async updateAbjointRow<T extends E>(
|
||||
entity: T,
|
||||
operation: DeduceCreateSingleOperation<E, ED, T, SH> | DeduceUpdateOperation<E, ED, T, SH> | DeduceRemoveOperation<E, ED, T, SH>,
|
||||
context: Context<E, ED, SH>,
|
||||
params: Object = {}): Promise<void> {
|
||||
const { data, action } = operation;
|
||||
|
||||
switch (action) {
|
||||
case 'create': {
|
||||
const { id } = data as DeduceCreateSingleOperation<E, ED, T, SH>['data'];
|
||||
const row = get(this.store, `${entity}.${id!}`);
|
||||
if (row) {
|
||||
throw new OakError(RowStore.$$LEVEL, RowStore.$$CODES.primaryKeyConfilict);
|
||||
}
|
||||
const node: RowNode<SH> = {
|
||||
$uuid: context.uuid!,
|
||||
$current: null,
|
||||
$next: data as Partial<SH>,
|
||||
$path: `${entity}.${id!}`,
|
||||
};
|
||||
set(this.store, `${entity}.${id!}`, node);
|
||||
this.addToTxnNode(node, context, 'create');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const selection = assign({}, operation, {
|
||||
data: {
|
||||
id: 1,
|
||||
},
|
||||
action: 'select',
|
||||
}) as ED[T]['Selection'];
|
||||
const rows = await this.selectAbjointRow(entity, selection, context, params);
|
||||
|
||||
const ids = rows.map(ele => ele.id);
|
||||
ids.forEach(
|
||||
(id) => {
|
||||
const node = (this.store[entity]!)[id as string];
|
||||
assert(node && !node.$uuid && node.$next === undefined);
|
||||
node.$uuid = context.uuid!;
|
||||
if (action === 'remove') {
|
||||
node.$next = null;
|
||||
node.$path = `${entity}.${id!}`,
|
||||
this.addToTxnNode(node, context, 'remove');
|
||||
}
|
||||
else {
|
||||
node.$next = data as Partial<SH>;
|
||||
this.addToTxnNode(node, context, 'update');
|
||||
}
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async doOperation<T extends E>(entity: T, operation: ED[T]['Operation'], context: Context<E, ED, SH>, params?: Object): Promise<void> {
|
||||
const { action } = operation;
|
||||
if (action === 'select') {
|
||||
// return this.cascadeSelect(entity, operation as any, context, params);
|
||||
assert(false);
|
||||
}
|
||||
else {
|
||||
return this.cascadeUpdate(entity, operation as any, context, params);
|
||||
}
|
||||
}
|
||||
|
||||
async operate<T extends E>(entity: T, operation: ED[T]['Operation'], context: Context<E, ED, SH>, params?: Object): Promise<void> {
|
||||
let autoCommit = false;
|
||||
if (!context.uuid) {
|
||||
autoCommit = true;
|
||||
await context.begin();
|
||||
}
|
||||
try {
|
||||
await this.doOperation(entity, operation, context, params);
|
||||
} catch (err) {
|
||||
if (autoCommit) {
|
||||
await context.rollback();
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
if (autoCommit) {
|
||||
await context.commit();
|
||||
}
|
||||
}
|
||||
|
||||
protected async formProjection<T extends E>(
|
||||
entity: T,
|
||||
row: ED[T]['Schema'],
|
||||
data: DeduceSelection<E, ED, T, SH>['data'],
|
||||
result: Partial<ED[T]['Schema']>,
|
||||
nodeDict: NodeDict<SH>,
|
||||
context: Context<E, ED, SH>) {
|
||||
const row2 = row as any;
|
||||
const data2 = data as any;
|
||||
|
||||
const laterExprDict: {
|
||||
[A in ExpressionKey]?: ExprLaterCheckFn;
|
||||
} = {};
|
||||
for (const attr in data) {
|
||||
if (attr.startsWith(EXPRESSION_PREFIX)) {
|
||||
const ExprNodeTranslator = this.translateExpression(entity, data2[attr], context);
|
||||
const exprResult = await ExprNodeTranslator(row, nodeDict);
|
||||
if (typeof exprResult === 'function') {
|
||||
assign(laterExprDict, {
|
||||
[attr]: exprResult,
|
||||
});
|
||||
}
|
||||
else {
|
||||
assign(result, {
|
||||
[attr]: exprResult,
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (attr === '#id') {
|
||||
const nodeId = data[attr] as NodeId;
|
||||
assert(!nodeDict.hasOwnProperty(nodeId), new OakError(RowStore.$$LEVEL, RowStore.$$CODES.nodeIdRepeated, `Filter中的nodeId「${nodeId}」出现了多次`));
|
||||
assign(nodeDict, {
|
||||
[nodeId]: row,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const attr in data) {
|
||||
if (!attr.startsWith(EXPRESSION_PREFIX) && attr !== '#id') {
|
||||
const relation = judgeRelation(this.storageSchema, entity, attr);
|
||||
if (relation === 1) {
|
||||
assign(result, {
|
||||
[attr]: row2[attr],
|
||||
});
|
||||
}
|
||||
else if (relation === 2) {
|
||||
const result2 = {};
|
||||
await this.formProjection(attr as E, row2[attr], data2[attr], result2, nodeDict, context);
|
||||
assign(result, {
|
||||
[attr]: result2,
|
||||
entity: row2.entity,
|
||||
entityId: row2.entityId,
|
||||
});
|
||||
}
|
||||
else if (typeof relation === 'string') {
|
||||
const result2 = {};
|
||||
await this.formProjection(relation as E, row2[attr], data2[attr], result2, nodeDict, context);
|
||||
assign(result, {
|
||||
[attr]: result2,
|
||||
[`${attr}Id`]: row2[`${attr}Id`],
|
||||
});
|
||||
}
|
||||
else {
|
||||
assert(relation instanceof Array);
|
||||
assert(row2[attr] instanceof Array);
|
||||
const result2 = await this.formResult(relation[0] as E, row2[attr], data2[attr], context, nodeDict);
|
||||
|
||||
assign(result, {
|
||||
[attr]: result2,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const attr in laterExprDict) {
|
||||
const exprResult = laterExprDict[attr as ExpressionKey]!(nodeDict);
|
||||
// projection是不应出现计算不出来的情况
|
||||
assert(typeof exprResult !== 'function', new OakError(RowStore.$$LEVEL, RowStore.$$CODES.expressionUnresolved, 'data中的expr无法计算,请检查命名与引用的一致性'));
|
||||
assign(result, {
|
||||
[attr]: exprResult,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async formResult<T extends E>(
|
||||
entity: T,
|
||||
rows: Array<Partial<ED[T]['Schema']>>,
|
||||
selection: Omit<ED[T]['Selection'], 'filter'>,
|
||||
context: Context<E, ED, SH>,
|
||||
nodeDict?: NodeDict<SH>) {
|
||||
const { data, sorter, indexFrom, count } = selection;
|
||||
// 先计算projection
|
||||
const rows2 = await Promise.all(
|
||||
rows.map(
|
||||
async (row) => {
|
||||
const result: Partial<ED[T]['Schema']> = {};
|
||||
const nodeDict2: NodeDict<SH> = {};
|
||||
if (nodeDict) {
|
||||
assign(nodeDict2, nodeDict);
|
||||
}
|
||||
await this.formProjection(entity, row as ED[T]['Schema'], data, result, nodeDict2, context);
|
||||
return result;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return rows2;
|
||||
}
|
||||
|
||||
async select<T extends E>(entity: T, selection: ED[T]['Selection'], context: Context<E, ED, SH>, params?: Object): Promise<SelectionResult<E, ED, T, SH>> {
|
||||
const rows = await this.cascadeSelect(entity, selection, context, params);
|
||||
|
||||
return await this.formResult(entity, rows, selection, context);
|
||||
}
|
||||
|
||||
async count<T extends E>(entity: T, selection: Omit<ED[T]['Selection'], "action" | "data" | "sorter">, context: Context<E, ED, SH>, params?: Object): Promise<number> {
|
||||
const rows = await this.cascadeSelect(entity, assign({}, selection, {
|
||||
data: {
|
||||
id: 1,
|
||||
}
|
||||
}) as any, context, params);
|
||||
|
||||
return rows.length;
|
||||
}
|
||||
|
||||
private addToTxnNode(node: RowNode<SH>, context: Context<E, ED, SH>, action: 'create' | 'update' | 'remove') {
|
||||
const txnNode = this.activeTxnDict[context.uuid!];
|
||||
assert(txnNode);
|
||||
assert(!node.$nextNode);
|
||||
if (txnNode.nodeHeader) {
|
||||
node.$nextNode = txnNode.nodeHeader;
|
||||
txnNode.nodeHeader = node;
|
||||
}
|
||||
else {
|
||||
txnNode.nodeHeader = node;
|
||||
}
|
||||
txnNode[action]++;
|
||||
}
|
||||
|
||||
begin(uuid: string) {
|
||||
assert(!this.activeTxnDict.hasOwnProperty(uuid));
|
||||
assign(this.activeTxnDict, {
|
||||
[uuid]: {
|
||||
create: 0,
|
||||
update: 0,
|
||||
remove: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
commit(uuid: string) {
|
||||
assert(this.activeTxnDict.hasOwnProperty(uuid));
|
||||
let node = this.activeTxnDict[uuid].nodeHeader;
|
||||
while (node) {
|
||||
const node2 = node.$nextNode;
|
||||
assert(node.$uuid === uuid);
|
||||
if (node.$next) {
|
||||
// create/update
|
||||
node.$current = assign(node.$current, node.$next);
|
||||
unset(node, '$uuid');
|
||||
unset(node, '$next');
|
||||
unset(node, '$path');
|
||||
unset(node, '$nextNode');
|
||||
}
|
||||
else {
|
||||
// remove
|
||||
assert(node.$path);
|
||||
unset(this.store, node.$path);
|
||||
}
|
||||
node = node2;
|
||||
}
|
||||
unset(this.activeTxnDict, uuid);
|
||||
}
|
||||
|
||||
rollback(uuid: string) {
|
||||
assert(this.activeTxnDict.hasOwnProperty(uuid));
|
||||
let node = this.activeTxnDict[uuid].nodeHeader;
|
||||
while (node) {
|
||||
const node2 = node.$nextNode;
|
||||
assert(node.$uuid === uuid);
|
||||
if (node.$current) {
|
||||
// update/remove
|
||||
unset(node, '$uuid');
|
||||
unset(node, '$next');
|
||||
unset(node, '$path');
|
||||
unset(node, '$nextNode');
|
||||
}
|
||||
else {
|
||||
// create
|
||||
assert(node.$path);
|
||||
unset(this.store, node.$path);
|
||||
}
|
||||
node = node2;
|
||||
}
|
||||
unset(this.activeTxnDict, uuid);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { NodeId } from "oak-domain/lib/types/Demand";
|
||||
import { EntityShape, EntityDef } from "oak-domain/src/types/Entity";
|
||||
|
||||
export type RowNode<SH extends EntityShape = EntityShape> = {
|
||||
$uuid?: string; // 当前加锁的事务号
|
||||
$next?: Partial<SH> | null; // 更新的后项,如果是删除则为null
|
||||
$current?: SH | null; // 当前数据,如果是插入则为null
|
||||
$nextNode?: RowNode<SH>; // 当前事务的下一结点(提交回滚时遍历)
|
||||
$path?: string; // 结点在树上的路径,为了create的回滚所用
|
||||
};
|
||||
|
||||
export type NodeDict<SH extends EntityShape = EntityShape> = {
|
||||
[K in NodeId]: SH;
|
||||
};
|
||||
|
||||
export type ExprResolveFn<SH extends EntityShape = EntityShape> = (nodeDict: NodeDict<SH>) => ExprResolveFn<SH> | any;
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
import { String, Int, Float, Double, Boolean, Text, Datetime, File, Image } from "oak-domain/src/types/DataType";
|
||||
import { Q_DateValue, Q_BooleanValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, FulltextFilter, ExprOp, ExpressionKey } from "oak-domain/src/types/Demand";
|
||||
import { OneOf, ValueOf } from "oak-domain/src/types/Polyfill";
|
||||
import * as SubQuery from "../_SubQuery";
|
||||
import { Operation as OakOperation } from "oak-domain/src/types/Entity";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
import * as System from "../System/Schema";
|
||||
import * as WechatUser from "../WechatUser/Schema";
|
||||
import * as ExtraFile from "../ExtraFile/Schema";
|
||||
export type OpSchema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
name: String<32>;
|
||||
description: Text;
|
||||
type: 'web' | 'wechatPublic' | 'weChatMp';
|
||||
systemId: String<64>;
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
export type Schema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
name: String<32>;
|
||||
description: Text;
|
||||
type: 'web' | 'wechatPublic' | 'weChatMp';
|
||||
systemId: String<64>;
|
||||
system: System.Schema;
|
||||
wechatUser$application?: Array<WechatUser.Schema>;
|
||||
extraFile$entity?: Array<ExtraFile.Schema>;
|
||||
} & {
|
||||
[A in ExpressionKey]?: any;
|
||||
};
|
||||
type AttrFilter = {
|
||||
id: Q_StringValue | SubQuery.ApplicationIdSubQuery;
|
||||
$$createAt$$: Q_DateValue;
|
||||
$$updateAt$$: Q_DateValue;
|
||||
name: Q_StringValue;
|
||||
description: Q_StringValue;
|
||||
type: Q_EnumValue<'web' | 'wechatPublic' | 'weChatMp'>;
|
||||
systemId: Q_StringValue | SubQuery.SystemIdSubQuery;
|
||||
system: System.Filter;
|
||||
};
|
||||
export type Filter = MakeFilter<AttrFilter & ExprOp<OpAttr>>;
|
||||
export type Projection = {
|
||||
"#id"?: NodeId;
|
||||
id: 1;
|
||||
$$createAt$$?: 1;
|
||||
$$updateAt$$?: 1;
|
||||
name?: 1;
|
||||
description?: 1;
|
||||
type?: 1;
|
||||
systemId?: 1;
|
||||
system?: System.Projection;
|
||||
wechatUser$application?: WechatUser.Selection;
|
||||
extraFile$entity?: ExtraFile.Selection;
|
||||
} & ExprOp<OpAttr>;
|
||||
export type ExportProjection = {
|
||||
"#id"?: NodeId;
|
||||
id?: string;
|
||||
$$createAt$$?: string;
|
||||
$$updateAt$$?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
type?: string;
|
||||
systemId?: string;
|
||||
system?: System.ExportProjection;
|
||||
wechatUser$application?: WechatUser.Exportation;
|
||||
extraFile$entity?: ExtraFile.Exportation;
|
||||
} & ExprOp<OpAttr>;
|
||||
type ApplicationIdProjection = OneOf<{
|
||||
id: 1;
|
||||
}>;
|
||||
type SystemIdProjection = OneOf<{
|
||||
systemId: 1;
|
||||
}>;
|
||||
export type SortAttr = OneOf<{
|
||||
id: 1;
|
||||
$$createAt$$: 1;
|
||||
$$updateAt$$: 1;
|
||||
name: 1;
|
||||
description: 1;
|
||||
type: 1;
|
||||
systemId: 1;
|
||||
system: System.SortAttr;
|
||||
} & ExprOp<OpAttr>>;
|
||||
export type SortNode = {
|
||||
$attr: SortAttr;
|
||||
$direction?: "asc" | "desc";
|
||||
};
|
||||
export type Sorter = SortNode[];
|
||||
export type SelectOperation<P = Projection> = OakOperation<"select", P, Filter, Sorter>;
|
||||
export type Selection<P = Projection> = Omit<SelectOperation<P>, "action">;
|
||||
export type Exportation = OakOperation<"export", ExportProjection, Filter, Sorter>;
|
||||
type CreateOperationData = Omit<OpSchema, "systemId"> & ({
|
||||
system?: System.CreateSingleOperation | (System.UpdateOperation & {
|
||||
id: String<64>;
|
||||
});
|
||||
systemId?: undefined;
|
||||
} | {
|
||||
system?: undefined;
|
||||
systemId?: String<64>;
|
||||
}) & {
|
||||
wechatUser$application?: WechatUser.CreateOperation | WechatUser.UpdateOperation;
|
||||
extraFile$entity?: ExtraFile.CreateOperation | ExtraFile.UpdateOperation;
|
||||
};
|
||||
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
|
||||
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
|
||||
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
|
||||
type UpdateOperationData = Partial<Omit<OpSchema, "id" | "systemId">> & ({
|
||||
system?: System.CreateSingleOperation | Omit<System.UpdateOperation, "id" | "ids" | "filter">;
|
||||
systemId?: undefined;
|
||||
} | {
|
||||
system?: undefined;
|
||||
systemId?: String<64>;
|
||||
}) & {
|
||||
wechatUsers$application?: WechatUser.CreateOperation | Omit<WechatUser.UpdateOperation, "id" | "ids" | "filter">;
|
||||
extraFiles$entity?: ExtraFile.CreateOperation | Omit<ExtraFile.UpdateOperation, "id" | "ids" | "filter">;
|
||||
};
|
||||
export type UpdateOperation = OakOperation<"update", UpdateOperationData, Filter>;
|
||||
type RemoveOperationData = {} & {
|
||||
system?: Omit<System.UpdateOperation | System.RemoveOperation, "id" | "ids" | "filter">;
|
||||
} & {
|
||||
wechatUsers$application?: Omit<WechatUser.UpdateOperation | WechatUser.RemoveOperation, "id" | "ids" | "filter">;
|
||||
extraFiles$entity?: Omit<ExtraFile.UpdateOperation | ExtraFile.RemoveOperation, "id" | "ids" | "filter">;
|
||||
};
|
||||
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter>;
|
||||
export type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation;
|
||||
export type SystemIdSubQuery = Selection<SystemIdProjection>;
|
||||
export type ApplicationIdSubQuery = Selection<ApplicationIdProjection>;
|
||||
export type NativeAttr = OpAttr | `system.${System.NativeAttr}`;
|
||||
export type FullAttr = NativeAttr | `wechatUsers$${number}.${WechatUser.NativeAttr}` | `extraFiles$${number}.${ExtraFile.NativeAttr}`;
|
||||
export type EntityDef = {
|
||||
Schema: Schema;
|
||||
OpSchema: OpSchema;
|
||||
Action: GenericAction;
|
||||
Selection: Selection;
|
||||
Operation: Operation;
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { StorageDesc } from "oak-domain/src/types/Storage";
|
||||
export const desc: StorageDesc = {
|
||||
attributes: {
|
||||
name: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 32
|
||||
}
|
||||
},
|
||||
description: {
|
||||
type: "text"
|
||||
},
|
||||
type: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 16
|
||||
}
|
||||
},
|
||||
system: {
|
||||
type: "ref",
|
||||
ref: "system"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { EntityDef as Application } from "./Application/Schema";
|
||||
import { EntityDef as ExtraFile } from "./ExtraFile/Schema";
|
||||
import { EntityDef as Mobile } from "./Mobile/Schema";
|
||||
import { EntityDef as UserSystem } from "./UserSystem/Schema";
|
||||
import { EntityDef as System } from "./System/Schema";
|
||||
import { EntityDef as Token } from "./Token/Schema";
|
||||
import { EntityDef as User } from "./User/Schema";
|
||||
import { EntityDef as WechatUser } from "./WechatUser/Schema";
|
||||
type ES = {
|
||||
application: Application;
|
||||
extraFile: ExtraFile;
|
||||
mobile: Mobile;
|
||||
userSystem: UserSystem;
|
||||
system: System;
|
||||
token: Token;
|
||||
user: User;
|
||||
wechatUser: WechatUser;
|
||||
};
|
||||
export default ES;
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
import { String, Int, Float, Double, Boolean, Text, Datetime, File, Image } from "oak-domain/src/types/DataType";
|
||||
import { Q_DateValue, Q_BooleanValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, FulltextFilter, ExprOp, ExpressionKey } from "oak-domain/src/types/Demand";
|
||||
import { OneOf, ValueOf } from "oak-domain/src/types/Polyfill";
|
||||
import * as SubQuery from "../_SubQuery";
|
||||
import { Operation as OakOperation } from "oak-domain/src/types/Entity";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
import * as Application from "../Application/Schema";
|
||||
import * as User from "../User/Schema";
|
||||
export type OpSchema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
origin: 'qiniu';
|
||||
type: 'image' | 'pdf' | 'video' | 'audio' | 'file';
|
||||
bucket: String<16>;
|
||||
objectId: String<64>;
|
||||
tag1: String<16>;
|
||||
tag2: String<16>;
|
||||
filename: String<64>;
|
||||
md5: Text;
|
||||
entity: "application" | "user";
|
||||
entityId: String<64>;
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
export type Schema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
origin: 'qiniu';
|
||||
type: 'image' | 'pdf' | 'video' | 'audio' | 'file';
|
||||
bucket: String<16>;
|
||||
objectId: String<64>;
|
||||
tag1: String<16>;
|
||||
tag2: String<16>;
|
||||
filename: String<64>;
|
||||
md5: Text;
|
||||
entity: "application" | "user";
|
||||
entityId: String<64>;
|
||||
application?: Application.Schema;
|
||||
user?: User.Schema;
|
||||
} & {
|
||||
[A in ExpressionKey]?: any;
|
||||
};
|
||||
type AttrFilter<E = Q_EnumValue<"application" | "user">> = {
|
||||
id: Q_StringValue | SubQuery.ExtraFileIdSubQuery;
|
||||
$$createAt$$: Q_DateValue;
|
||||
$$updateAt$$: Q_DateValue;
|
||||
origin: Q_EnumValue<'qiniu'>;
|
||||
type: Q_EnumValue<'image' | 'pdf' | 'video' | 'audio' | 'file'>;
|
||||
bucket: Q_StringValue;
|
||||
objectId: Q_StringValue;
|
||||
tag1: Q_StringValue;
|
||||
tag2: Q_StringValue;
|
||||
filename: Q_StringValue;
|
||||
md5: Q_StringValue;
|
||||
entity: E;
|
||||
entityId: Q_StringValue;
|
||||
};
|
||||
export type Filter<E = Q_EnumValue<"application" | "user">> = MakeFilter<AttrFilter<E> & ExprOp<OpAttr>>;
|
||||
export type Projection = {
|
||||
"#id"?: NodeId;
|
||||
id: 1;
|
||||
$$createAt$$?: 1;
|
||||
$$updateAt$$?: 1;
|
||||
origin?: 1;
|
||||
type?: 1;
|
||||
bucket?: 1;
|
||||
objectId?: 1;
|
||||
tag1?: 1;
|
||||
tag2?: 1;
|
||||
filename?: 1;
|
||||
md5?: 1;
|
||||
entity?: 1;
|
||||
entityId?: 1;
|
||||
application?: Application.Projection;
|
||||
user?: User.Projection;
|
||||
} & ExprOp<OpAttr>;
|
||||
export type ExportProjection = {
|
||||
"#id"?: NodeId;
|
||||
id?: string;
|
||||
$$createAt$$?: string;
|
||||
$$updateAt$$?: string;
|
||||
origin?: string;
|
||||
type?: string;
|
||||
bucket?: string;
|
||||
objectId?: string;
|
||||
tag1?: string;
|
||||
tag2?: string;
|
||||
filename?: string;
|
||||
md5?: string;
|
||||
entity?: string;
|
||||
entityId?: string;
|
||||
application?: Application.ExportProjection;
|
||||
user?: User.ExportProjection;
|
||||
} & ExprOp<OpAttr>;
|
||||
type ExtraFileIdProjection = OneOf<{
|
||||
id: 1;
|
||||
}>;
|
||||
type ApplicationIdProjection = OneOf<{
|
||||
entityId: 1;
|
||||
}>;
|
||||
type UserIdProjection = OneOf<{
|
||||
entityId: 1;
|
||||
}>;
|
||||
export type SortAttr = OneOf<{
|
||||
id: 1;
|
||||
$$createAt$$: 1;
|
||||
$$updateAt$$: 1;
|
||||
origin: 1;
|
||||
type: 1;
|
||||
bucket: 1;
|
||||
objectId: 1;
|
||||
tag1: 1;
|
||||
tag2: 1;
|
||||
filename: 1;
|
||||
md5: 1;
|
||||
entity: 1;
|
||||
entityId: 1;
|
||||
application: Application.SortAttr;
|
||||
user: User.SortAttr;
|
||||
} & ExprOp<OpAttr>>;
|
||||
export type SortNode = {
|
||||
$attr: SortAttr;
|
||||
$direction?: "asc" | "desc";
|
||||
};
|
||||
export type Sorter = SortNode[];
|
||||
export type SelectOperation<P = Projection> = OakOperation<"select", P, Filter, Sorter>;
|
||||
export type Selection<P = Projection> = Omit<SelectOperation<P>, "action">;
|
||||
export type Exportation = OakOperation<"export", ExportProjection, Filter, Sorter>;
|
||||
type CreateOperationData = Omit<OpSchema, "entityId" | "entityId"> & ({
|
||||
entity: "application" | "user";
|
||||
entityId: String<64>;
|
||||
application?: undefined;
|
||||
user?: undefined;
|
||||
} | ({
|
||||
entity?: undefined;
|
||||
entityId?: undefined;
|
||||
} & OneOf<{
|
||||
application: Application.CreateSingleOperation | (Application.UpdateOperation & {
|
||||
id: String<64>;
|
||||
});
|
||||
user: User.CreateSingleOperation | (User.UpdateOperation & {
|
||||
id: String<64>;
|
||||
});
|
||||
}>));
|
||||
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
|
||||
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
|
||||
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
|
||||
type UpdateOperationData = Partial<Omit<OpSchema, "id" | "entityId" | "entityId">> & ({
|
||||
entity?: "application" | "user";
|
||||
entityId?: String<64>;
|
||||
application?: undefined;
|
||||
user?: undefined;
|
||||
} | ({
|
||||
entity?: undefined;
|
||||
entityId?: undefined;
|
||||
} & OneOf<{
|
||||
application: Application.CreateSingleOperation | Omit<Application.UpdateOperation, "id" | "ids" | "filter">;
|
||||
user: User.CreateSingleOperation | Omit<User.UpdateOperation, "id" | "ids" | "filter">;
|
||||
}>));
|
||||
export type UpdateOperation = OakOperation<"update", UpdateOperationData, Filter>;
|
||||
type RemoveOperationData = {} & OneOf<{
|
||||
application?: Omit<Application.UpdateOperation | Application.RemoveOperation, "id" | "ids" | "filter">;
|
||||
user?: Omit<User.UpdateOperation | User.RemoveOperation, "id" | "ids" | "filter">;
|
||||
}>;
|
||||
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter>;
|
||||
export type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation;
|
||||
export type ApplicationIdSubQuery = Selection<ApplicationIdProjection>;
|
||||
export type UserIdSubQuery = Selection<UserIdProjection>;
|
||||
export type ExtraFileIdSubQuery = Selection<ExtraFileIdProjection>;
|
||||
export type NativeAttr = OpAttr | `entity.${Application.NativeAttr}` | `entity.${User.NativeAttr}`;
|
||||
export type FullAttr = NativeAttr;
|
||||
export type EntityDef = {
|
||||
Schema: Schema;
|
||||
OpSchema: OpSchema;
|
||||
Action: GenericAction;
|
||||
Selection: Selection;
|
||||
Operation: Operation;
|
||||
};
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import { StorageDesc } from "oak-domain/src/types/Storage";
|
||||
export const desc: StorageDesc = {
|
||||
attributes: {
|
||||
origin: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 16
|
||||
}
|
||||
},
|
||||
type: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 16
|
||||
}
|
||||
},
|
||||
bucket: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 16
|
||||
}
|
||||
},
|
||||
objectId: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 64
|
||||
}
|
||||
},
|
||||
tag1: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 16
|
||||
}
|
||||
},
|
||||
tag2: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 16
|
||||
}
|
||||
},
|
||||
filename: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 64
|
||||
}
|
||||
},
|
||||
md5: {
|
||||
type: "text"
|
||||
},
|
||||
entity: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 32
|
||||
}
|
||||
},
|
||||
entityId: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 64
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
import { String, Int, Float, Double, Boolean, Text, Datetime, File, Image } from "oak-domain/src/types/DataType";
|
||||
import { Q_DateValue, Q_BooleanValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, FulltextFilter, ExprOp, ExpressionKey } from "oak-domain/src/types/Demand";
|
||||
import { OneOf, ValueOf } from "oak-domain/src/types/Polyfill";
|
||||
import * as SubQuery from "../_SubQuery";
|
||||
import { Operation as OakOperation } from "oak-domain/src/types/Entity";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
import * as User from "../User/Schema";
|
||||
import * as Token from "../Token/Schema";
|
||||
export type OpSchema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
mobile: String<16>;
|
||||
userId: String<64>;
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
export type Schema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
mobile: String<16>;
|
||||
userId: String<64>;
|
||||
user: User.Schema;
|
||||
token$entity?: Array<Token.Schema>;
|
||||
} & {
|
||||
[A in ExpressionKey]?: any;
|
||||
};
|
||||
type AttrFilter = {
|
||||
id: Q_StringValue | SubQuery.MobileIdSubQuery;
|
||||
$$createAt$$: Q_DateValue;
|
||||
$$updateAt$$: Q_DateValue;
|
||||
mobile: Q_StringValue;
|
||||
userId: Q_StringValue | SubQuery.UserIdSubQuery;
|
||||
user: User.Filter;
|
||||
};
|
||||
export type Filter = MakeFilter<AttrFilter & ExprOp<OpAttr>>;
|
||||
export type Projection = {
|
||||
"#id"?: NodeId;
|
||||
id: 1;
|
||||
$$createAt$$?: 1;
|
||||
$$updateAt$$?: 1;
|
||||
mobile?: 1;
|
||||
userId?: 1;
|
||||
user?: User.Projection;
|
||||
token$entity?: Token.Selection;
|
||||
} & ExprOp<OpAttr>;
|
||||
export type ExportProjection = {
|
||||
"#id"?: NodeId;
|
||||
id?: string;
|
||||
$$createAt$$?: string;
|
||||
$$updateAt$$?: string;
|
||||
mobile?: string;
|
||||
userId?: string;
|
||||
user?: User.ExportProjection;
|
||||
token$entity?: Token.Exportation;
|
||||
} & ExprOp<OpAttr>;
|
||||
type MobileIdProjection = OneOf<{
|
||||
id: 1;
|
||||
}>;
|
||||
type UserIdProjection = OneOf<{
|
||||
userId: 1;
|
||||
}>;
|
||||
export type SortAttr = OneOf<{
|
||||
id: 1;
|
||||
$$createAt$$: 1;
|
||||
$$updateAt$$: 1;
|
||||
mobile: 1;
|
||||
userId: 1;
|
||||
user: User.SortAttr;
|
||||
} & ExprOp<OpAttr>>;
|
||||
export type SortNode = {
|
||||
$attr: SortAttr;
|
||||
$direction?: "asc" | "desc";
|
||||
};
|
||||
export type Sorter = SortNode[];
|
||||
export type SelectOperation<P = Projection> = OakOperation<"select", P, Filter, Sorter>;
|
||||
export type Selection<P = Projection> = Omit<SelectOperation<P>, "action">;
|
||||
export type Exportation = OakOperation<"export", ExportProjection, Filter, Sorter>;
|
||||
type CreateOperationData = Omit<OpSchema, "userId"> & ({
|
||||
user?: User.CreateSingleOperation | (User.UpdateOperation & {
|
||||
id: String<64>;
|
||||
});
|
||||
userId?: undefined;
|
||||
} | {
|
||||
user?: undefined;
|
||||
userId?: String<64>;
|
||||
}) & {
|
||||
token$entity?: Token.CreateOperation | Token.UpdateOperation;
|
||||
};
|
||||
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
|
||||
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
|
||||
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
|
||||
type UpdateOperationData = Partial<Omit<OpSchema, "id" | "userId">> & ({
|
||||
user?: User.CreateSingleOperation | Omit<User.UpdateOperation, "id" | "ids" | "filter">;
|
||||
userId?: undefined;
|
||||
} | {
|
||||
user?: undefined;
|
||||
userId?: String<64>;
|
||||
}) & {
|
||||
tokens$entity?: Token.CreateOperation | Omit<Token.UpdateOperation, "id" | "ids" | "filter">;
|
||||
};
|
||||
export type UpdateOperation = OakOperation<"update", UpdateOperationData, Filter>;
|
||||
type RemoveOperationData = {} & {
|
||||
user?: Omit<User.UpdateOperation | User.RemoveOperation, "id" | "ids" | "filter">;
|
||||
} & {
|
||||
tokens$entity?: Omit<Token.UpdateOperation | Token.RemoveOperation, "id" | "ids" | "filter">;
|
||||
};
|
||||
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter>;
|
||||
export type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation;
|
||||
export type UserIdSubQuery = Selection<UserIdProjection>;
|
||||
export type MobileIdSubQuery = Selection<MobileIdProjection>;
|
||||
export type NativeAttr = OpAttr | `user.${User.NativeAttr}`;
|
||||
export type FullAttr = NativeAttr | `tokens$${number}.${Token.NativeAttr}`;
|
||||
export type EntityDef = {
|
||||
Schema: Schema;
|
||||
OpSchema: OpSchema;
|
||||
Action: GenericAction;
|
||||
Selection: Selection;
|
||||
Operation: Operation;
|
||||
};
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { StorageDesc } from "oak-domain/src/types/Storage";
|
||||
export const desc: StorageDesc = {
|
||||
attributes: {
|
||||
mobile: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 16
|
||||
}
|
||||
},
|
||||
user: {
|
||||
type: "ref",
|
||||
ref: "user"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { StorageSchema } from "oak-domain/src/types/Storage";
|
||||
import { desc as applicationDesc } from "./Application/Storage";
|
||||
import { desc as extraFileDesc } from "./ExtraFile/Storage";
|
||||
import { desc as mobileDesc } from "./Mobile/Storage";
|
||||
import { desc as userSystemDesc } from "./UserSystem/Storage";
|
||||
import { desc as systemDesc } from "./System/Storage";
|
||||
import { desc as tokenDesc } from "./Token/Storage";
|
||||
import { desc as userDesc } from "./User/Storage";
|
||||
import { desc as wechatUserDesc } from "./WechatUser/Storage";
|
||||
export const storageSchema: StorageSchema = {
|
||||
application: applicationDesc,
|
||||
extraFile: extraFileDesc,
|
||||
mobile: mobileDesc,
|
||||
userSystem: userSystemDesc,
|
||||
system: systemDesc,
|
||||
token: tokenDesc,
|
||||
user: userDesc,
|
||||
wechatUser: wechatUserDesc
|
||||
};
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
import { String, Int, Float, Double, Boolean, Text, Datetime, File, Image } from "oak-domain/src/types/DataType";
|
||||
import { Q_DateValue, Q_BooleanValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, FulltextFilter, ExprOp, ExpressionKey } from "oak-domain/src/types/Demand";
|
||||
import { OneOf, ValueOf } from "oak-domain/src/types/Polyfill";
|
||||
import * as SubQuery from "../_SubQuery";
|
||||
import { Operation as OakOperation } from "oak-domain/src/types/Entity";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
import * as Application from "../Application/Schema";
|
||||
import * as UserSystem from "../UserSystem/Schema";
|
||||
export type OpSchema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
name: String<32>;
|
||||
description: Text;
|
||||
config: Object;
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
export type Schema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
name: String<32>;
|
||||
description: Text;
|
||||
config: Object;
|
||||
application$system?: Array<Application.Schema>;
|
||||
userSystem$system?: Array<UserSystem.Schema>;
|
||||
} & {
|
||||
[A in ExpressionKey]?: any;
|
||||
};
|
||||
type AttrFilter = {
|
||||
id: Q_StringValue | SubQuery.SystemIdSubQuery;
|
||||
$$createAt$$: Q_DateValue;
|
||||
$$updateAt$$: Q_DateValue;
|
||||
name: Q_StringValue;
|
||||
description: Q_StringValue;
|
||||
};
|
||||
export type Filter = MakeFilter<AttrFilter & ExprOp<OpAttr>>;
|
||||
export type Projection = {
|
||||
"#id"?: NodeId;
|
||||
id: 1;
|
||||
$$createAt$$?: 1;
|
||||
$$updateAt$$?: 1;
|
||||
name?: 1;
|
||||
description?: 1;
|
||||
config?: 1;
|
||||
application$system?: Application.Selection;
|
||||
userSystem$system?: UserSystem.Selection;
|
||||
} & ExprOp<OpAttr>;
|
||||
export type ExportProjection = {
|
||||
"#id"?: NodeId;
|
||||
id?: string;
|
||||
$$createAt$$?: string;
|
||||
$$updateAt$$?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
config?: string;
|
||||
application$system?: Application.Exportation;
|
||||
userSystem$system?: UserSystem.Exportation;
|
||||
} & ExprOp<OpAttr>;
|
||||
type SystemIdProjection = OneOf<{
|
||||
id: 1;
|
||||
}>;
|
||||
export type SortAttr = OneOf<{
|
||||
id: 1;
|
||||
$$createAt$$: 1;
|
||||
$$updateAt$$: 1;
|
||||
name: 1;
|
||||
description: 1;
|
||||
} & ExprOp<OpAttr>>;
|
||||
export type SortNode = {
|
||||
$attr: SortAttr;
|
||||
$direction?: "asc" | "desc";
|
||||
};
|
||||
export type Sorter = SortNode[];
|
||||
export type SelectOperation<P = Projection> = OakOperation<"select", P, Filter, Sorter>;
|
||||
export type Selection<P = Projection> = Omit<SelectOperation<P>, "action">;
|
||||
export type Exportation = OakOperation<"export", ExportProjection, Filter, Sorter>;
|
||||
type CreateOperationData = OpSchema & {
|
||||
application$system?: Application.CreateOperation | Application.UpdateOperation;
|
||||
userSystem$system?: UserSystem.CreateOperation | UserSystem.UpdateOperation;
|
||||
};
|
||||
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
|
||||
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
|
||||
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
|
||||
type UpdateOperationData = Partial<Omit<OpSchema, "id">> & {
|
||||
applications$system?: Application.CreateOperation | Omit<Application.UpdateOperation, "id" | "ids" | "filter">;
|
||||
userSystems$system?: UserSystem.CreateOperation | Omit<UserSystem.UpdateOperation, "id" | "ids" | "filter">;
|
||||
};
|
||||
export type UpdateOperation = OakOperation<"update", UpdateOperationData, Filter>;
|
||||
type RemoveOperationData = {} & {
|
||||
applications$system?: Omit<Application.UpdateOperation | Application.RemoveOperation, "id" | "ids" | "filter">;
|
||||
userSystems$system?: Omit<UserSystem.UpdateOperation | UserSystem.RemoveOperation, "id" | "ids" | "filter">;
|
||||
};
|
||||
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter>;
|
||||
export type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation;
|
||||
export type SystemIdSubQuery = Selection<SystemIdProjection>;
|
||||
export type NativeAttr = OpAttr;
|
||||
export type FullAttr = NativeAttr | `applications$${number}.${Application.NativeAttr}` | `userSystems$${number}.${UserSystem.NativeAttr}`;
|
||||
export type EntityDef = {
|
||||
Schema: Schema;
|
||||
OpSchema: OpSchema;
|
||||
Action: GenericAction;
|
||||
Selection: Selection;
|
||||
Operation: Operation;
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { StorageDesc } from "oak-domain/src/types/Storage";
|
||||
export const desc: StorageDesc = {
|
||||
attributes: {
|
||||
name: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 32
|
||||
}
|
||||
},
|
||||
description: {
|
||||
type: "text"
|
||||
},
|
||||
config: {
|
||||
type: "object"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { AbleAction } from "oak-domain/lib/actions/action";
|
||||
import { ActionDef } from "oak-domain/src/types/Action";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
export type ParticularAction = AbleAction;
|
||||
export type Action = GenericAction | ParticularAction;
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
import { String, Int, Float, Double, Boolean, Text, Datetime, File, Image } from "oak-domain/src/types/DataType";
|
||||
import { Q_DateValue, Q_BooleanValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, FulltextFilter, ExprOp, ExpressionKey } from "oak-domain/src/types/Demand";
|
||||
import { OneOf, ValueOf } from "oak-domain/src/types/Polyfill";
|
||||
import * as SubQuery from "../_SubQuery";
|
||||
import { Operation as OakOperation } from "oak-domain/src/types/Entity";
|
||||
import { AbleState } from "oak-domain/lib/actions/action";
|
||||
import { ParticularAction, Action } from "./Action";
|
||||
import * as User from "../User/Schema";
|
||||
import * as Mobile from "../Mobile/Schema";
|
||||
export type OpSchema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
entity: "mobile";
|
||||
entityId: String<64>;
|
||||
userId?: String<64>;
|
||||
playerId?: String<64>;
|
||||
ableState?: AbleState;
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
export type Schema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
entity: "mobile";
|
||||
entityId: String<64>;
|
||||
userId?: String<64>;
|
||||
playerId?: String<64>;
|
||||
ableState?: AbleState;
|
||||
user?: User.Schema;
|
||||
player?: User.Schema;
|
||||
mobile?: Mobile.Schema;
|
||||
} & {
|
||||
[A in ExpressionKey]?: any;
|
||||
};
|
||||
type AttrFilter<E = Q_EnumValue<"mobile">> = {
|
||||
id: Q_StringValue | SubQuery.TokenIdSubQuery;
|
||||
$$createAt$$: Q_DateValue;
|
||||
$$updateAt$$: Q_DateValue;
|
||||
entity: E;
|
||||
entityId: Q_StringValue;
|
||||
userId: Q_StringValue | SubQuery.UserIdSubQuery;
|
||||
user: User.Filter;
|
||||
playerId: Q_StringValue | SubQuery.UserIdSubQuery;
|
||||
player: User.Filter;
|
||||
ableState: Q_EnumValue<AbleState>;
|
||||
};
|
||||
export type Filter<E = Q_EnumValue<"mobile">> = MakeFilter<AttrFilter<E> & ExprOp<OpAttr>>;
|
||||
export type Projection = {
|
||||
"#id"?: NodeId;
|
||||
id: 1;
|
||||
$$createAt$$?: 1;
|
||||
$$updateAt$$?: 1;
|
||||
entity?: 1;
|
||||
entityId?: 1;
|
||||
userId?: 1;
|
||||
user?: User.Projection;
|
||||
playerId?: 1;
|
||||
player?: User.Projection;
|
||||
ableState?: 1;
|
||||
mobile?: Mobile.Projection;
|
||||
} & ExprOp<OpAttr>;
|
||||
export type ExportProjection = {
|
||||
"#id"?: NodeId;
|
||||
id?: string;
|
||||
$$createAt$$?: string;
|
||||
$$updateAt$$?: string;
|
||||
entity?: string;
|
||||
entityId?: string;
|
||||
userId?: string;
|
||||
user?: User.ExportProjection;
|
||||
playerId?: string;
|
||||
player?: User.ExportProjection;
|
||||
ableState?: string;
|
||||
mobile?: Mobile.ExportProjection;
|
||||
} & ExprOp<OpAttr>;
|
||||
type TokenIdProjection = OneOf<{
|
||||
id: 1;
|
||||
}>;
|
||||
type UserIdProjection = OneOf<{
|
||||
userId: 1;
|
||||
playerId: 1;
|
||||
}>;
|
||||
type MobileIdProjection = OneOf<{
|
||||
entityId: 1;
|
||||
}>;
|
||||
export type SortAttr = OneOf<{
|
||||
id: 1;
|
||||
$$createAt$$: 1;
|
||||
$$updateAt$$: 1;
|
||||
entity: 1;
|
||||
entityId: 1;
|
||||
userId: 1;
|
||||
user: User.SortAttr;
|
||||
playerId: 1;
|
||||
player: User.SortAttr;
|
||||
ableState: 1;
|
||||
mobile: Mobile.SortAttr;
|
||||
} & ExprOp<OpAttr>>;
|
||||
export type SortNode = {
|
||||
$attr: SortAttr;
|
||||
$direction?: "asc" | "desc";
|
||||
};
|
||||
export type Sorter = SortNode[];
|
||||
export type SelectOperation<P = Projection> = OakOperation<"select", P, Filter, Sorter>;
|
||||
export type Selection<P = Projection> = Omit<SelectOperation<P>, "action">;
|
||||
export type Exportation = OakOperation<"export", ExportProjection, Filter, Sorter>;
|
||||
type CreateOperationData = Omit<OpSchema, "userId" | "playerId" | "entityId"> & ({
|
||||
entity: "mobile";
|
||||
entityId: String<64>;
|
||||
mobile?: undefined;
|
||||
} | ({
|
||||
entity?: undefined;
|
||||
entityId?: undefined;
|
||||
} & OneOf<{
|
||||
mobile: Mobile.CreateSingleOperation | (Mobile.UpdateOperation & {
|
||||
id: String<64>;
|
||||
});
|
||||
}>));
|
||||
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
|
||||
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
|
||||
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
|
||||
type UpdateOperationData = Partial<Omit<OpSchema, "id" | "userId" | "playerId" | "entityId">> & ({
|
||||
entity?: "mobile";
|
||||
entityId?: String<64>;
|
||||
mobile?: undefined;
|
||||
} | ({
|
||||
entity?: undefined;
|
||||
entityId?: undefined;
|
||||
} & OneOf<{
|
||||
mobile: Mobile.CreateSingleOperation | Omit<Mobile.UpdateOperation, "id" | "ids" | "filter">;
|
||||
}>));
|
||||
export type UpdateOperation = OakOperation<ParticularAction | "update", UpdateOperationData, Filter>;
|
||||
type RemoveOperationData = {} & OneOf<{
|
||||
mobile?: Omit<Mobile.UpdateOperation | Mobile.RemoveOperation, "id" | "ids" | "filter">;
|
||||
}>;
|
||||
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter>;
|
||||
export type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation;
|
||||
export type UserIdSubQuery = Selection<UserIdProjection>;
|
||||
export type MobileIdSubQuery = Selection<MobileIdProjection>;
|
||||
export type TokenIdSubQuery = Selection<TokenIdProjection>;
|
||||
export type NativeAttr = OpAttr | `user.${User.NativeAttr}` | `player.${User.NativeAttr}` | `entity.${Mobile.NativeAttr}`;
|
||||
export type FullAttr = NativeAttr;
|
||||
export type EntityDef = {
|
||||
Schema: Schema;
|
||||
OpSchema: OpSchema;
|
||||
Action: Action;
|
||||
Selection: Selection;
|
||||
Operation: Operation;
|
||||
ParticularAction: ParticularAction;
|
||||
};
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { StorageDesc } from "oak-domain/src/types/Storage";
|
||||
export const desc: StorageDesc = {
|
||||
attributes: {
|
||||
entity: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 32
|
||||
}
|
||||
},
|
||||
entityId: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 64
|
||||
}
|
||||
},
|
||||
user: {
|
||||
type: "ref",
|
||||
ref: "user"
|
||||
},
|
||||
player: {
|
||||
type: "ref",
|
||||
ref: "user"
|
||||
},
|
||||
ableState: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { ActionDef } from "oak-domain/src/types/Action";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
type IdAction = 'verify' | 'accept' | 'reject';
|
||||
export type IdState = 'unverified' | 'verified' | 'verifying';
|
||||
const IdActionDef: ActionDef<IdAction, IdState> = {
|
||||
stm: {
|
||||
verify: ['unverified', 'verifying'],
|
||||
accept: [['unverified', 'verifying'], 'verified'],
|
||||
reject: [['verifying', 'verified'], 'unverified']
|
||||
},
|
||||
is: 'unverified'
|
||||
};
|
||||
type UserAction = 'activate' | 'disable' | 'enable' | 'mergeTo' | 'mergeFrom';
|
||||
export type UserState = 'shadow' | 'normal' | 'disabled' | 'merged';
|
||||
const UserActionDef: ActionDef<UserAction, UserState> = {
|
||||
stm: {
|
||||
activate: ['shadow', 'normal'],
|
||||
disable: [['normal', 'shadow'], 'disabled'],
|
||||
enable: ['disabled', 'normal'],
|
||||
mergeTo: [['normal', 'shadow'], 'merged'],
|
||||
mergeFrom: ['normal', 'normal']
|
||||
}
|
||||
};
|
||||
export type ParticularAction = UserAction | IdAction;
|
||||
export type Action = GenericAction | ParticularAction;
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
import { String, Int, Float, Double, Boolean, Text, Datetime, File, Image } from "oak-domain/src/types/DataType";
|
||||
import { Q_DateValue, Q_BooleanValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, FulltextFilter, ExprOp, ExpressionKey } from "oak-domain/src/types/Demand";
|
||||
import { OneOf, ValueOf } from "oak-domain/src/types/Polyfill";
|
||||
import * as SubQuery from "../_SubQuery";
|
||||
import { Operation as OakOperation } from "oak-domain/src/types/Entity";
|
||||
import { UserState, IdState, ParticularAction, Action } from "./Action";
|
||||
import * as Mobile from "../Mobile/Schema";
|
||||
import * as UserSystem from "../UserSystem/Schema";
|
||||
import * as Token from "../Token/Schema";
|
||||
import * as WechatUser from "../WechatUser/Schema";
|
||||
import * as ExtraFile from "../ExtraFile/Schema";
|
||||
export type OpSchema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
name: String<16>;
|
||||
nickname?: String<64>;
|
||||
password?: Text;
|
||||
birth?: Datetime;
|
||||
gender?: 'male' | 'female';
|
||||
avatar?: Image;
|
||||
idCardType?: 'ID-Card' | 'passport' | 'Mainland-passport';
|
||||
idNumber?: String<32>;
|
||||
refId?: String<64>;
|
||||
userState?: UserState;
|
||||
idState?: IdState;
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
export type Schema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
name: String<16>;
|
||||
nickname?: String<64>;
|
||||
password?: Text;
|
||||
birth?: Datetime;
|
||||
gender?: 'male' | 'female';
|
||||
avatar?: Image;
|
||||
idCardType?: 'ID-Card' | 'passport' | 'Mainland-passport';
|
||||
idNumber?: String<32>;
|
||||
refId?: String<64>;
|
||||
userState?: UserState;
|
||||
idState?: IdState;
|
||||
ref?: Schema;
|
||||
mobile$user?: Array<Mobile.Schema>;
|
||||
userSystem$user?: Array<UserSystem.Schema>;
|
||||
token$user?: Array<Token.Schema>;
|
||||
token$player?: Array<Token.Schema>;
|
||||
user$ref?: Array<Schema>;
|
||||
wechatUser$user?: Array<WechatUser.Schema>;
|
||||
extraFile$entity?: Array<ExtraFile.Schema>;
|
||||
} & {
|
||||
[A in ExpressionKey]?: any;
|
||||
};
|
||||
type AttrFilter = {
|
||||
id: Q_StringValue | SubQuery.UserIdSubQuery;
|
||||
$$createAt$$: Q_DateValue;
|
||||
$$updateAt$$: Q_DateValue;
|
||||
name: Q_StringValue;
|
||||
nickname: Q_StringValue;
|
||||
password: Q_StringValue;
|
||||
birth: Q_DateValue;
|
||||
gender: Q_EnumValue<'male' | 'female'>;
|
||||
avatar: Q_StringValue;
|
||||
idCardType: Q_EnumValue<'ID-Card' | 'passport' | 'Mainland-passport'>;
|
||||
idNumber: Q_StringValue;
|
||||
refId: Q_StringValue | SubQuery.UserIdSubQuery;
|
||||
ref: Filter;
|
||||
userState: Q_EnumValue<UserState>;
|
||||
idState: Q_EnumValue<IdState>;
|
||||
};
|
||||
export type Filter = MakeFilter<AttrFilter & ExprOp<OpAttr> & FulltextFilter>;
|
||||
export type Projection = {
|
||||
"#id"?: NodeId;
|
||||
id: 1;
|
||||
$$createAt$$?: 1;
|
||||
$$updateAt$$?: 1;
|
||||
name?: 1;
|
||||
nickname?: 1;
|
||||
password?: 1;
|
||||
birth?: 1;
|
||||
gender?: 1;
|
||||
avatar?: 1;
|
||||
idCardType?: 1;
|
||||
idNumber?: 1;
|
||||
refId?: 1;
|
||||
ref?: Projection;
|
||||
userState?: 1;
|
||||
idState?: 1;
|
||||
mobile$user?: Mobile.Selection;
|
||||
userSystem$user?: UserSystem.Selection;
|
||||
token$user?: Token.Selection;
|
||||
token$player?: Token.Selection;
|
||||
user$ref?: Selection;
|
||||
wechatUser$user?: WechatUser.Selection;
|
||||
extraFile$entity?: ExtraFile.Selection;
|
||||
} & ExprOp<OpAttr>;
|
||||
export type ExportProjection = {
|
||||
"#id"?: NodeId;
|
||||
id?: string;
|
||||
$$createAt$$?: string;
|
||||
$$updateAt$$?: string;
|
||||
name?: string;
|
||||
nickname?: string;
|
||||
password?: string;
|
||||
birth?: string;
|
||||
gender?: string;
|
||||
avatar?: string;
|
||||
idCardType?: string;
|
||||
idNumber?: string;
|
||||
refId?: string;
|
||||
ref?: ExportProjection;
|
||||
userState?: string;
|
||||
idState?: string;
|
||||
mobile$user?: Mobile.Exportation;
|
||||
userSystem$user?: UserSystem.Exportation;
|
||||
token$user?: Token.Exportation;
|
||||
token$player?: Token.Exportation;
|
||||
user$ref?: Exportation;
|
||||
wechatUser$user?: WechatUser.Exportation;
|
||||
extraFile$entity?: ExtraFile.Exportation;
|
||||
} & ExprOp<OpAttr>;
|
||||
type UserIdProjection = OneOf<{
|
||||
id: 1;
|
||||
refId: 1;
|
||||
}>;
|
||||
export type SortAttr = OneOf<{
|
||||
id: 1;
|
||||
$$createAt$$: 1;
|
||||
$$updateAt$$: 1;
|
||||
name: 1;
|
||||
nickname: 1;
|
||||
password: 1;
|
||||
birth: 1;
|
||||
gender: 1;
|
||||
avatar: 1;
|
||||
idCardType: 1;
|
||||
idNumber: 1;
|
||||
refId: 1;
|
||||
ref: SortAttr;
|
||||
userState: 1;
|
||||
idState: 1;
|
||||
} & ExprOp<OpAttr>>;
|
||||
export type SortNode = {
|
||||
$attr: SortAttr;
|
||||
$direction?: "asc" | "desc";
|
||||
};
|
||||
export type Sorter = SortNode[];
|
||||
export type SelectOperation<P = Projection> = OakOperation<"select", P, Filter, Sorter>;
|
||||
export type Selection<P = Projection> = Omit<SelectOperation<P>, "action">;
|
||||
export type Exportation = OakOperation<"export", ExportProjection, Filter, Sorter>;
|
||||
type CreateOperationData = Omit<OpSchema, "refId"> & ({
|
||||
ref?: CreateSingleOperation | (UpdateOperation & {
|
||||
id: String<64>;
|
||||
});
|
||||
refId?: undefined;
|
||||
} | {
|
||||
ref?: undefined;
|
||||
refId?: String<64>;
|
||||
}) & {
|
||||
mobile$user?: Mobile.CreateOperation | Mobile.UpdateOperation;
|
||||
userSystem$user?: UserSystem.CreateOperation | UserSystem.UpdateOperation;
|
||||
token$user?: Token.CreateOperation | Token.UpdateOperation;
|
||||
token$player?: Token.CreateOperation | Token.UpdateOperation;
|
||||
user$ref?: CreateOperation | UpdateOperation;
|
||||
wechatUser$user?: WechatUser.CreateOperation | WechatUser.UpdateOperation;
|
||||
extraFile$entity?: ExtraFile.CreateOperation | ExtraFile.UpdateOperation;
|
||||
};
|
||||
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
|
||||
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
|
||||
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
|
||||
type UpdateOperationData = Partial<Omit<OpSchema, "id" | "refId">> & ({
|
||||
ref?: CreateSingleOperation | Omit<UpdateOperation, "id" | "ids" | "filter">;
|
||||
refId?: undefined;
|
||||
} | {
|
||||
ref?: undefined;
|
||||
refId?: String<64>;
|
||||
}) & {
|
||||
mobiles$user?: Mobile.CreateOperation | Omit<Mobile.UpdateOperation, "id" | "ids" | "filter">;
|
||||
userSystems$user?: UserSystem.CreateOperation | Omit<UserSystem.UpdateOperation, "id" | "ids" | "filter">;
|
||||
tokens$user?: Token.CreateOperation | Omit<Token.UpdateOperation, "id" | "ids" | "filter">;
|
||||
tokens$player?: Token.CreateOperation | Omit<Token.UpdateOperation, "id" | "ids" | "filter">;
|
||||
users$ref?: CreateOperation | Omit<UpdateOperation, "id" | "ids" | "filter">;
|
||||
wechatUsers$user?: WechatUser.CreateOperation | Omit<WechatUser.UpdateOperation, "id" | "ids" | "filter">;
|
||||
extraFiles$entity?: ExtraFile.CreateOperation | Omit<ExtraFile.UpdateOperation, "id" | "ids" | "filter">;
|
||||
};
|
||||
export type UpdateOperation = OakOperation<ParticularAction | "update", UpdateOperationData, Filter>;
|
||||
type RemoveOperationData = {} & {
|
||||
ref?: Omit<UpdateOperation | RemoveOperation, "id" | "ids" | "filter">;
|
||||
} & {
|
||||
mobiles$user?: Omit<Mobile.UpdateOperation | Mobile.RemoveOperation, "id" | "ids" | "filter">;
|
||||
userSystems$user?: Omit<UserSystem.UpdateOperation | UserSystem.RemoveOperation, "id" | "ids" | "filter">;
|
||||
tokens$user?: Omit<Token.UpdateOperation | Token.RemoveOperation, "id" | "ids" | "filter">;
|
||||
tokens$player?: Omit<Token.UpdateOperation | Token.RemoveOperation, "id" | "ids" | "filter">;
|
||||
users$ref?: Omit<UpdateOperation | RemoveOperation, "id" | "ids" | "filter">;
|
||||
wechatUsers$user?: Omit<WechatUser.UpdateOperation | WechatUser.RemoveOperation, "id" | "ids" | "filter">;
|
||||
extraFiles$entity?: Omit<ExtraFile.UpdateOperation | ExtraFile.RemoveOperation, "id" | "ids" | "filter">;
|
||||
};
|
||||
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter>;
|
||||
export type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation;
|
||||
export type UserIdSubQuery = Selection<UserIdProjection>;
|
||||
export type NativeAttr = OpAttr | `ref.${OpAttr}` | `ref.ref.${OpAttr}` | `ref.ref.ref.${OpAttr}`;
|
||||
export type FullAttr = NativeAttr | `mobiles$${number}.${Mobile.NativeAttr}` | `userSystems$${number}.${UserSystem.NativeAttr}` | `tokens$user$${number}.${Token.NativeAttr}` | `tokens$player$${number}.${Token.NativeAttr}` | `users$${number}.${NativeAttr}` | `wechatUsers$${number}.${WechatUser.NativeAttr}` | `extraFiles$${number}.${ExtraFile.NativeAttr}`;
|
||||
export type EntityDef = {
|
||||
Schema: Schema;
|
||||
OpSchema: OpSchema;
|
||||
Action: Action;
|
||||
Selection: Selection;
|
||||
Operation: Operation;
|
||||
ParticularAction: ParticularAction;
|
||||
};
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import { StorageDesc } from "oak-domain/src/types/Storage";
|
||||
export const desc: StorageDesc = {
|
||||
attributes: {
|
||||
name: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 16
|
||||
}
|
||||
},
|
||||
nickname: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 64
|
||||
}
|
||||
},
|
||||
password: {
|
||||
type: "text"
|
||||
},
|
||||
birth: {
|
||||
type: "datetime"
|
||||
},
|
||||
gender: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 16
|
||||
}
|
||||
},
|
||||
avatar: {
|
||||
type: "text"
|
||||
},
|
||||
idCardType: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 16
|
||||
}
|
||||
},
|
||||
idNumber: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 32
|
||||
}
|
||||
},
|
||||
ref: {
|
||||
type: "ref",
|
||||
ref: "user"
|
||||
},
|
||||
userState: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 16
|
||||
}
|
||||
},
|
||||
idState: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 16
|
||||
}
|
||||
}
|
||||
},
|
||||
indexes: [
|
||||
{
|
||||
name: 'index_test2',
|
||||
attributes: [
|
||||
{
|
||||
name: 'birth',
|
||||
direction: 'ASC'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'index_test',
|
||||
attributes: [
|
||||
{
|
||||
name: 'name'
|
||||
},
|
||||
{
|
||||
name: 'nickname'
|
||||
}
|
||||
],
|
||||
config: {
|
||||
type: 'fulltext',
|
||||
parser: 'ngram'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
import { String, Int, Float, Double, Boolean, Text, Datetime, File, Image } from "oak-domain/src/types/DataType";
|
||||
import { Q_DateValue, Q_BooleanValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, FulltextFilter, ExprOp, ExpressionKey } from "oak-domain/src/types/Demand";
|
||||
import { OneOf, ValueOf } from "oak-domain/src/types/Polyfill";
|
||||
import * as SubQuery from "../_SubQuery";
|
||||
import { Operation as OakOperation } from "oak-domain/src/types/Entity";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
import * as User from "../User/Schema";
|
||||
import * as System from "../System/Schema";
|
||||
export type OpSchema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
userId: String<64>;
|
||||
systemId: String<64>;
|
||||
relation: 'owner';
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
export type Schema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
userId: String<64>;
|
||||
systemId: String<64>;
|
||||
relation: 'owner';
|
||||
user: User.Schema;
|
||||
system: System.Schema;
|
||||
} & {
|
||||
[A in ExpressionKey]?: any;
|
||||
};
|
||||
type AttrFilter = {
|
||||
id: Q_StringValue | SubQuery.UserSystemIdSubQuery;
|
||||
$$createAt$$: Q_DateValue;
|
||||
$$updateAt$$: Q_DateValue;
|
||||
userId: Q_StringValue | SubQuery.UserIdSubQuery;
|
||||
user: User.Filter;
|
||||
systemId: Q_StringValue | SubQuery.SystemIdSubQuery;
|
||||
system: System.Filter;
|
||||
relation: Q_EnumValue<'owner'>;
|
||||
};
|
||||
export type Filter = MakeFilter<AttrFilter & ExprOp<OpAttr>>;
|
||||
export type Projection = {
|
||||
"#id"?: NodeId;
|
||||
id: 1;
|
||||
$$createAt$$?: 1;
|
||||
$$updateAt$$?: 1;
|
||||
userId?: 1;
|
||||
user?: User.Projection;
|
||||
systemId?: 1;
|
||||
system?: System.Projection;
|
||||
relation?: 1;
|
||||
} & ExprOp<OpAttr>;
|
||||
export type ExportProjection = {
|
||||
"#id"?: NodeId;
|
||||
id?: string;
|
||||
$$createAt$$?: string;
|
||||
$$updateAt$$?: string;
|
||||
userId?: string;
|
||||
user?: User.ExportProjection;
|
||||
systemId?: string;
|
||||
system?: System.ExportProjection;
|
||||
relation?: string;
|
||||
} & ExprOp<OpAttr>;
|
||||
type UserSystemIdProjection = OneOf<{
|
||||
id: 1;
|
||||
}>;
|
||||
type UserIdProjection = OneOf<{
|
||||
userId: 1;
|
||||
}>;
|
||||
type SystemIdProjection = OneOf<{
|
||||
systemId: 1;
|
||||
}>;
|
||||
export type SortAttr = OneOf<{
|
||||
id: 1;
|
||||
$$createAt$$: 1;
|
||||
$$updateAt$$: 1;
|
||||
userId: 1;
|
||||
user: User.SortAttr;
|
||||
systemId: 1;
|
||||
system: System.SortAttr;
|
||||
relation: 1;
|
||||
} & ExprOp<OpAttr>>;
|
||||
export type SortNode = {
|
||||
$attr: SortAttr;
|
||||
$direction?: "asc" | "desc";
|
||||
};
|
||||
export type Sorter = SortNode[];
|
||||
export type SelectOperation<P = Projection> = OakOperation<"select", P, Filter, Sorter>;
|
||||
export type Selection<P = Projection> = Omit<SelectOperation<P>, "action">;
|
||||
export type Exportation = OakOperation<"export", ExportProjection, Filter, Sorter>;
|
||||
type CreateOperationData = Omit<OpSchema, "userId" | "systemId"> & ({
|
||||
user?: User.CreateSingleOperation | (User.UpdateOperation & {
|
||||
id: String<64>;
|
||||
});
|
||||
userId?: undefined;
|
||||
} | {
|
||||
user?: undefined;
|
||||
userId?: String<64>;
|
||||
}) & ({
|
||||
system?: System.CreateSingleOperation | (System.UpdateOperation & {
|
||||
id: String<64>;
|
||||
});
|
||||
systemId?: undefined;
|
||||
} | {
|
||||
system?: undefined;
|
||||
systemId?: String<64>;
|
||||
});
|
||||
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
|
||||
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
|
||||
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
|
||||
type UpdateOperationData = Partial<Omit<OpSchema, "id" | "userId" | "systemId">> & ({
|
||||
user?: User.CreateSingleOperation | Omit<User.UpdateOperation, "id" | "ids" | "filter">;
|
||||
userId?: undefined;
|
||||
} | {
|
||||
user?: undefined;
|
||||
userId?: String<64>;
|
||||
}) & ({
|
||||
system?: System.CreateSingleOperation | Omit<System.UpdateOperation, "id" | "ids" | "filter">;
|
||||
systemId?: undefined;
|
||||
} | {
|
||||
system?: undefined;
|
||||
systemId?: String<64>;
|
||||
});
|
||||
export type UpdateOperation = OakOperation<"update", UpdateOperationData, Filter>;
|
||||
type RemoveOperationData = {} & {
|
||||
user?: Omit<User.UpdateOperation | User.RemoveOperation, "id" | "ids" | "filter">;
|
||||
system?: Omit<System.UpdateOperation | System.RemoveOperation, "id" | "ids" | "filter">;
|
||||
};
|
||||
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter>;
|
||||
export type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation;
|
||||
export type UserIdSubQuery = Selection<UserIdProjection>;
|
||||
export type SystemIdSubQuery = Selection<SystemIdProjection>;
|
||||
export type UserSystemIdSubQuery = Selection<UserSystemIdProjection>;
|
||||
export type NativeAttr = OpAttr | `user.${User.NativeAttr}` | `system.${System.NativeAttr}`;
|
||||
export type FullAttr = NativeAttr;
|
||||
export type EntityDef = {
|
||||
Schema: Schema;
|
||||
OpSchema: OpSchema;
|
||||
Action: GenericAction;
|
||||
Selection: Selection;
|
||||
Operation: Operation;
|
||||
};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { StorageDesc } from "oak-domain/src/types/Storage";
|
||||
export const desc: StorageDesc = {
|
||||
attributes: {
|
||||
user: {
|
||||
type: "ref",
|
||||
ref: "user"
|
||||
},
|
||||
system: {
|
||||
type: "ref",
|
||||
ref: "system"
|
||||
},
|
||||
relation: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
import { String, Int, Float, Double, Boolean, Text, Datetime, File, Image } from "oak-domain/src/types/DataType";
|
||||
import { Q_DateValue, Q_BooleanValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, FulltextFilter, ExprOp, ExpressionKey } from "oak-domain/src/types/Demand";
|
||||
import { OneOf, ValueOf } from "oak-domain/src/types/Polyfill";
|
||||
import * as SubQuery from "../_SubQuery";
|
||||
import { Operation as OakOperation } from "oak-domain/src/types/Entity";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
import * as User from "../User/Schema";
|
||||
import * as Application from "../Application/Schema";
|
||||
export type OpSchema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
origin: 'mp' | 'public';
|
||||
openId?: String<32>;
|
||||
unionId?: String<32>;
|
||||
accessToken: String<32>;
|
||||
sessionKey?: String<64>;
|
||||
subscribed?: Boolean;
|
||||
subscribedAt?: Datetime;
|
||||
unsubscribedAt?: Datetime;
|
||||
userId?: String<64>;
|
||||
applicationId: String<64>;
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
export type Schema = {
|
||||
id: String<64>;
|
||||
$$createAt$$?: Datetime;
|
||||
$$updateAt$$?: Datetime;
|
||||
$$removeAt$$?: Datetime;
|
||||
origin: 'mp' | 'public';
|
||||
openId?: String<32>;
|
||||
unionId?: String<32>;
|
||||
accessToken: String<32>;
|
||||
sessionKey?: String<64>;
|
||||
subscribed?: Boolean;
|
||||
subscribedAt?: Datetime;
|
||||
unsubscribedAt?: Datetime;
|
||||
userId?: String<64>;
|
||||
applicationId: String<64>;
|
||||
user?: User.Schema;
|
||||
application: Application.Schema;
|
||||
} & {
|
||||
[A in ExpressionKey]?: any;
|
||||
};
|
||||
type AttrFilter = {
|
||||
id: Q_StringValue | SubQuery.WechatUserIdSubQuery;
|
||||
$$createAt$$: Q_DateValue;
|
||||
$$updateAt$$: Q_DateValue;
|
||||
origin: Q_EnumValue<'mp' | 'public'>;
|
||||
openId: Q_StringValue;
|
||||
unionId: Q_StringValue;
|
||||
accessToken: Q_StringValue;
|
||||
sessionKey: Q_StringValue;
|
||||
subscribed: Q_BooleanValue;
|
||||
subscribedAt: Q_DateValue;
|
||||
unsubscribedAt: Q_DateValue;
|
||||
userId: Q_StringValue | SubQuery.UserIdSubQuery;
|
||||
user: User.Filter;
|
||||
applicationId: Q_StringValue | SubQuery.ApplicationIdSubQuery;
|
||||
application: Application.Filter;
|
||||
};
|
||||
export type Filter = MakeFilter<AttrFilter & ExprOp<OpAttr>>;
|
||||
export type Projection = {
|
||||
"#id"?: NodeId;
|
||||
id: 1;
|
||||
$$createAt$$?: 1;
|
||||
$$updateAt$$?: 1;
|
||||
origin?: 1;
|
||||
openId?: 1;
|
||||
unionId?: 1;
|
||||
accessToken?: 1;
|
||||
sessionKey?: 1;
|
||||
subscribed?: 1;
|
||||
subscribedAt?: 1;
|
||||
unsubscribedAt?: 1;
|
||||
userId?: 1;
|
||||
user?: User.Projection;
|
||||
applicationId?: 1;
|
||||
application?: Application.Projection;
|
||||
} & ExprOp<OpAttr>;
|
||||
export type ExportProjection = {
|
||||
"#id"?: NodeId;
|
||||
id?: string;
|
||||
$$createAt$$?: string;
|
||||
$$updateAt$$?: string;
|
||||
origin?: string;
|
||||
openId?: string;
|
||||
unionId?: string;
|
||||
accessToken?: string;
|
||||
sessionKey?: string;
|
||||
subscribed?: string;
|
||||
subscribedAt?: string;
|
||||
unsubscribedAt?: string;
|
||||
userId?: string;
|
||||
user?: User.ExportProjection;
|
||||
applicationId?: string;
|
||||
application?: Application.ExportProjection;
|
||||
} & ExprOp<OpAttr>;
|
||||
type WechatUserIdProjection = OneOf<{
|
||||
id: 1;
|
||||
}>;
|
||||
type UserIdProjection = OneOf<{
|
||||
userId: 1;
|
||||
}>;
|
||||
type ApplicationIdProjection = OneOf<{
|
||||
applicationId: 1;
|
||||
}>;
|
||||
export type SortAttr = OneOf<{
|
||||
id: 1;
|
||||
$$createAt$$: 1;
|
||||
$$updateAt$$: 1;
|
||||
origin: 1;
|
||||
openId: 1;
|
||||
unionId: 1;
|
||||
accessToken: 1;
|
||||
sessionKey: 1;
|
||||
subscribed: 1;
|
||||
subscribedAt: 1;
|
||||
unsubscribedAt: 1;
|
||||
userId: 1;
|
||||
user: User.SortAttr;
|
||||
applicationId: 1;
|
||||
application: Application.SortAttr;
|
||||
} & ExprOp<OpAttr>>;
|
||||
export type SortNode = {
|
||||
$attr: SortAttr;
|
||||
$direction?: "asc" | "desc";
|
||||
};
|
||||
export type Sorter = SortNode[];
|
||||
export type SelectOperation<P = Projection> = OakOperation<"select", P, Filter, Sorter>;
|
||||
export type Selection<P = Projection> = Omit<SelectOperation<P>, "action">;
|
||||
export type Exportation = OakOperation<"export", ExportProjection, Filter, Sorter>;
|
||||
type CreateOperationData = Omit<OpSchema, "userId" | "applicationId"> & ({
|
||||
user?: User.CreateSingleOperation | (User.UpdateOperation & {
|
||||
id: String<64>;
|
||||
});
|
||||
userId?: undefined;
|
||||
} | {
|
||||
user?: undefined;
|
||||
userId?: String<64>;
|
||||
}) & ({
|
||||
application?: Application.CreateSingleOperation | (Application.UpdateOperation & {
|
||||
id: String<64>;
|
||||
});
|
||||
applicationId?: undefined;
|
||||
} | {
|
||||
application?: undefined;
|
||||
applicationId?: String<64>;
|
||||
});
|
||||
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
|
||||
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
|
||||
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
|
||||
type UpdateOperationData = Partial<Omit<OpSchema, "id" | "userId" | "applicationId">> & ({
|
||||
user?: User.CreateSingleOperation | Omit<User.UpdateOperation, "id" | "ids" | "filter">;
|
||||
userId?: undefined;
|
||||
} | {
|
||||
user?: undefined;
|
||||
userId?: String<64>;
|
||||
}) & ({
|
||||
application?: Application.CreateSingleOperation | Omit<Application.UpdateOperation, "id" | "ids" | "filter">;
|
||||
applicationId?: undefined;
|
||||
} | {
|
||||
application?: undefined;
|
||||
applicationId?: String<64>;
|
||||
});
|
||||
export type UpdateOperation = OakOperation<"update", UpdateOperationData, Filter>;
|
||||
type RemoveOperationData = {} & {
|
||||
user?: Omit<User.UpdateOperation | User.RemoveOperation, "id" | "ids" | "filter">;
|
||||
application?: Omit<Application.UpdateOperation | Application.RemoveOperation, "id" | "ids" | "filter">;
|
||||
};
|
||||
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter>;
|
||||
export type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation;
|
||||
export type UserIdSubQuery = Selection<UserIdProjection>;
|
||||
export type ApplicationIdSubQuery = Selection<ApplicationIdProjection>;
|
||||
export type WechatUserIdSubQuery = Selection<WechatUserIdProjection>;
|
||||
export type NativeAttr = OpAttr | `user.${User.NativeAttr}` | `application.${Application.NativeAttr}`;
|
||||
export type FullAttr = NativeAttr;
|
||||
export type EntityDef = {
|
||||
Schema: Schema;
|
||||
OpSchema: OpSchema;
|
||||
Action: GenericAction;
|
||||
Selection: Selection;
|
||||
Operation: Operation;
|
||||
};
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { StorageDesc } from "oak-domain/src/types/Storage";
|
||||
export const desc: StorageDesc = {
|
||||
attributes: {
|
||||
origin: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 16
|
||||
}
|
||||
},
|
||||
openId: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 32
|
||||
}
|
||||
},
|
||||
unionId: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 32
|
||||
}
|
||||
},
|
||||
accessToken: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 32
|
||||
}
|
||||
},
|
||||
sessionKey: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
width: 64
|
||||
}
|
||||
},
|
||||
subscribed: {
|
||||
type: "boolean"
|
||||
},
|
||||
subscribedAt: {
|
||||
type: "datetime"
|
||||
},
|
||||
unsubscribedAt: {
|
||||
type: "datetime"
|
||||
},
|
||||
user: {
|
||||
type: "ref",
|
||||
ref: "user"
|
||||
},
|
||||
application: {
|
||||
type: "ref",
|
||||
ref: "application"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import * as Application from "./Application/Schema";
|
||||
import * as ExtraFile from "./ExtraFile/Schema";
|
||||
import * as Mobile from "./Mobile/Schema";
|
||||
import * as UserSystem from "./UserSystem/Schema";
|
||||
import * as System from "./System/Schema";
|
||||
import * as Token from "./Token/Schema";
|
||||
import * as User from "./User/Schema";
|
||||
import * as WechatUser from "./WechatUser/Schema";
|
||||
export type ApplicationIdSubQuery = {
|
||||
[K in "$in" | "$nin"]?: (WechatUser.ApplicationIdSubQuery & {
|
||||
entity: "wechatUser";
|
||||
}) | (Application.ApplicationIdSubQuery & {
|
||||
entity: "application";
|
||||
});
|
||||
};
|
||||
export type ExtraFileIdSubQuery = {
|
||||
[K in "$in" | "$nin"]?: (ExtraFile.ExtraFileIdSubQuery & {
|
||||
entity: "extraFile";
|
||||
});
|
||||
};
|
||||
export type MobileIdSubQuery = {
|
||||
[K in "$in" | "$nin"]?: (Mobile.MobileIdSubQuery & {
|
||||
entity: "mobile";
|
||||
});
|
||||
};
|
||||
export type UserSystemIdSubQuery = {
|
||||
[K in "$in" | "$nin"]?: (UserSystem.UserSystemIdSubQuery & {
|
||||
entity: "userSystem";
|
||||
});
|
||||
};
|
||||
export type SystemIdSubQuery = {
|
||||
[K in "$in" | "$nin"]?: (Application.SystemIdSubQuery & {
|
||||
entity: "application";
|
||||
}) | (UserSystem.SystemIdSubQuery & {
|
||||
entity: "userSystem";
|
||||
}) | (System.SystemIdSubQuery & {
|
||||
entity: "system";
|
||||
});
|
||||
};
|
||||
export type TokenIdSubQuery = {
|
||||
[K in "$in" | "$nin"]?: (Token.TokenIdSubQuery & {
|
||||
entity: "token";
|
||||
});
|
||||
};
|
||||
export type UserIdSubQuery = {
|
||||
[K in "$in" | "$nin"]?: (Mobile.UserIdSubQuery & {
|
||||
entity: "mobile";
|
||||
}) | (UserSystem.UserIdSubQuery & {
|
||||
entity: "userSystem";
|
||||
}) | (Token.UserIdSubQuery & {
|
||||
entity: "token";
|
||||
}) | (User.UserIdSubQuery & {
|
||||
entity: "user";
|
||||
}) | (WechatUser.UserIdSubQuery & {
|
||||
entity: "wechatUser";
|
||||
}) | (User.UserIdSubQuery & {
|
||||
entity: "user";
|
||||
});
|
||||
};
|
||||
export type WechatUserIdSubQuery = {
|
||||
[K in "$in" | "$nin"]?: (WechatUser.WechatUserIdSubQuery & {
|
||||
entity: "wechatUser";
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import buildSchema from 'oak-domain/src/compiler/schemalBuilder';
|
||||
|
||||
process.env.NODE_ENV = 'development';
|
||||
process.env.IN_OAK_DOMAIN = 'yes';
|
||||
buildSchema(`${__dirname}/../node_modules/oak-domain/src/entities`, `${__dirname}/app-domain/`);
|
||||
process.env.IN_OAK_DOMAIN = undefined;
|
||||
|
|
@ -0,0 +1,602 @@
|
|||
import { v4 } from 'uuid';
|
||||
import { describe, it } from 'mocha';
|
||||
import TreeStore from '../src/store';
|
||||
import EntityDict from './app-domain/EntityDict';
|
||||
import { Context } from '../src/context';
|
||||
import { storageSchema } from './app-domain/Storage';
|
||||
import assert from 'assert';
|
||||
|
||||
describe('基础测试', function () {
|
||||
this.timeout(1000000);
|
||||
|
||||
it('[1.0]简单查询', async () => {
|
||||
const store = new TreeStore<keyof EntityDict, EntityDict>(storageSchema);
|
||||
const context = new Context(store);
|
||||
await store.operate('application', {
|
||||
action: 'create',
|
||||
data: [{
|
||||
id: 'aaa',
|
||||
name: 'test',
|
||||
description: 'ttttt',
|
||||
type: 'web',
|
||||
system: {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: 'bbb',
|
||||
name: 'systest',
|
||||
description: 'aaaaa',
|
||||
config: {},
|
||||
}
|
||||
}
|
||||
}, {
|
||||
id: 'aaa2',
|
||||
name: 'test2',
|
||||
description: 'ttttt2',
|
||||
type: 'web',
|
||||
system: {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: 'ccc',
|
||||
name: 'systest2',
|
||||
description: 'aaaaa2',
|
||||
config: {},
|
||||
}
|
||||
}
|
||||
}]
|
||||
}, context);
|
||||
|
||||
const applications = await store.select('application', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
systemId: 1,
|
||||
system: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
}
|
||||
},
|
||||
sorter: [
|
||||
{
|
||||
$attr: {
|
||||
system: {
|
||||
name: 1,
|
||||
}
|
||||
},
|
||||
$direction: 'asc',
|
||||
}
|
||||
]
|
||||
}, context);
|
||||
console.log(applications);
|
||||
});
|
||||
|
||||
it('[1.1]子查询', async () => {
|
||||
const store = new TreeStore<keyof EntityDict, EntityDict>(storageSchema);
|
||||
const context = new Context(store);
|
||||
|
||||
await store.operate('user', {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: v4(),
|
||||
name: 'xc',
|
||||
nickname: 'xc',
|
||||
}
|
||||
}, context);
|
||||
|
||||
/**
|
||||
* 这个子查询没有跨结点的表达式,所以应该可以提前计算子查询的值
|
||||
* 这个可以跟一下store.ts中translateAttribute函数里$in的分支代码
|
||||
* by Xc
|
||||
*/
|
||||
const rows = await store.select('user', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
nickname: 1,
|
||||
},
|
||||
filter: {
|
||||
id: {
|
||||
$in: {
|
||||
entity: 'token',
|
||||
data: {
|
||||
userId: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: 'mobile',
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}, context);
|
||||
// console.log(rows);
|
||||
assert(rows.length === 0);
|
||||
});
|
||||
|
||||
it('[1.2]行内属性上的表达式', async () => {
|
||||
const store = new TreeStore<keyof EntityDict, EntityDict>(storageSchema);
|
||||
const context = new Context(store);
|
||||
|
||||
await store.operate('user', {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: v4(),
|
||||
name: 'xc',
|
||||
nickname: 'xc',
|
||||
}
|
||||
}, context);
|
||||
|
||||
const users = await store.select('user', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
nickname: 1,
|
||||
},
|
||||
filter: {
|
||||
// '#id': 'node-123',
|
||||
$expr: {
|
||||
$ne: [{
|
||||
'#attr': 'name',
|
||||
}, {
|
||||
"#attr": 'nickname',
|
||||
}]
|
||||
}
|
||||
},
|
||||
}, context);
|
||||
|
||||
console.log(users);
|
||||
});
|
||||
|
||||
it('[1.3]跨filter结点的表达式', async () => {
|
||||
const store = new TreeStore<keyof EntityDict, EntityDict>(storageSchema);
|
||||
const context = new Context(store);
|
||||
|
||||
await store.operate('application', {
|
||||
action: 'create',
|
||||
data: [{
|
||||
id: 'aaa',
|
||||
name: 'test',
|
||||
description: 'ttttt',
|
||||
type: 'web',
|
||||
system: {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: 'bbb',
|
||||
name: 'systest',
|
||||
description: 'aaaaa',
|
||||
config: {},
|
||||
}
|
||||
}
|
||||
}, {
|
||||
id: 'aaa2',
|
||||
name: 'test2',
|
||||
description: 'ttttt2',
|
||||
type: 'web',
|
||||
system: {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: 'ccc',
|
||||
name: 'test2',
|
||||
description: 'aaaaa2',
|
||||
config: {},
|
||||
}
|
||||
}
|
||||
}]
|
||||
}, context);
|
||||
|
||||
const applications = await store.select('application', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
systemId: 1,
|
||||
system: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
}
|
||||
},
|
||||
filter: {
|
||||
$expr: {
|
||||
$startsWith: [
|
||||
{
|
||||
"#refAttr": 'name',
|
||||
"#refId": 'node-1',
|
||||
},
|
||||
{
|
||||
"#attr": 'name',
|
||||
}
|
||||
]
|
||||
},
|
||||
system: {
|
||||
"#id": 'node-1',
|
||||
}
|
||||
},
|
||||
sorter: [
|
||||
{
|
||||
$attr: {
|
||||
system: {
|
||||
name: 1,
|
||||
}
|
||||
},
|
||||
$direction: 'asc',
|
||||
}
|
||||
]
|
||||
}, context);
|
||||
console.log(applications);
|
||||
});
|
||||
|
||||
|
||||
it('[1.4]跨filter子查询的表达式', async () => {
|
||||
const store = new TreeStore<keyof EntityDict, EntityDict>(storageSchema);
|
||||
const context = new Context(store);
|
||||
|
||||
await store.operate('application', {
|
||||
action: 'create',
|
||||
data: [{
|
||||
id: 'aaa',
|
||||
name: 'test',
|
||||
description: 'ttttt',
|
||||
type: 'web',
|
||||
system: {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: 'bbb',
|
||||
name: 'systest',
|
||||
description: 'aaaaa',
|
||||
config: {},
|
||||
}
|
||||
}
|
||||
}, {
|
||||
id: 'aaa2',
|
||||
name: 'test2',
|
||||
description: 'ttttt2',
|
||||
type: 'web',
|
||||
system: {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: 'ccc',
|
||||
name: 'test2',
|
||||
description: 'aaaaa2',
|
||||
config: {},
|
||||
}
|
||||
}
|
||||
}]
|
||||
}, context);
|
||||
|
||||
let systems = await store.select('system', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
},
|
||||
filter: {
|
||||
"#id": 'node-1',
|
||||
id: {
|
||||
$nin: {
|
||||
entity: 'application',
|
||||
data: {
|
||||
systemId: 1,
|
||||
},
|
||||
filter: {
|
||||
$expr: {
|
||||
$eq: [
|
||||
{
|
||||
"#attr": 'name',
|
||||
},
|
||||
{
|
||||
'#refId': 'node-1',
|
||||
"#refAttr": 'name',
|
||||
}
|
||||
]
|
||||
},
|
||||
'#id': 'node-2',
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
sorter: [
|
||||
{
|
||||
$attr: {
|
||||
name: 1,
|
||||
},
|
||||
$direction: 'asc',
|
||||
}
|
||||
]
|
||||
}, context);
|
||||
assert(systems.length === 1 && systems[0].id === 'bbb');
|
||||
systems = await store.select('system', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
},
|
||||
filter: {
|
||||
"#id": 'node-1',
|
||||
id: {
|
||||
$in: {
|
||||
entity: 'application',
|
||||
data: {
|
||||
systemId: 1,
|
||||
},
|
||||
filter: {
|
||||
$expr: {
|
||||
$eq: [
|
||||
{
|
||||
"#attr": 'name',
|
||||
},
|
||||
{
|
||||
'#refId': 'node-1',
|
||||
"#refAttr": 'name',
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
sorter: [
|
||||
{
|
||||
$attr: {
|
||||
name: 1,
|
||||
},
|
||||
$direction: 'asc',
|
||||
}
|
||||
]
|
||||
}, context);
|
||||
assert(systems.length === 1 && systems[0].id === 'ccc');
|
||||
});
|
||||
|
||||
it('[1.5]projection中的跨结点表达式', async () => {
|
||||
const store = new TreeStore<keyof EntityDict, EntityDict>(storageSchema);
|
||||
const context = new Context(store);
|
||||
|
||||
await store.operate('application', {
|
||||
action: 'create',
|
||||
data: [{
|
||||
id: 'aaa',
|
||||
name: 'test',
|
||||
description: 'ttttt',
|
||||
type: 'web',
|
||||
system: {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: 'bbb',
|
||||
name: 'systest',
|
||||
description: 'aaaaa',
|
||||
config: {},
|
||||
}
|
||||
}
|
||||
}, {
|
||||
id: 'aaa2',
|
||||
name: 'test2',
|
||||
description: 'ttttt2',
|
||||
type: 'web',
|
||||
system: {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: 'ccc',
|
||||
name: 'test2',
|
||||
description: 'aaaaa2',
|
||||
config: {},
|
||||
}
|
||||
}
|
||||
}]
|
||||
}, context);
|
||||
|
||||
let applications = await store.select('application', {
|
||||
data: {
|
||||
"#id": 'node-1',
|
||||
id: 1,
|
||||
name: 1,
|
||||
system: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
$expr: {
|
||||
$eq: [
|
||||
{
|
||||
"#attr": 'name',
|
||||
},
|
||||
{
|
||||
'#refId': 'node-1',
|
||||
"#refAttr": 'name',
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
}, context);
|
||||
// console.log(applications);
|
||||
assert(applications.length === 2);
|
||||
applications.forEach(
|
||||
(app) => {
|
||||
assert(app.id === 'aaa' && app.system!.$expr === false
|
||||
|| app.id === 'aaa2' && app.system!.$expr === true);
|
||||
}
|
||||
);
|
||||
|
||||
applications = await store.select('application', {
|
||||
data: {
|
||||
$expr: {
|
||||
$eq: [
|
||||
{
|
||||
"#attr": 'name',
|
||||
},
|
||||
{
|
||||
'#refId': 'node-1',
|
||||
"#refAttr": 'name',
|
||||
}
|
||||
]
|
||||
},
|
||||
id: 1,
|
||||
name: 1,
|
||||
system: {
|
||||
"#id": 'node-1',
|
||||
id: 1,
|
||||
name: 1,
|
||||
}
|
||||
},
|
||||
}, context);
|
||||
console.log(applications);
|
||||
// assert(applications.length === 2);
|
||||
applications.forEach(
|
||||
(app) => {
|
||||
assert(app.id === 'aaa' && app.$expr === false
|
||||
|| app.id === 'aaa2' && app.$expr === true);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('[1.6]projection中的一对多跨结点表达式', async () => {
|
||||
const store = new TreeStore<keyof EntityDict, EntityDict>(storageSchema);
|
||||
const context = new Context(store);
|
||||
|
||||
await store.operate('system', {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: 'bbb',
|
||||
name: 'test2',
|
||||
description: 'aaaaa',
|
||||
config: {},
|
||||
application$system: {
|
||||
action: 'create',
|
||||
data: [
|
||||
{
|
||||
id: 'aaa',
|
||||
name: 'test',
|
||||
description: 'ttttt',
|
||||
type: 'web',
|
||||
},
|
||||
{
|
||||
|
||||
id: 'aaa2',
|
||||
name: 'test2',
|
||||
description: 'ttttt2',
|
||||
type: 'weChatMp',
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}, context);
|
||||
|
||||
const systems = await store.select('system', {
|
||||
data: {
|
||||
"#id": 'node-1',
|
||||
id: 1,
|
||||
name: 1,
|
||||
application$system: {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
$expr: {
|
||||
$eq: [
|
||||
{
|
||||
"#attr": 'name',
|
||||
},
|
||||
{
|
||||
'#refId': 'node-1',
|
||||
"#refAttr": 'name',
|
||||
}
|
||||
]
|
||||
},
|
||||
$expr2: {
|
||||
'#refId': 'node-1',
|
||||
"#refAttr": 'id',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}, context);
|
||||
// console.log(systems);
|
||||
assert(systems.length === 1);
|
||||
const [ system ] = systems;
|
||||
const { application$system: applications } = system;
|
||||
assert(applications!.length === 2);
|
||||
applications!.forEach(
|
||||
(ele) => {
|
||||
assert(ele.id === 'aaa' && ele.$expr === false && ele.$expr2 === 'bbb'
|
||||
|| ele.id === 'aaa2' && ele.$expr === true && ele.$expr2 === 'bbb');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('[1.7]事务性测试', async () => {
|
||||
const store = new TreeStore<keyof EntityDict, EntityDict>(storageSchema);
|
||||
const context = new Context(store);
|
||||
|
||||
await store.operate('system', {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: 'bbb',
|
||||
name: 'test2',
|
||||
description: 'aaaaa',
|
||||
config: {},
|
||||
application$system: {
|
||||
action: 'create',
|
||||
data: [
|
||||
{
|
||||
id: 'aaa',
|
||||
name: 'test',
|
||||
description: 'ttttt',
|
||||
type: 'web',
|
||||
},
|
||||
{
|
||||
|
||||
id: 'aaa2',
|
||||
name: 'test2',
|
||||
description: 'ttttt2',
|
||||
type: 'weChatMp',
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}, context);
|
||||
|
||||
await context.begin();
|
||||
const systems = await store.select('system', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
application$system: {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
}
|
||||
},
|
||||
},
|
||||
}, context);
|
||||
assert(systems.length === 1 && systems[0].application$system!.length === 2);
|
||||
|
||||
await store.operate('application', {
|
||||
action: 'remove',
|
||||
data: {},
|
||||
filter: {
|
||||
id: 'aaa',
|
||||
}
|
||||
}, context);
|
||||
|
||||
const systems2 = await store.select('system', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
application$system: {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
}
|
||||
},
|
||||
},
|
||||
}, context);
|
||||
assert(systems2.length === 1 && systems2[0].application$system!.length === 1);
|
||||
await context.rollback();
|
||||
|
||||
const systems3 = await store.select('system', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
application$system: {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
}
|
||||
},
|
||||
},
|
||||
}, context);
|
||||
assert(systems3.length === 1 && systems3[0].application$system!.length === 2);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "lib", /* Redirect output structure to the directory. */
|
||||
"rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
"downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
/* Advanced Options */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
, "oak-domain/src/compiler/schemalBuilder.ts" ],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/*.spec.ts",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue