modi相关的checkers以及schema中actionType的细化

This commit is contained in:
Xu Chang 2022-08-29 15:53:47 +08:00
parent 87b30fb8fa
commit 51b4600147
38 changed files with 559 additions and 54 deletions

View File

@ -1,5 +1,11 @@
import { ActionDef } from '../types/Action';
export declare type GenericAction = 'create' | 'update' | 'remove' | 'select' | 'count' | 'stat' | 'download';
export declare type ReadOnlyAction = 'select' | 'count' | 'stat' | 'download';
export declare type AppendOnlyAction = ReadOnlyAction | 'create';
export declare type ExcludeUpdateAction = AppendOnlyAction | 'remove';
export declare type GenericAction = 'update' | ExcludeUpdateAction;
export declare const readOnlyActions: string[];
export declare const appendOnlyActions: string[];
export declare const excludeUpdateActions: string[];
export declare const genericActions: string[];
export declare type AbleAction = 'enable' | 'disable';
export declare type AbleState = 'enabled' | 'disabled';

View File

@ -1,7 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeAbleActionDef = exports.genericActions = void 0;
exports.genericActions = ['create', 'update', 'remove', 'count', 'stat', 'download', 'select'];
exports.makeAbleActionDef = exports.genericActions = exports.excludeUpdateActions = exports.appendOnlyActions = exports.readOnlyActions = void 0;
exports.readOnlyActions = ['count', 'stat', 'download', 'select'];
exports.appendOnlyActions = exports.readOnlyActions.concat('create');
exports.excludeUpdateActions = exports.appendOnlyActions.concat('remove');
exports.genericActions = exports.excludeUpdateActions.concat('update');
var makeAbleActionDef = function (initialState) { return ({
stm: {
enable: ['disabled', 'enabled'],

View File

@ -4,6 +4,7 @@ export declare type IState = 'active' | 'applied' | 'abandoned';
export declare type IAction = 'apply' | 'abandon';
export declare type ParticularAction = IAction;
export declare type Action = GenericAction | ParticularAction;
export declare const actions: string[];
export declare const ActionDefDict: {
iState: ActionDef<IAction, IState>;
};

View File

@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActionDefDict = void 0;
exports.ActionDefDict = exports.actions = void 0;
var IActionDef = {
stm: {
apply: ['active', 'applied'],
@ -8,6 +8,7 @@ var IActionDef = {
},
is: 'active'
};
exports.actions = ["count", "stat", "download", "select", "create", "remove", "update", "apply", "abandon"];
exports.ActionDefDict = {
iState: IActionDef
};

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.desc = void 0;
var Action_1 = require("./Action");
exports.desc = {
attributes: {
targetEntity: {
@ -43,6 +44,8 @@ exports.desc = {
}
}
},
actionType: "crud",
actions: Action_1.actions,
indexes: [
{
name: 'index_state',

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.desc = void 0;
var action_1 = require("../../actions/action");
exports.desc = {
attributes: {
modiId: {
@ -19,5 +20,7 @@ exports.desc = {
length: 64
}
}
}
},
actionType: "appendOnly",
actions: action_1.appendOnlyActions
};

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.desc = void 0;
var action_1 = require("../../actions/action");
exports.desc = {
attributes: {
action: {
@ -22,5 +23,7 @@ exports.desc = {
type: "ref",
ref: "user"
}
}
},
actionType: "appendOnly",
actions: action_1.appendOnlyActions
};

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.desc = void 0;
var action_1 = require("../../actions/action");
exports.desc = {
attributes: {
operId: {
@ -19,5 +20,7 @@ exports.desc = {
length: 64
}
}
}
},
actionType: "appendOnly",
actions: action_1.appendOnlyActions
};

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.desc = void 0;
var action_1 = require("../../actions/action");
exports.desc = {
attributes: {
name: {
@ -18,5 +19,7 @@ exports.desc = {
password: {
type: "text"
}
}
},
actionType: "crud",
actions: action_1.genericActions
};

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

@ -0,0 +1,3 @@
import { EntityDict } from '../base-app-domain';
import { StorageSchema, EntityDict as BaseEntityDict, Context } from '../types';
export declare function createCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends Context<ED>>(schema: StorageSchema<ED>): import("../types").Checker<ED, keyof ED, Cxt>[];

8
lib/checkers/index.js Normal file
View File

@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCheckers = void 0;
var modi_1 = require("../store/modi");
function createCheckers(schema) {
return (0, modi_1.createModiRelatedCheckers)(schema);
}
exports.createCheckers = createCheckers;

View File

@ -199,7 +199,7 @@ var RESERVED_ACTION_NAMES = ['GenericAction', 'ParticularAction'];
var action_1 = require("../actions/action");
var DataType_1 = require("../types/DataType");
var Entity_1 = require("../types/Entity");
function dealWithActions(moduleName, filename, node, program) {
function dealWithActions(moduleName, filename, node, program, sourceFile) {
var actionTexts = action_1.genericActions.map(function (ele) { return ele; });
if (ts.isUnionTypeNode(node)) {
var actionNames = node.types.map(function (ele) {
@ -243,8 +243,7 @@ function dealWithActions(moduleName, filename, node, program) {
_a));
}
});
// 为每个action在schema中建立相应的state域(除了genericState)
// 放到actionDef的定义处去做。by Xc
pushStatementIntoActionAst(moduleName, factory.createVariableStatement([factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier("actions"), undefined, undefined, factory.createArrayLiteralExpression(actionTexts.map(function (ele) { return factory.createStringLiteral(ele); }), false))], ts.NodeFlags.Const)), sourceFile);
}
function getEntityImported(declaration, filename) {
var moduleSpecifier = declaration.moduleSpecifier, importClause = declaration.importClause;
@ -315,6 +314,7 @@ function analyzeEntity(filename, path, program) {
var hasRelationDef = false;
var hasActionOrStateDef = false;
var toModi = false;
var actionType = 'crud';
var enumStringAttrs = [];
var states = [];
var localEnumStringTypes = [];
@ -484,7 +484,7 @@ function analyzeEntity(filename, path, program) {
factory.createTypeReferenceNode(factory.createIdentifier("GenericAction"), undefined),
factory.createTypeReferenceNode(factory.createIdentifier("ParticularAction"), undefined)
])), sourceFile);
dealWithActions(moduleName, filename, node.type, program);
dealWithActions(moduleName, filename, node.type, program, sourceFile);
}
else if (node.name.text === 'Relation') {
(0, assert_1.default)(!localeDef, "\u3010".concat(filename, "\u3011locale\u5B9A\u4E49\u987B\u5728Relation\u4E4B\u540E"));
@ -688,11 +688,10 @@ function analyzeEntity(filename, path, program) {
});
indexes = declaration.initializer;
}
else if (ts.isIdentifier(declaration.name) && declaration.name.text === 'locale') {
else if (ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'LocaleDef') {
// locale定义
var type = declaration.type, initializer = declaration.initializer;
(0, assert_1.default)(ts.isObjectLiteralExpression(initializer));
(0, assert_1.default)(ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) && type.typeName.text === 'LocaleDef', 'locale定义的类型必须是LocaleDef');
var properties = initializer.properties;
(0, assert_1.default)(properties.length > 0, "".concat(filename, "\u81F3\u5C11\u9700\u8981\u6709\u4E00\u79CDlocale\u5B9A\u4E49"));
var allEnumStringAttrs = enumStringAttrs.concat(states);
@ -737,6 +736,10 @@ function analyzeEntity(filename, path, program) {
}
localeDef = initializer;
}
else if (ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'ActionType') {
(0, assert_1.default)(ts.isStringLiteral(declaration.initializer));
actionType = declaration.initializer.text;
}
else {
throw new Error("\u4E0D\u80FD\u7406\u89E3\u7684\u5B9A\u4E49\u5185\u5BB9".concat(declaration.name.getText()));
}
@ -746,11 +749,15 @@ function analyzeEntity(filename, path, program) {
if (!hasActionDef && hasActionOrStateDef) {
throw new Error("".concat(filename, "\u4E2D\u6709Action\u6216State\u5B9A\u4E49\uFF0C\u4F46\u6CA1\u6709\u5B9A\u4E49\u5B8C\u6574\u7684Action\u7C7B\u578B"));
}
if (hasActionDef && actionType !== 'crud') {
throw new Error("".concat(filename, "\u4E2D\u6709Action\u5B9A\u4E49\uFF0C\u4F46\u5374\u5B9A\u4E49\u4E86actionType\u4E0D\u662Fcrud"));
}
(0, assert_1.default)(schemaAttrs.length > 0);
var schema = {
schemaAttrs: schemaAttrs,
sourceFile: sourceFile,
toModi: toModi,
actionType: actionType,
};
if (hasFulltextIndex) {
(0, lodash_1.assign)(schema, {
@ -2639,11 +2646,33 @@ function outputStorage(outputDir, printer) {
var entityAssignments = [];
for (var entity in Schema) {
var indexExpressions = [];
var _a = Schema[entity], sourceFile = _a.sourceFile, fulltextIndex = _a.fulltextIndex, indexes = _a.indexes, toModi = _a.toModi;
var _a = Schema[entity], sourceFile = _a.sourceFile, fulltextIndex = _a.fulltextIndex, indexes = _a.indexes, toModi = _a.toModi, actionType = _a.actionType;
var statements = [
factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("StorageDesc"))])), factory.createStringLiteral("".concat((0, env_1.TYPE_PATH_IN_OAK_DOMAIN)(), "Storage")), undefined),
factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("OpSchema"))])), factory.createStringLiteral("./Schema"), undefined)
];
switch (actionType) {
case 'readOnly': {
statements.push(factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("readOnlyActions"), factory.createIdentifier("actions"))])), factory.createStringLiteral((0, env_1.ACTION_CONSTANT_IN_OAK_DOMAIN)()), undefined));
break;
}
case 'appendOnly': {
statements.push(factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("appendOnlyActions"), factory.createIdentifier("actions"))])), factory.createStringLiteral((0, env_1.ACTION_CONSTANT_IN_OAK_DOMAIN)()), undefined));
break;
}
case 'excludeUpdate': {
statements.push(factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("excludeUpdateActions"), factory.createIdentifier("actions"))])), factory.createStringLiteral((0, env_1.ACTION_CONSTANT_IN_OAK_DOMAIN)()), undefined));
break;
}
default: {
if (ActionAsts[entity]) {
statements.push(factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("actions"))])), factory.createStringLiteral("./Action"), undefined));
}
else {
statements.push(factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("genericActions"), factory.createIdentifier("actions"))])), factory.createStringLiteral((0, env_1.ACTION_CONSTANT_IN_OAK_DOMAIN)()), undefined));
}
}
}
var propertyAssignments = [];
var attributes = constructAttributes(entity);
propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("attributes"), factory.createObjectLiteralExpression(attributes, true)));
@ -2653,6 +2682,7 @@ function outputStorage(outputDir, printer) {
if (toModi) {
propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("toModi"), factory.createTrue()));
}
propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("actionType"), factory.createStringLiteral(actionType)), factory.createShorthandPropertyAssignment(factory.createIdentifier("actions"), undefined));
if (indexExpressions.length > 0) {
propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("indexes"), factory.createArrayLiteralExpression(indexExpressions, true)));
}

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
;
var actionType = 'appendOnly';
var locale = {
zh_CN: {
attr: {

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
;
var actionType = 'appendOnly';
var locale = {
zh_CN: {
attr: {

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
;
var actionType = 'appendOnly';
var locale = {
zh_CN: {
attr: {

View File

@ -723,7 +723,7 @@ var CascadeStore = /** @class */ (function (_super) {
}
return [3 /*break*/, 23];
case 1:
if (!(option.modiParentEntity && !['modi', 'modiEntity'].includes(entity))) return [3 /*break*/, 3];
if (!(option.modiParentEntity && !['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity))) return [3 /*break*/, 3];
modiCreate = {
id: 'dummy',
action: 'create',

9
lib/store/modi.d.ts vendored
View File

@ -1,6 +1,11 @@
import { OpSchema as Modi } from '../base-app-domain/Modi/Schema';
import { Operation } from '../types';
import { EntityDict } from '../base-app-domain';
import { UniversalContext } from '../store/UniversalContext';
import { OpSchema as Modi, Filter } from '../base-app-domain/Modi/Schema';
import { Checker, Operation, StorageSchema, EntityDict as BaseEntityDict, Context } from '../types';
export declare function createOperationsFromModies(modies: Modi[]): Array<{
operation: Operation<string, Object, Object>;
entity: string;
}>;
export declare function applyModis<ED extends EntityDict, Cxt extends UniversalContext<ED>>(filter: Filter, context: Cxt): Promise<import("../types").OperationResult<ED>>;
export declare function abandonModis<ED extends EntityDict, Cxt extends UniversalContext<ED>>(filter: Filter, context: Cxt): Promise<import("../types").OperationResult<ED>>;
export declare function createModiRelatedCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends Context<ED>>(schema: StorageSchema<ED>): Checker<ED, keyof ED, Cxt>[];

View File

@ -1,6 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createOperationsFromModies = void 0;
exports.createModiRelatedCheckers = exports.abandonModis = exports.applyModis = exports.createOperationsFromModies = void 0;
var tslib_1 = require("tslib");
var types_1 = require("../types");
var action_1 = require("../actions/action");
var lodash_1 = require("../utils/lodash");
function createOperationsFromModies(modies) {
return modies.map(function (modi) {
return {
@ -15,3 +19,125 @@ function createOperationsFromModies(modies) {
});
}
exports.createOperationsFromModies = createOperationsFromModies;
function applyModis(filter, context) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var _a, _b, _c;
var _d;
return tslib_1.__generator(this, function (_e) {
switch (_e.label) {
case 0:
_b = (_a = context.rowStore).operate;
_c = ['modi'];
_d = {};
return [4 /*yield*/, generateNewId()];
case 1: return [2 /*return*/, _b.apply(_a, _c.concat([(_d.id = _e.sent(),
_d.action = 'apply',
_d.data = {},
_d.filter = filter,
_d.sorter = [
{
$attr: {
$$createAt$$: 1,
},
$direction: 'asc',
}
],
_d), context, {
dontCollect: true,
blockTrigger: true,
}]))];
}
});
});
}
exports.applyModis = applyModis;
function abandonModis(filter, context) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var _a, _b, _c;
var _d;
return tslib_1.__generator(this, function (_e) {
switch (_e.label) {
case 0:
_b = (_a = context.rowStore).operate;
_c = ['modi'];
_d = {};
return [4 /*yield*/, generateNewId()];
case 1: return [2 /*return*/, _b.apply(_a, _c.concat([(_d.id = _e.sent(),
_d.action = 'abadon',
_d.data = {},
_d.filter = filter,
_d.sorter = [
{
$attr: {
$$createAt$$: 1,
},
$direction: 'asc',
}
],
_d), context, {
dontCollect: true,
blockTrigger: true,
}]))];
}
});
});
}
exports.abandonModis = abandonModis;
function createModiRelatedCheckers(schema) {
var _this = this;
var checkers = [];
var _loop_1 = function (entity) {
var _a = schema[entity], actionType = _a.actionType, actions = _a.actions;
if (['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity) || ['readOnly', 'appendOnly'].includes(actionType)) {
return "continue";
}
var restActions = (0, lodash_1.difference)(actions, action_1.appendOnlyActions);
checkers.push({
entity: entity,
action: restActions,
type: 'row',
checker: function (_a, context) {
var operation = _a.operation;
return tslib_1.__awaiter(_this, void 0, void 0, function () {
var filter, filter2, count;
var _b;
return tslib_1.__generator(this, function (_c) {
switch (_c.label) {
case 0:
filter = operation.filter;
filter2 = {
modi: {
iState: 'active',
},
};
if (filter) {
Object.assign(filter2, (_b = {},
_b[entity] = filter,
_b));
}
else {
Object.assign(filter2, {
entity: entity,
});
}
return [4 /*yield*/, context.rowStore.count('modiEntity', {
filter: filter2,
}, context, {})];
case 1:
count = _c.sent();
if (count > 0) {
throw new types_1.OakRowLockedException();
}
return [2 /*return*/, 0];
}
});
});
},
});
};
for (var entity in schema) {
_loop_1(entity);
}
return checkers;
}
exports.createModiRelatedCheckers = createModiRelatedCheckers;

View File

@ -42,7 +42,9 @@ var triggers = [
action: action,
data: data,
filter: filter_1,
}, context, option)];
}, context, Object.assign({}, option, {
blockTrigger: true,
}))];
case 4:
_c.sent();
_c.label = 5;

View File

@ -13,13 +13,14 @@ export declare type Filter<A extends string, F extends Object | undefined = unde
};
export declare type SelectOption = {
dontCollect?: boolean;
ignoreTrigger?: true;
blockTrigger?: true;
obscure?: boolean;
forUpdate?: true;
includedDeleted?: true;
dummy?: 1;
};
export declare type OperateOption = {
blockTrigger?: true;
dontCollect?: boolean;
dontCreateOper?: boolean;
allowExists?: boolean;
@ -38,7 +39,6 @@ export declare type Operation<A extends GenericAction | string, DATA extends Obj
action: A;
data: DATA;
sorter?: SORTER;
option?: A extends 'select' ? SelectOption : undefined;
} & Filter<A, FILTER>;
export declare type Selection<DATA extends Object, FILTER extends Object | undefined = undefined, SORT extends Object | undefined = undefined> = Omit<Operation<'select', DATA, FILTER, SORT>, 'id'>;
export interface EntityShape {
@ -143,4 +143,5 @@ export declare type SelectRowShape<E extends GeneralEntityShape, P extends Deduc
export declare type SelectionResult<E extends GeneralEntityShape, P extends DeduceProjection<GeneralEntityShape>> = {
result: Array<SelectRowShape<E, P>>;
};
export declare type ActionType = 'readOnly' | 'appendOnly' | 'excludeUpdate' | 'crud';
export {};

View File

@ -42,6 +42,12 @@ export declare class OakUserUnpermittedException extends OakUserException {
export declare class OakUnloggedInException extends OakUserException {
constructor(message?: string);
}
/**
*
*/
export declare class OakRowLockedException extends OakUserException {
constructor(message?: string);
}
/**
*
*/

View File

@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeException = exports.OakCongruentRowExists = exports.OakUnloggedInException = exports.OakUserUnpermittedException = exports.OakInputIllegalException = exports.OakRowInconsistencyException = exports.OakUserException = exports.OakExternalException = exports.OakOperExistedException = exports.OakDataException = exports.OakException = void 0;
exports.makeException = exports.OakCongruentRowExists = exports.OakRowLockedException = exports.OakUnloggedInException = exports.OakUserUnpermittedException = exports.OakInputIllegalException = exports.OakRowInconsistencyException = exports.OakUserException = exports.OakExternalException = exports.OakOperExistedException = exports.OakDataException = exports.OakException = void 0;
var tslib_1 = require("tslib");
var OakException = /** @class */ (function (_super) {
tslib_1.__extends(OakException, _super);
@ -138,6 +138,18 @@ var OakUnloggedInException = /** @class */ (function (_super) {
}(OakUserException));
exports.OakUnloggedInException = OakUnloggedInException;
;
/**
* 用户未登录抛的异常
*/
var OakRowLockedException = /** @class */ (function (_super) {
tslib_1.__extends(OakRowLockedException, _super);
function OakRowLockedException(message) {
return _super.call(this, message || '该行数据正在被更新中,请稍后再试') || this;
}
return OakRowLockedException;
}(OakUserException));
exports.OakRowLockedException = OakRowLockedException;
;
/**
* 要插入行时发现已经有相同的行数据
*/
@ -188,6 +200,9 @@ function makeException(data) {
case OakCongruentRowExists.name: {
return new OakCongruentRowExists(data.data, data.message);
}
case OakRowLockedException.name: {
return new OakRowLockedException(data.message);
}
default:
return;
}

View File

@ -1,3 +1,4 @@
import { ActionType } from '.';
import { EntityDict, EntityShape, InstinctiveAttributes } from './Entity';
import { DataType, DataTypeParams } from './schema/DataTypes';
export declare type Ref = 'ref';
@ -42,6 +43,8 @@ export interface StorageDesc<SH extends EntityShape> {
indexes?: Index<SH>[];
config?: EntityConfig;
toModi?: true;
actions: string[];
actionType: ActionType;
view?: true;
}
export declare type StorageSchema<ED extends EntityDict> = {

View File

@ -14,4 +14,5 @@ import cloneDeep from 'lodash/cloneDeep';
import pick from 'lodash/pick';
import isEqual from 'lodash/isEqual';
import union from 'lodash/union';
export { unset, pull, uniq, get, set, intersection, omit, merge, cloneDeep, pick, isEqual, union, };
import difference from 'lodash/difference';
export { unset, pull, uniq, get, set, intersection, omit, merge, cloneDeep, pick, isEqual, union, difference, };

View File

@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.union = exports.isEqual = exports.pick = exports.cloneDeep = exports.merge = exports.omit = exports.intersection = exports.set = exports.get = exports.uniq = exports.pull = exports.unset = void 0;
exports.difference = exports.union = exports.isEqual = exports.pick = exports.cloneDeep = exports.merge = exports.omit = exports.intersection = exports.set = exports.get = exports.uniq = exports.pull = exports.unset = void 0;
var tslib_1 = require("tslib");
/**
* 避免lodash打包体积过大
@ -30,3 +30,5 @@ var isEqual_1 = tslib_1.__importDefault(require("lodash/isEqual"));
exports.isEqual = isEqual_1.default;
var union_1 = tslib_1.__importDefault(require("lodash/union"));
exports.union = union_1.default;
var difference_1 = tslib_1.__importDefault(require("lodash/difference"));
exports.difference = difference_1.default;

View File

@ -1,6 +1,13 @@
import { ActionDef } from '../types/Action';
export type GenericAction = 'create' | 'update' | 'remove' | 'select' | 'count' | 'stat' | 'download';
export const genericActions = ['create', 'update', 'remove', 'count', 'stat', 'download', 'select'];
export type ReadOnlyAction = 'select' | 'count' | 'stat' | 'download';
export type AppendOnlyAction = ReadOnlyAction | 'create';
export type ExcludeUpdateAction = AppendOnlyAction | 'remove';
export type GenericAction = 'update' | ExcludeUpdateAction;
export const readOnlyActions = ['count', 'stat', 'download', 'select'];
export const appendOnlyActions = readOnlyActions.concat('create');
export const excludeUpdateActions = appendOnlyActions.concat('remove');
export const genericActions = excludeUpdateActions.concat('update');
export type AbleAction = 'enable' | 'disable';
export type AbleState = 'enabled' | 'disabled';

7
src/checkers/index.ts Normal file
View File

@ -0,0 +1,7 @@
import { EntityDict } from '../base-app-domain';
import { createModiRelatedCheckers } from '../store/modi';
import { StorageSchema, EntityDict as BaseEntityDict, Context } from '../types';
export function createCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends Context<ED>>(schema: StorageSchema<ED>){
return createModiRelatedCheckers<ED, Cxt>(schema);
}

View File

@ -27,6 +27,7 @@ const Schema: Record<string, {
sourceFile: ts.SourceFile;
locale: ts.ObjectLiteralExpression;
toModi: boolean;
actionType: string;
}> = {};
const OneToMany: Record<string, Array<[string, string, boolean]>> = {};
const ManyToOne: Record<string, Array<[string, string, boolean]>> = {};
@ -285,7 +286,7 @@ const RESERVED_ACTION_NAMES = ['GenericAction', 'ParticularAction'];
import { genericActions } from '../actions/action';
import { unIndexedTypes } from '../types/DataType';
import { initinctiveAttributes } from '../types/Entity';
function dealWithActions(moduleName: string, filename: string, node: ts.TypeNode, program: ts.Program) {
function dealWithActions(moduleName: string, filename: string, node: ts.TypeNode, program: ts.Program, sourceFile: ts.SourceFile) {
const actionTexts = genericActions.map(
ele => ele
);
@ -343,8 +344,26 @@ function dealWithActions(moduleName: string, filename: string, node: ts.TypeNode
}
);
// 为每个action在schema中建立相应的state域(除了genericState)
// 放到actionDef的定义处去做。by Xc
pushStatementIntoActionAst(moduleName,
factory.createVariableStatement(
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
factory.createVariableDeclarationList(
[factory.createVariableDeclaration(
factory.createIdentifier("actions"),
undefined,
undefined,
factory.createArrayLiteralExpression(
actionTexts.map(
ele => factory.createStringLiteral(ele)
),
false
)
)],
ts.NodeFlags.Const
)
),
sourceFile
);
}
function getEntityImported(declaration: ts.ImportDeclaration, filename: string) {
@ -429,6 +448,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) {
let hasRelationDef = false;
let hasActionOrStateDef = false;
let toModi = false;
let actionType = 'crud';
const enumStringAttrs: string[] = [];
const states: string[] = [];
const localEnumStringTypes: string[] = [];
@ -635,7 +655,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) {
),
sourceFile!
);
dealWithActions(moduleName, filename, node.type, program);
dealWithActions(moduleName, filename, node.type, program, sourceFile!);
}
else if (node.name.text === 'Relation') {
assert(!localeDef, `${filename}】locale定义须在Relation之后`);
@ -685,6 +705,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) {
[relationEntityName]: {
schemaAttrs: relationSchemaAttrs,
sourceFile,
actionType: 'crud',
},
});
addRelationship(relationEntityName, 'User', 'user', true);
@ -916,12 +937,11 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) {
indexes = declaration.initializer;
}
else if (ts.isIdentifier(declaration.name) && declaration.name.text === 'locale') {
else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'LocaleDef') {
// locale定义
const { type, initializer } = declaration;
assert(ts.isObjectLiteralExpression(initializer!));
assert(ts.isTypeReferenceNode(type!) && ts.isIdentifier(type.typeName!) && type.typeName.text === 'LocaleDef', 'locale定义的类型必须是LocaleDef');
const { properties } = initializer;
assert(properties.length > 0, `${filename}至少需要有一种locale定义`);
@ -972,6 +992,10 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) {
localeDef = initializer;
}
else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'ActionType') {
assert(ts.isStringLiteral(declaration.initializer!));
actionType = declaration.initializer.text;
}
else {
throw new Error(`不能理解的定义内容${declaration.name.getText()}`);
}
@ -983,11 +1007,15 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) {
if (!hasActionDef && hasActionOrStateDef) {
throw new Error(`${filename}中有Action或State定义但没有定义完整的Action类型`);
}
if (hasActionDef && actionType !== 'crud') {
throw new Error(`${filename}中有Action定义但却定义了actionType不是crud`);
}
assert(schemaAttrs.length > 0);
const schema = {
schemaAttrs,
sourceFile,
toModi,
actionType,
};
if (hasFulltextIndex) {
assign(schema, {
@ -1510,17 +1538,17 @@ function constructFilter(statements: Array<ts.Statement>, entity: string) {
if (ReversePointerRelations[entity]) {
// 有反向指针将反向指针关联的对象的Filter也注入
ReversePointerRelations[entity].forEach(
ele =>
members.push(
factory.createPropertySignature(
undefined,
firstLetterLowerCase(ele),
undefined,
factory.createTypeReferenceNode(
createForeignRef(entity, ele, 'Filter')
ele =>
members.push(
factory.createPropertySignature(
undefined,
firstLetterLowerCase(ele),
undefined,
factory.createTypeReferenceNode(
createForeignRef(entity, ele, 'Filter')
)
)
)
)
);
}
const eumUnionTypeNode: ts.TypeNode[] = ReversePointerRelations[entity] && ReversePointerRelations[entity].map(
@ -4922,7 +4950,7 @@ function outputStorage(outputDir: string, printer: ts.Printer) {
for (const entity in Schema) {
const indexExpressions: ts.Expression[] = [];
const { sourceFile, fulltextIndex, indexes, toModi } = Schema[entity];
const { sourceFile, fulltextIndex, indexes, toModi, actionType } = Schema[entity];
const statements: ts.Statement[] = [
factory.createImportDeclaration(
undefined,
@ -4955,8 +4983,110 @@ function outputStorage(outputDir: string, printer: ts.Printer) {
undefined
)
];
switch (actionType) {
case 'readOnly': {
statements.push(
factory.createImportDeclaration(
undefined,
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([factory.createImportSpecifier(
false,
factory.createIdentifier("readOnlyActions"),
factory.createIdentifier("actions")
)])
),
factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()),
undefined
)
);
break;
}
case 'appendOnly': {
statements.push(
factory.createImportDeclaration(
undefined,
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([factory.createImportSpecifier(
false,
factory.createIdentifier("appendOnlyActions"),
factory.createIdentifier("actions")
)])
),
factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()),
undefined
)
);
break;
}
case 'excludeUpdate': {
statements.push(
factory.createImportDeclaration(
undefined,
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([factory.createImportSpecifier(
false,
factory.createIdentifier("excludeUpdateActions"),
factory.createIdentifier("actions")
)])
),
factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()),
undefined
)
);
break;
}
default: {
if (ActionAsts[entity]) {
statements.push(
factory.createImportDeclaration(
undefined,
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([factory.createImportSpecifier(
false,
undefined,
factory.createIdentifier("actions")
)])
),
factory.createStringLiteral("./Action"),
undefined
)
);
}
else {
statements.push(
factory.createImportDeclaration(
undefined,
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([factory.createImportSpecifier(
false,
factory.createIdentifier("genericActions"),
factory.createIdentifier("actions")
)])
),
factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()),
undefined
)
);
}
}
}
const propertyAssignments: ts.PropertyAssignment[] = [];
const propertyAssignments: (ts.PropertyAssignment | ts.ShorthandPropertyAssignment)[] = [];
const attributes = constructAttributes(entity);
propertyAssignments.push(
factory.createPropertyAssignment(
@ -4982,6 +5112,17 @@ function outputStorage(outputDir: string, printer: ts.Printer) {
);
}
propertyAssignments.push(
factory.createPropertyAssignment(
factory.createIdentifier("actionType"),
factory.createStringLiteral(actionType)
),
factory.createShorthandPropertyAssignment(
factory.createIdentifier("actions"),
undefined
)
);
if (indexExpressions.length > 0) {
propertyAssignments.push(
factory.createPropertyAssignment(

View File

@ -1,5 +1,5 @@
import { String } from '../types/DataType';
import { EntityShape } from '../types/Entity';
import { EntityShape, ActionType } from '../types/Entity';
import { LocaleDef } from '../types/Locale';
import { Schema as Modi } from './Modi';
@ -9,6 +9,7 @@ export interface Schema extends EntityShape {
entityId: String<64>;
};
const actionType: ActionType = 'appendOnly';
const locale: LocaleDef<Schema, '', '', {}> = {
zh_CN: {

View File

@ -1,5 +1,5 @@
import { String, Int, Datetime, Image, Boolean, Text } from '../types/DataType';
import { EntityShape } from '../types/Entity';
import { EntityShape, ActionType } from '../types/Entity';
import { LocaleDef } from '../types/Locale';
import { Schema as User } from './User';
@ -11,6 +11,8 @@ export interface Schema extends EntityShape {
operator?: User;
};
const actionType: ActionType = 'appendOnly';
const locale: LocaleDef<Schema, '', '', {}> = {
zh_CN: {
attr: {

View File

@ -1,5 +1,5 @@
import { String } from '../types/DataType';
import { EntityShape } from '../types/Entity';
import { EntityShape, ActionType } from '../types/Entity';
import { LocaleDef } from '../types/Locale';
import { Schema as Oper } from './Oper';
@ -9,6 +9,7 @@ export interface Schema extends EntityShape {
entityId: String<64>;
};
const actionType: ActionType = 'appendOnly';
const locale: LocaleDef<Schema, '', '', {}> = {
zh_CN: {

View File

@ -633,7 +633,7 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict, Cxt e
switch (action) {
case 'create': {
if (option.modiParentEntity && !['modi', 'modiEntity'].includes(entity as string)) {
if (option.modiParentEntity && !['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity as string)) {
// 变成对modi的插入
const modiCreate: CreateModiOperation = {
id: 'dummy',

View File

@ -1,5 +1,10 @@
import { OpSchema as Modi } from '../base-app-domain/Modi/Schema';
import { Operation } from '../types';
import { EntityDict } from '../base-app-domain';
import { UniversalContext } from '../store/UniversalContext';
import { OpSchema as Modi, Filter } from '../base-app-domain/Modi/Schema';
import { Checker, Operation, StorageSchema, UpdateChecker, EntityDict as BaseEntityDict, OakRowLockedException, Context } from '../types';
import { appendOnlyActions } from '../actions/action';
import { difference } from '../utils/lodash';
export function createOperationsFromModies(modies: Modi[]): Array<{
operation: Operation <string, Object, Object>,
entity: string,
@ -17,4 +22,93 @@ export function createOperationsFromModies(modies: Modi[]): Array<{
}
}
);
}
export async function applyModis<ED extends EntityDict, Cxt extends UniversalContext<ED>>(filter: Filter, context: Cxt) {
return context.rowStore.operate('modi', {
id: await generateNewId(),
action: 'apply',
data: {},
filter,
sorter: [
{
$attr: {
$$createAt$$: 1,
},
$direction: 'asc',
}
]
}, context, {
dontCollect: true,
blockTrigger: true,
});
}
export async function abandonModis<ED extends EntityDict, Cxt extends UniversalContext<ED>>(filter: Filter, context: Cxt) {
return context.rowStore.operate('modi', {
id: await generateNewId(),
action: 'abadon',
data: {},
filter,
sorter: [
{
$attr: {
$$createAt$$: 1,
},
$direction: 'asc',
}
]
}, context, {
dontCollect: true,
blockTrigger: true,
});
}
export function createModiRelatedCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends Context<ED>>(schema: StorageSchema<ED>) {
const checkers:Checker<ED, keyof ED, Cxt>[] = [];
for (const entity in schema) {
const { actionType, actions } = schema[entity];
if (['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity) || ['readOnly', 'appendOnly'].includes(actionType)) {
continue;
}
const restActions = difference(actions, appendOnlyActions);
checkers.push({
entity,
action: restActions as any,
type: 'row',
checker: async ({ operation }, context) => {
const { filter } = operation;
const filter2 = {
modi: {
iState: 'active',
},
};
if (filter) {
Object.assign(filter2, {
[entity]: filter
});
}
else {
Object.assign(filter2, {
entity,
});
}
const count = await context.rowStore.count(
'modiEntity',
{
filter: filter2 as any,
},
context,
{}
);
if (count > 0) {
throw new OakRowLockedException();
}
return 0;
},
} as UpdateChecker<ED, keyof ED, Cxt>)
}
return checkers;
}

View File

@ -28,7 +28,9 @@ const triggers: Trigger<EntityDict, 'modi', UniversalContext<EntityDict>>[] = [
action,
data,
filter: filter as any,
}, context, option);
}, context, Object.assign({}, option, {
blockTrigger: true,
}));
}
return modies.length;

View File

@ -17,7 +17,7 @@ export type Filter<A extends string, F extends Object | undefined = undefined> =
export type SelectOption = {
dontCollect?: boolean;
ignoreTrigger?: true;
blockTrigger?: true;
obscure?: boolean; // 如果为置为true则在filter过程中因数据不完整而不能判断为真的时候都假设为真前端缓存专用
forUpdate?: true;
includedDeleted?: true; // 是否包含删除行的信息
@ -25,6 +25,7 @@ export type SelectOption = {
};
export type OperateOption = {
blockTrigger?: true;
dontCollect?: boolean;
dontCreateOper?: boolean;
allowExists?: boolean; // 插入时允许已经存在唯一键值的行了即insert / update逻辑
@ -47,7 +48,6 @@ export type Operation<A extends GenericAction | string,
action: A;
data: DATA;
sorter?: SORTER;
option?: A extends 'select' ? SelectOption : undefined;
} & Filter<A, FILTER>;
export type Selection<DATA extends Object,
@ -204,3 +204,5 @@ export type SelectRowShape<E extends GeneralEntityShape, P extends DeduceProject
export type SelectionResult<E extends GeneralEntityShape, P extends DeduceProjection<GeneralEntityShape>> = {
result: Array<SelectRowShape<E, P>>;
}
export type ActionType = 'readOnly' | 'appendOnly' | 'excludeUpdate' | 'crud'; // 只读型、只插入型、没有更新型、所有型

View File

@ -108,6 +108,15 @@ export class OakUnloggedInException extends OakUserException {
}
};
/**
*
*/
export class OakRowLockedException extends OakUserException {
constructor(message?: string) {
super(message || '该行数据正在被更新中,请稍后再试');
}
};
/**
*
*/
@ -162,6 +171,9 @@ export function makeException(data: {
case OakCongruentRowExists.name: {
return new OakCongruentRowExists(data.data, data.message);
}
case OakRowLockedException.name: {
return new OakRowLockedException(data.message);
}
default:
return;
}

View File

@ -1,3 +1,4 @@
import { ActionType } from '.';
import { EntityDict, EntityShape, InstinctiveAttributes } from './Entity';
import { DataType, DataTypeParams } from './schema/DataTypes';
@ -52,7 +53,9 @@ export interface StorageDesc<SH extends EntityShape> {
uniqueConstraints?: UniqConstraint<SH>[];
indexes?: Index<SH>[];
config?: EntityConfig;
toModi?: true, // 标识一下是否关联在modi上
toModi?: true; // 标识一下是否关联在modi上
actions: string[];
actionType: ActionType;
// view 相关
view?: true;
}

View File

@ -14,6 +14,7 @@ import cloneDeep from 'lodash/cloneDeep';
import pick from 'lodash/pick';
import isEqual from 'lodash/isEqual';
import union from 'lodash/union';
import difference from 'lodash/difference';
export {
unset,
@ -28,4 +29,5 @@ export {
pick,
isEqual,
union,
difference,
};