initialize

This commit is contained in:
Xu Chang 2022-02-26 11:05:38 +08:00
commit 4a330123c2
39 changed files with 4452 additions and 0 deletions

67
.gitignore vendored Normal file
View File

@ -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

5
.mocharc.json Normal file
View File

@ -0,0 +1,5 @@
{
"extension": ["ts"],
"spec": "test/test*.ts",
"require": "ts-node/register"
}

14
lib/context.d.ts vendored Normal file
View File

@ -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>;
}

33
lib/context.js Normal file
View File

@ -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;

3
lib/index.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
import TreeStore from "./store";
import { Context } from './context';
export { TreeStore, Context, };

10
lib/index.js Normal file
View File

@ -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; } });

56
lib/store.d.ts vendored Normal file
View File

@ -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
* 3ExprNodeTranslatorcase
* 3.1
* 3.2ExprLaterCheckFn
* @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;
}

799
lib/store.js Normal file
View File

@ -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;

13
lib/types/type.d.ts vendored Normal file
View File

@ -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;

2
lib/types/type.js Normal file
View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

36
package.json Normal file
View File

@ -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"
}
}

35
src/context.ts Normal file
View File

@ -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;
}
}

7
src/index.ts Normal file
View File

@ -0,0 +1,7 @@
import TreeStore from "./store";
import { Context } from './context';
export {
TreeStore,
Context,
};

991
src/store.ts Normal file
View File

@ -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
* 3ExprNodeTranslatorcase
* 3.1
* 3.2ExprLaterCheckFn
* @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);
}
}

16
src/types/type.ts Normal file
View File

@ -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;

View File

@ -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;
};

View File

@ -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"
}
}
};

View File

@ -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;

View File

@ -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;
};

View File

@ -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
}
}
}
};

View File

@ -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;
};

View File

@ -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"
}
}
};

View File

@ -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
};

View File

@ -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;
};

View File

@ -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"
}
}
};

View File

@ -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;

View File

@ -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;
};

View File

@ -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
}
}
}
};

View File

@ -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;

View File

@ -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;
};

View File

@ -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'
}
}
]
};

View File

@ -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;
};

View File

@ -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
}
}
}
};

View File

@ -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;
};

View File

@ -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"
}
}
};

View File

@ -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";
});
};

6
test/build-app-domain.ts Normal file
View File

@ -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;

602
test/test.ts Normal file
View File

@ -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);
});
});

74
tsconfig.json Normal file
View File

@ -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"
]
}