Merge branch 'release'

This commit is contained in:
Xu Chang 2023-12-25 20:48:53 +08:00
commit f9aa003f1a
16 changed files with 832 additions and 35 deletions

View File

@ -54,9 +54,23 @@ class LocaleBuilder {
const statements = [
factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("CreateOperationData"), factory.createIdentifier("I18n"))])), factory.createStringLiteral("../oak-app-domain/I18n/Schema"), undefined)
];
if (this.dependencies) {
this.dependencies.forEach((ele, idx) => statements.push(factory.createImportDeclaration(undefined, factory.createImportClause(false, factory.createIdentifier(`i18ns${idx}`), undefined), factory.createStringLiteral(`${ele}/lib/data/i18n`), undefined)));
}
// 改为在初始化时合并
/* if (this.dependencies) {
this.dependencies.forEach(
(ele, idx) => statements.push(
factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
factory.createIdentifier(`i18ns${idx}`),
undefined
),
factory.createStringLiteral(`${ele}/lib/data/i18n`),
undefined
)
)
)
} */
statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier("i18ns"), undefined, factory.createArrayTypeNode(factory.createTypeReferenceNode(factory.createIdentifier("I18n"), undefined)), factory.createArrayLiteralExpression(Object.keys(this.locales).map((k) => {
const [module, position, language, data] = this.locales[k];
// 用哈希计算来保证id唯一性
@ -73,12 +87,27 @@ class LocaleBuilder {
factory.createPropertyAssignment(factory.createIdentifier("data"), transferObjectToObjectLiteral(data))
], true);
}), true))], ts.NodeFlags.Const)));
if (this.dependencies.length > 0) {
statements.push(factory.createExportAssignment(undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("i18ns"), factory.createIdentifier("concat")), undefined, this.dependencies.map((ele, idx) => factory.createIdentifier(`i18ns${idx}`)))));
}
else {
statements.push(factory.createExportAssignment(undefined, undefined, factory.createIdentifier("i18ns")));
/* if (this.dependencies.length > 0) {
statements.push(
factory.createExportAssignment(
undefined,
undefined,
factory.createCallExpression(
factory.createPropertyAccessExpression(
factory.createIdentifier("i18ns"),
factory.createIdentifier("concat")
),
undefined,
this.dependencies.map(
(ele, idx) => factory.createIdentifier(`i18ns${idx}`)
)
)
)
);
}
else { */
statements.push(factory.createExportAssignment(undefined, undefined, factory.createIdentifier("i18ns")));
/* } */
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const result = printer.printList(ts.ListFormat.SourceFileStatements, factory.createNodeArray(statements), ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS));
const filename = (0, path_1.join)(this.pwd, 'src', 'data', 'i18n.ts');

1
lib/compiler/routerBuilder.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare function buildRouter(projectDir: string, startupDir: string, watch?: boolean): void;

View File

@ -0,0 +1,257 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildRouter = void 0;
const tslib_1 = require("tslib");
const path_1 = require("path");
const fs_extra_1 = require("fs-extra");
const assert_1 = tslib_1.__importDefault(require("assert"));
const ts = tslib_1.__importStar(require("typescript"));
const node_watch_1 = tslib_1.__importDefault(require("node-watch"));
const { factory } = ts;
const NameSpaceDescDict = {};
function checkPageDir(dir, relativePath, ns, type) {
let changed = false;
const { pages } = NameSpaceDescDict[ns];
const subdirs = [];
const files = (0, fs_extra_1.readdirSync)(dir);
files.forEach((file) => {
const filepath = (0, path_1.join)(dir, file);
const stat = (0, fs_extra_1.statSync)(filepath);
if (stat.isFile() &&
['web.tsx', 'web.pc.tsx', 'render.native.tsx', 'render.ios.tsx', 'render.android.tsx', 'index.xml'].includes(file)) {
if (!pages.hasOwnProperty(dir)) {
const indexJsonFile = (0, path_1.join)(dir, 'index.json');
let oakDisablePulldownRefresh = false;
if ((0, fs_extra_1.existsSync)(indexJsonFile)) {
const { enablePullDownRefresh = true, } = require(indexJsonFile);
oakDisablePulldownRefresh =
!enablePullDownRefresh;
}
pages[dir] = {
path: relativePath.replace(/\\/g, '/'),
oakDisablePulldownRefresh,
hasNative: ['render.native.tsx', 'render.ios.tsx', 'render.android.tsx'].includes(file),
hasWeb: ['web.tsx', 'web.pc.tsx'].includes(file),
hasWechatMp: file === 'index.xml',
};
changed = true;
}
else {
if (['render.native.tsx', 'render.ios.tsx', 'render.android.tsx'].includes(file) && type === 'native') {
if (pages[dir].hasNative === false) {
pages[dir].hasNative = true;
changed = true;
}
}
else if (['web.tsx', 'web.pc.tsx'].includes(file) && type === 'web') {
if (pages[dir].hasWeb === false) {
pages[dir].hasWeb = true;
changed = true;
}
}
else {
if (pages[dir].hasWechatMp === false && type === 'wechatMp') {
pages[dir].hasWechatMp = true;
}
}
}
}
else if (stat.isDirectory()) {
subdirs.push(file);
}
});
return {
subdirs,
changed,
};
}
function traverseNsDir(nsDir, ns, type) {
NameSpaceDescDict[ns] = {
pages: {}
};
const { pages } = NameSpaceDescDict[ns];
const traverse = (dir, relativePath) => {
const { subdirs } = checkPageDir(dir, relativePath, ns, type);
subdirs.forEach((subdir) => {
const dir2 = (0, path_1.join)(dir, subdir);
const relativePath2 = (0, path_1.join)(relativePath, subdir);
traverse(dir2, relativePath2);
});
};
traverse(nsDir, '');
}
function traversePageDir(projectDir, type) {
const pageDir = (0, path_1.join)(projectDir, 'src', 'pages');
const namespaces = (0, fs_extra_1.readdirSync)(pageDir);
namespaces.forEach((ns) => {
const nsDir = (0, path_1.join)(pageDir, ns);
const stat = (0, fs_extra_1.statSync)(nsDir);
if (stat.isDirectory()) {
traverseNsDir(nsDir, ns, type);
}
});
}
function makeWebAllRouters(namespaceDir, projectDir, routerFileDir) {
const nss = (0, fs_extra_1.readdirSync)(namespaceDir);
return factory.createArrayLiteralExpression(nss.map((ns) => {
(0, assert_1.default)(NameSpaceDescDict[ns], `${ns}在pages下没有对应的目录`);
const { pages } = NameSpaceDescDict[ns];
const nsIndexJsonFile = (0, path_1.join)(namespaceDir, ns, 'index.json');
let path2 = `/${ns}`;
let notFound2 = '', first2 = '';
if ((0, fs_extra_1.existsSync)(nsIndexJsonFile)) {
const { path, notFound, first } = require(nsIndexJsonFile);
if (path) {
path2 = path.replace(/\\/g, '/');
}
if (notFound) {
notFound2 = notFound.replace(/\\/g, '/');
}
if (first) {
first2 = first.replace(/\\/g, '/');
if (first2.startsWith('/')) {
first2 = first2.slice(1);
}
}
}
const children = Object.values(pages).filter((ele) => ele.hasWeb).map(({ path, oakDisablePulldownRefresh }) => {
const properties = [
factory.createPropertyAssignment('path', factory.createStringLiteral(path)),
factory.createPropertyAssignment('namespace', factory.createStringLiteral(path2)),
factory.createPropertyAssignment('meta', factory.createObjectLiteralExpression([
factory.createPropertyAssignment('oakDisablePulldownRefresh', oakDisablePulldownRefresh ? factory.createTrue() : factory.createFalse())
])),
factory.createPropertyAssignment(factory.createIdentifier("Component"), factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("React"), factory.createIdentifier("lazy")), undefined, [factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createCallExpression(factory.createIdentifier('import'), undefined, [
factory.createStringLiteral((0, path_1.relative)(routerFileDir, (0, path_1.join)(projectDir, 'src', 'pages', ns, path)).replace(/\\/g, '/'))
]))]))
];
if (first2 === path) {
properties.push(factory.createPropertyAssignment('isFirst', factory.createTrue()));
}
return factory.createObjectLiteralExpression(properties, true);
});
if (notFound2) {
children.push(factory.createObjectLiteralExpression([
factory.createPropertyAssignment('path', factory.createStringLiteral('*')),
factory.createPropertyAssignment('namespace', factory.createStringLiteral(path2)),
factory.createPropertyAssignment(factory.createIdentifier("Component"), factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("React"), factory.createIdentifier("lazy")), undefined, [factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createCallExpression(factory.createIdentifier('import'), undefined, [
factory.createStringLiteral((0, path_1.relative)(routerFileDir, (0, path_1.join)(projectDir, 'src', 'pages', ns, notFound2)).replace(/\\/g, '/'))
]))]))
], true));
}
return factory.createObjectLiteralExpression([
factory.createPropertyAssignment('path', factory.createStringLiteral(path2)),
factory.createPropertyAssignment('namespace', factory.createStringLiteral(path2)),
factory.createPropertyAssignment(factory.createIdentifier("Component"), factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("React"), factory.createIdentifier("lazy")), undefined, [factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createCallExpression(factory.createIdentifier('import'), undefined, [
factory.createStringLiteral((0, path_1.relative)(routerFileDir, (0, path_1.join)(namespaceDir, ns)).replace(/\\/g, '/'))
]))])),
factory.createPropertyAssignment('children', factory.createArrayLiteralExpression(children))
], true);
}), true);
}
function judgeUseOakRouterBuilder(statements) {
const stmt = statements[0];
return ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression) && stmt.expression.text === 'use oak router builder';
}
function outputInWebAppDir(appDir) {
const routerFileName = (0, path_1.join)(appDir, 'router', 'allRouters.ts');
if ((0, fs_extra_1.existsSync)(routerFileName)) {
const program = ts.createProgram([routerFileName], {
removeComments: false,
});
const routerFile = program.getSourceFile(routerFileName);
(0, assert_1.default)(routerFile);
const namespaceDir = (0, path_1.join)(appDir, 'namespaces');
const { statements } = routerFile;
if (judgeUseOakRouterBuilder(statements)) {
statements.forEach((statement) => {
if (ts.isVariableStatement(statement)) {
const declaration = statement.declarationList.declarations.find(declaration => ts.isIdentifier(declaration.name) && declaration.name.text === 'allRouters');
if (declaration) {
Object.assign(declaration, {
initializer: makeWebAllRouters(namespaceDir, (0, path_1.join)(appDir, '../../../..'), (0, path_1.dirname)(routerFileName))
});
}
}
});
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, removeComments: false });
const result = printer.printNode(ts.EmitHint.Unspecified, routerFile, routerFile);
(0, fs_extra_1.writeFileSync)(routerFileName, result);
}
}
else {
console.warn(`${appDir}的目录结构未按照标准建立,缺少了${routerFileName}`);
}
}
function outputInWebDir(dir) {
const srcAppDir = (0, path_1.join)(dir, 'src', 'app');
const apps = (0, fs_extra_1.readdirSync)(srcAppDir);
apps.forEach((app) => {
const appDir = (0, path_1.join)(srcAppDir, app);
const stat = (0, fs_extra_1.statSync)(appDir);
if (stat.isDirectory()) {
outputInWebAppDir(appDir);
}
});
}
function watchDir(projectDir, startupDir, type) {
const srcPageDir = (0, path_1.join)(projectDir, 'src', 'pages');
console.log('watch dir ', srcPageDir);
if (startupDir.startsWith('web')) {
const srcAppDir = (0, path_1.join)(projectDir, startupDir, 'src', 'app');
const apps = (0, fs_extra_1.readdirSync)(srcAppDir);
const tryOutputAppDir = (ns) => {
apps.forEach((app) => {
const appDir = (0, path_1.join)(srcAppDir, app);
const namespaceDir = (0, path_1.join)(appDir, 'namespaces');
const namespaces = (0, fs_extra_1.readdirSync)(namespaceDir);
if (namespaces.includes(ns)) {
outputInWebAppDir(appDir);
}
});
};
(0, node_watch_1.default)(srcPageDir, {
recursive: true,
filter: new RegExp('web\.tsx|web\.pc\.tsx|index\.xml|render\.(native|ios|android)\.tsx'),
}, (evt, filepath) => {
const dir = (0, path_1.dirname)(filepath);
const relativeDir = (0, path_1.relative)((0, path_1.join)(projectDir, 'src', 'pages'), filepath);
const ns = relativeDir.split('\\')[0];
const relativePath = (0, path_1.relative)(ns, (0, path_1.dirname)(relativeDir));
const { pages } = NameSpaceDescDict[ns];
console.log(filepath, dir, ns);
if (evt === 'remove') {
if ((0, fs_extra_1.existsSync)(dir)) {
const { changed } = checkPageDir(dir, relativePath, ns, type);
if (changed) {
tryOutputAppDir(ns);
}
}
else {
delete pages[dir];
tryOutputAppDir(ns);
}
}
else {
const { changed } = checkPageDir(dir, relativePath, ns, type);
if (changed) {
tryOutputAppDir(ns);
}
}
});
}
}
function buildRouter(projectDir, startupDir, watch) {
const type = startupDir.startsWith('web') ? 'web' : (startupDir.startsWith('native') ? 'native' : 'wechatMp');
traversePageDir(projectDir, type);
const subDir = (0, fs_extra_1.readdirSync)(projectDir);
(0, assert_1.default)(subDir.includes(startupDir));
if (startupDir.startsWith('web')) {
outputInWebDir((0, path_1.join)(projectDir, startupDir));
}
// todo native
if (watch) {
watchDir(projectDir, startupDir, type);
}
}
exports.buildRouter = buildRouter;

View File

@ -18,9 +18,10 @@ export declare abstract class CascadeStore<ED extends EntityDict & BaseEntityDic
registerOperationRewriter(rewriter: OperationRewriter<ED, AsyncContext<ED> | SyncContext<ED>, OperateOption>): void;
registerSelectionRewriter(rewriter: SelectionRewriter<ED, AsyncContext<ED> | SyncContext<ED>, SelectOption>): void;
protected abstract selectAbjointRow<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Partial<ED[T]['Schema']>[];
protected abstract countAbjointRow<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: OP): number;
protected abstract countAbjointRowAsync<T extends keyof ED, OP extends SelectOption, Cxt extends AsyncContext<ED>>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: OP): Promise<number>;
protected abstract updateAbjointRow<T extends keyof ED, OP extends OperateOption, Cxt extends SyncContext<ED>>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): number;
protected abstract selectAbjointRowAsync<T extends keyof ED, OP extends SelectOption, Cxt extends AsyncContext<ED>>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Promise<Partial<ED[T]['Schema']>[]>;
protected abstract countAsync<T extends keyof ED, OP extends SelectOption, Cxt extends AsyncContext<ED>>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: OP): Promise<number>;
protected abstract updateAbjointRowAsync<T extends keyof ED, OP extends OperateOption, Cxt extends AsyncContext<ED>>(entity: T, operation: ED[T]['Create'] | ED[T]['Update'] | ED[T]['Remove'], context: Cxt, option: OP): Promise<number>;
protected abstract aggregateAbjointRowSync<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(entity: T, aggregation: ED[T]['Aggregation'], context: Cxt, option: OP): AggregationResult<ED[T]['Schema']>;
protected abstract aggregateAbjointRowAsync<T extends keyof ED, OP extends SelectOption, Cxt extends AsyncContext<ED>>(entity: T, aggregation: ED[T]['Aggregation'], context: Cxt, option: OP): Promise<AggregationResult<ED[T]['Schema']>>;
@ -103,4 +104,6 @@ export declare abstract class CascadeStore<ED extends EntityDict & BaseEntityDic
protected selectSync<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Partial<ED[T]['Schema']>[];
protected operateSync<T extends keyof ED, Cxt extends SyncContext<ED>, OP extends OperateOption>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult<ED>;
protected operateAsync<T extends keyof ED, Cxt extends AsyncContext<ED>, OP extends OperateOption>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): Promise<OperationResult<ED>>;
protected countSync<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: OP): number;
protected countAsync<T extends keyof ED, OP extends SelectOption, Cxt extends AsyncContext<ED>>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: OP): Promise<number>;
}

View File

@ -34,7 +34,7 @@ class CascadeStore extends RowStore_1.RowStore {
this.reinforceSelectionInner(entity, selection, context);
}
this.selectionRewriters.forEach(ele => {
const result = ele(this.getSchema(), entity, selection, context, option);
const result = ele(this.getSchema(), entity, selection, context, option, isAggr);
(0, assert_1.default)(!(result instanceof Promise));
});
}
@ -1736,5 +1736,13 @@ class CascadeStore extends RowStore_1.RowStore {
await this.reinforceOperation(entity, operation, context, option);
return this.cascadeUpdateAsync(entity, operation, context, option);
}
countSync(entity, selection, context, option) {
this.reinforceSelectionSync(entity, selection, context, option, true); // 这样写可能有问题的虽然能跳过本地的projection补全但如果有更多的selectionRewriter注入可能会出问题。by Xc 20231220
return this.countAbjointRow(entity, selection, context, option);
}
countAsync(entity, selection, context, option) {
this.reinforceSelectionAsync(entity, selection, context, option, true); // 这样写可能有问题的虽然能跳过本地的projection补全但如果有更多的selectionRewriter注入可能会出问题。by Xc 20231220
return this.countAbjointRowAsync(entity, selection, context, option);
}
}
exports.CascadeStore = CascadeStore;

View File

@ -452,7 +452,7 @@ class TriggerExecutor {
const rs = grouped[uuid];
const { [Entity_1.TriggerDataAttribute]: triggerData } = rs[0];
const { name, cxtStr, option } = triggerData;
await context.initialize(JSON.parse(cxtStr));
// await context.initialize(JSON.parse(cxtStr)); // 这里token有可能过期用户注销先用root态模拟吧
await this.execVolatileTrigger(entity, name, rs.map(ele => ele.id), context, option);
}
await context.commit();

View File

@ -146,7 +146,7 @@ function makeIntrinsicCTWs(schema, actionDefDict) {
action: 'create',
type: 'data',
entity,
priority: 10,
priority: 10, // 优先级要高先于真正的data检查进行
checker: (data) => {
if (data instanceof Array) {
data.forEach(ele => {
@ -181,7 +181,7 @@ function makeIntrinsicCTWs(schema, actionDefDict) {
entity,
action: 'create',
type: 'logicalData',
priority: types_1.CHECKER_MAX_PRIORITY,
priority: types_1.CHECKER_MAX_PRIORITY, // 优先级要放在最低所有前置的checker/trigger将数据完整之后再在这里检测
checker: (operation, context) => {
const { data } = operation;
if (data instanceof Array) {
@ -197,9 +197,9 @@ function makeIntrinsicCTWs(schema, actionDefDict) {
}
}, {
entity,
action: 'update',
action: 'update', // 只检查update其它状态转换的action应该不会涉及unique约束的属性
type: 'logicalData',
priority: types_1.CHECKER_MAX_PRIORITY,
priority: types_1.CHECKER_MAX_PRIORITY, // 优先级要放在最低所有前置的checker/trigger将数据完整之后再在这里检测
checker: (operation, context) => {
const { data, filter: operationFilter } = operation;
if (data) {

View File

@ -65,7 +65,7 @@ class SimpleConnector {
}
catch (err) {
// fetch返回异常一定是网络异常
throw new types_1.OakNetworkException();
throw new types_1.OakNetworkException(`请求[${this.serverAspectUrl}],发生网络异常`);
}
if (response.status > 299) {
const err = new types_1.OakServerProxyException(`网络请求返回status是${response.status}`);

View File

@ -1,3 +1,4 @@
/// <reference types="node" />
/**
* assert打包体积过大
*/

View File

@ -1,6 +1,6 @@
{
"name": "oak-domain",
"version": "4.0.1",
"version": "4.0.2",
"author": {
"name": "XuChang"
},
@ -37,6 +37,7 @@
"fs-extra": "^10.0.0",
"lodash": "^4.17.21",
"mocha": "^8.2.1",
"node-watch": "^0.7.4",
"ts-node": "^10.9.1",
"tslib": "^2.4.0",
"typescript": "^5.2.2"

View File

@ -80,7 +80,8 @@ export default class LocaleBuilder {
)
];
if (this.dependencies) {
// 改为在初始化时合并
/* if (this.dependencies) {
this.dependencies.forEach(
(ele, idx) => statements.push(
factory.createImportDeclaration(
@ -95,7 +96,7 @@ export default class LocaleBuilder {
)
)
)
}
} */
statements.push(
factory.createVariableStatement(
@ -159,7 +160,7 @@ export default class LocaleBuilder {
),
);
if (this.dependencies.length > 0) {
/* if (this.dependencies.length > 0) {
statements.push(
factory.createExportAssignment(
undefined,
@ -177,7 +178,7 @@ export default class LocaleBuilder {
)
);
}
else {
else { */
statements.push(
factory.createExportAssignment(
undefined,
@ -185,7 +186,7 @@ export default class LocaleBuilder {
factory.createIdentifier("i18ns")
)
);
}
/* } */
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const result = printer.printList(

View File

@ -0,0 +1,469 @@
import { join, relative, dirname } from 'path';
import { readdirSync, statSync, existsSync, writeFileSync } from 'fs-extra';
import assert from 'assert';
import * as ts from 'typescript';
import NodeWatch from 'node-watch';
const { factory } = ts;
/**
* pages下的目录结构web/native工程下的router以及pageMap
* wechatMp暂不处理
*
*
* -src
* --pages
* ---${namespace1}
* -----${page1}
* -------${subPage1.1}
* -------${subPage1.2}
* -----${page2}
* ---${namespace2}
* -----${page3}
*
* -web
* --src
* ---${appName}
* -----namespaces
* -------${namespace1}
* ---------index.json namespace下的配置
* ---------pageMap.json pageMap注入到这里
* -------${namespace2}
* -----router
* ---------index.ts router.ts注入到这里
*
* -native
* --namspaces
* ----${namespace1}
* -------index.json namespace下的配置
* -------pageMap.json pageMap注入到这里
* --router
* ----index.ts router.ts注入到这里
*
*/
type PageDesc = {
path: string;
oakDisablePulldownRefresh: boolean;
hasWeb: boolean;
hasNative: boolean;
hasWechatMp: boolean;
}
type NamespaceDesc = {
pages: Record<string, PageDesc>;
}
const NameSpaceDescDict: Record<string, NamespaceDesc> = {};
function checkPageDir(dir: string, relativePath: string, ns: string, type: 'native' | 'web' | 'wechatMp') {
let changed = false;
const { pages } = NameSpaceDescDict[ns];
const subdirs: string[] = [];
const files = readdirSync(dir);
files.forEach((file) => {
const filepath = join(dir, file);
const stat = statSync(filepath);
if (stat.isFile() &&
['web.tsx', 'web.pc.tsx', 'render.native.tsx', 'render.ios.tsx', 'render.android.tsx', 'index.xml'].includes(
file
)) {
if (!pages.hasOwnProperty(dir)) {
const indexJsonFile = join(dir, 'index.json');
let oakDisablePulldownRefresh = false;
if (existsSync(indexJsonFile)) {
const {
enablePullDownRefresh = true,
} = require(indexJsonFile);
oakDisablePulldownRefresh =
!enablePullDownRefresh;
}
pages[dir] = {
path: relativePath.replace(/\\/g, '/'),
oakDisablePulldownRefresh,
hasNative: ['render.native.tsx', 'render.ios.tsx', 'render.android.tsx'].includes(file),
hasWeb: ['web.tsx', 'web.pc.tsx'].includes(file),
hasWechatMp: file === 'index.xml',
};
changed = true;
}
else {
if (['render.native.tsx', 'render.ios.tsx', 'render.android.tsx'].includes(file) && type === 'native') {
if (pages[dir].hasNative === false) {
pages[dir].hasNative = true;
changed = true;
}
}
else if (['web.tsx', 'web.pc.tsx'].includes(file) && type === 'web') {
if (pages[dir].hasWeb === false) {
pages[dir].hasWeb = true;
changed = true;
}
}
else {
if (pages[dir].hasWechatMp === false && type === 'wechatMp') {
pages[dir].hasWechatMp = true;
}
}
}
} else if (stat.isDirectory()) {
subdirs.push(file);
}
});
return {
subdirs,
changed,
};
}
function traverseNsDir(nsDir: string, ns: string, type: 'native' | 'web' | 'wechatMp') {
NameSpaceDescDict[ns] = {
pages: {}
};
const { pages } = NameSpaceDescDict[ns];
const traverse = (dir: string, relativePath: string) => {
const { subdirs } = checkPageDir(dir, relativePath, ns, type);
subdirs.forEach(
(subdir) => {
const dir2 = join(dir, subdir);
const relativePath2 = join(relativePath, subdir);
traverse(dir2, relativePath2);
}
);
};
traverse(nsDir, '');
}
function traversePageDir(projectDir: string, type: 'native' | 'web' | 'wechatMp') {
const pageDir = join(projectDir, 'src', 'pages');
const namespaces = readdirSync(pageDir);
namespaces.forEach(
(ns) => {
const nsDir = join(pageDir, ns);
const stat = statSync(nsDir);
if (stat.isDirectory()) {
traverseNsDir(nsDir, ns, type);
}
}
);
}
function makeWebAllRouters(namespaceDir: string, projectDir: string, routerFileDir: string) {
const nss = readdirSync(namespaceDir);
return factory.createArrayLiteralExpression(
nss.map(
(ns) => {
assert(NameSpaceDescDict[ns], `${ns}在pages下没有对应的目录`);
const { pages } = NameSpaceDescDict[ns];
const nsIndexJsonFile = join(namespaceDir, ns, 'index.json');
let path2 = `/${ns}`;
let notFound2 = '', first2 = '';
if (existsSync(nsIndexJsonFile)) {
const { path, notFound, first } = require(nsIndexJsonFile);
if (path) {
path2 = path.replace(/\\/g, '/');
}
if (notFound) {
notFound2 = notFound.replace(/\\/g, '/');
}
if (first) {
first2 = first.replace(/\\/g, '/');
if (first2.startsWith('/')) {
first2 = first2.slice(1);
}
}
}
const children = Object.values(pages).filter(
(ele) => ele.hasWeb
).map(
({ path, oakDisablePulldownRefresh }) => {
const properties = [
factory.createPropertyAssignment(
'path',
factory.createStringLiteral(path)
),
factory.createPropertyAssignment(
'namespace',
factory.createStringLiteral(path2)
),
factory.createPropertyAssignment(
'meta',
factory.createObjectLiteralExpression(
[
factory.createPropertyAssignment(
'oakDisablePulldownRefresh',
oakDisablePulldownRefresh ? factory.createTrue() : factory.createFalse()
)
]
)
),
factory.createPropertyAssignment(
factory.createIdentifier("Component"),
factory.createCallExpression(
factory.createPropertyAccessExpression(
factory.createIdentifier("React"),
factory.createIdentifier("lazy")
),
undefined,
[factory.createArrowFunction(
undefined,
undefined,
[],
undefined,
factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
factory.createCallExpression(
factory.createIdentifier('import'),
undefined,
[
factory.createStringLiteral(
relative(routerFileDir, join(projectDir, 'src', 'pages', ns, path)).replace(/\\/g, '/')
)
]
)
)]
)
)
];
if (first2 === path) {
properties.push(
factory.createPropertyAssignment(
'isFirst',
factory.createTrue()
)
)
}
return factory.createObjectLiteralExpression(
properties,
true,
);
}
);
if (notFound2) {
children.push(
factory.createObjectLiteralExpression(
[
factory.createPropertyAssignment(
'path',
factory.createStringLiteral('*')
),
factory.createPropertyAssignment(
'namespace',
factory.createStringLiteral(path2)
),
factory.createPropertyAssignment(
factory.createIdentifier("Component"),
factory.createCallExpression(
factory.createPropertyAccessExpression(
factory.createIdentifier("React"),
factory.createIdentifier("lazy")
),
undefined,
[factory.createArrowFunction(
undefined,
undefined,
[],
undefined,
factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
factory.createCallExpression(
factory.createIdentifier('import'),
undefined,
[
factory.createStringLiteral(
relative(routerFileDir, join(projectDir, 'src', 'pages', ns, notFound2)).replace(/\\/g, '/')
)
]
)
)]
)
)
],
true
)
)
}
return factory.createObjectLiteralExpression(
[
factory.createPropertyAssignment(
'path',
factory.createStringLiteral(path2)
),
factory.createPropertyAssignment(
'namespace',
factory.createStringLiteral(path2)
),
factory.createPropertyAssignment(
factory.createIdentifier("Component"),
factory.createCallExpression(
factory.createPropertyAccessExpression(
factory.createIdentifier("React"),
factory.createIdentifier("lazy")
),
undefined,
[factory.createArrowFunction(
undefined,
undefined,
[],
undefined,
factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
factory.createCallExpression(
factory.createIdentifier('import'),
undefined,
[
factory.createStringLiteral(
relative(routerFileDir, join(namespaceDir, ns)).replace(/\\/g, '/')
)
]
)
)]
)
),
factory.createPropertyAssignment(
'children',
factory.createArrayLiteralExpression(
children
)
)
],
true
)
}
),
true
);
}
function judgeUseOakRouterBuilder(statements: ts.NodeArray<ts.Statement>) {
const stmt = statements[0];
return ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression) && stmt.expression.text === 'use oak router builder';
}
function outputInWebAppDir(appDir: string) {
const routerFileName = join(appDir, 'router', 'allRouters.ts');
if (existsSync(routerFileName)) {
const program = ts.createProgram([routerFileName], {
removeComments: false,
});
const routerFile = program.getSourceFile(routerFileName);
assert(routerFile);
const namespaceDir = join(appDir, 'namespaces');
const { statements } = routerFile;
if (judgeUseOakRouterBuilder(statements)) {
statements.forEach(
(statement) => {
if (ts.isVariableStatement(statement)) {
const declaration = statement.declarationList.declarations.find(
declaration => ts.isIdentifier(declaration.name) && declaration.name.text === 'allRouters'
);
if (declaration) {
Object.assign(declaration, {
initializer: makeWebAllRouters(namespaceDir, join(appDir, '../../../..'), dirname(routerFileName))
});
}
}
}
);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, removeComments: false });
const result = printer.printNode(
ts.EmitHint.Unspecified,
routerFile,
routerFile,
);
writeFileSync(routerFileName, result);
}
}
else {
console.warn(`${appDir}的目录结构未按照标准建立,缺少了${routerFileName}`);
}
}
function outputInWebDir(dir: string) {
const srcAppDir = join(dir, 'src', 'app');
const apps = readdirSync(srcAppDir);
apps.forEach(
(app) => {
const appDir = join(srcAppDir, app);
const stat = statSync(appDir);
if (stat.isDirectory()) {
outputInWebAppDir(appDir);
}
}
)
}
function watchDir(projectDir: string, startupDir: string, type: 'native' | 'web' | 'wechatMp') {
const srcPageDir = join(projectDir, 'src', 'pages');
console.log('watch dir ', srcPageDir);
if (startupDir.startsWith('web')) {
const srcAppDir = join(projectDir, startupDir, 'src', 'app');
const apps = readdirSync(srcAppDir);
const tryOutputAppDir = (ns: string) => {
apps.forEach(
(app) => {
const appDir = join(srcAppDir, app);
const namespaceDir = join(appDir, 'namespaces');
const namespaces = readdirSync(namespaceDir);
if (namespaces.includes(ns)) {
outputInWebAppDir(appDir);
}
}
);
}
NodeWatch(srcPageDir, {
recursive: true,
filter: new RegExp('web\.tsx|web\.pc\.tsx|index\.xml|render\.(native|ios|android)\.tsx'),
}, (evt, filepath) => {
const dir = dirname(filepath);
const relativeDir = relative(join(projectDir, 'src', 'pages'), filepath);
const ns = relativeDir.split('\\')[0];
const relativePath = relative(ns, dirname(relativeDir));
const { pages } = NameSpaceDescDict[ns];
console.log(filepath, dir, ns);
if (evt === 'remove') {
if (existsSync(dir)) {
const { changed } = checkPageDir(dir, relativePath, ns, type);
if (changed) {
tryOutputAppDir(ns);
}
}
else {
delete pages[dir];
tryOutputAppDir(ns);
}
}
else {
const { changed } = checkPageDir(dir, relativePath, ns, type);
if (changed) {
tryOutputAppDir(ns);
}
}
});
}
}
export function buildRouter(projectDir: string, startupDir: string, watch?: boolean) {
const type = startupDir.startsWith('web') ? 'web' : (startupDir.startsWith('native') ? 'native' : 'wechatMp');
traversePageDir(projectDir, type);
const subDir = readdirSync(projectDir);
assert(subDir.includes(startupDir));
if (startupDir.startsWith('web')) {
outputInWebDir(join(projectDir, startupDir));
}
// todo native
if (watch) {
watchDir(projectDir, startupDir, type);
}
}

View File

@ -62,7 +62,7 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
this.selectionRewriters.forEach(
ele => {
const result = ele(this.getSchema(), entity, selection, context, option);
const result = ele(this.getSchema(), entity, selection, context, option, isAggr);
assert(!(result instanceof Promise));
}
);
@ -377,7 +377,6 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
$$createAt$$: 1,
});
}
}
private async reinforceOperation<Cxt extends AsyncContext<ED>, Op extends OperateOption>(
@ -403,6 +402,19 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
selection: ED[T]['Selection'],
context: Cxt,
option: OP): Partial<ED[T]['Schema']>[];
protected abstract countAbjointRow<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(
entity: T,
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
context: Cxt,
option: OP): number;
protected abstract countAbjointRowAsync<T extends keyof ED, OP extends SelectOption, Cxt extends AsyncContext<ED>>(
entity: T,
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
context: Cxt,
option: OP): Promise<number>;
protected abstract updateAbjointRow<T extends keyof ED, OP extends OperateOption, Cxt extends SyncContext<ED>>(
entity: T,
@ -416,13 +428,6 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
context: Cxt,
option: OP): Promise<Partial<ED[T]['Schema']>[]>;
protected abstract countAsync<T extends keyof ED, OP extends SelectOption, Cxt extends AsyncContext<ED>>(
entity: T,
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
context: Cxt,
option: OP
): Promise<number>;
protected abstract updateAbjointRowAsync<T extends keyof ED, OP extends OperateOption, Cxt extends AsyncContext<ED>>(
entity: T,
operation: ED[T]['Create'] | ED[T]['Update'] | ED[T]['Remove'],
@ -2169,4 +2174,22 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
await this.reinforceOperation(entity, operation, context, option);
return this.cascadeUpdateAsync(entity, operation, context, option);
}
protected countSync<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(
entity: T,
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
context: Cxt,
option: OP) {
this.reinforceSelectionSync(entity, selection as ED[T]['Selection'], context, option, true); // 这样写可能有问题的虽然能跳过本地的projection补全但如果有更多的selectionRewriter注入可能会出问题。by Xc 20231220
return this.countAbjointRow(entity, selection as ED[T]['Selection'], context, option);
}
protected countAsync<T extends keyof ED, OP extends SelectOption, Cxt extends AsyncContext<ED>>(
entity: T,
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
context: Cxt,
option: OP) {
this.reinforceSelectionAsync(entity, selection as ED[T]['Selection'], context, option, true); // 这样写可能有问题的虽然能跳过本地的projection补全但如果有更多的selectionRewriter注入可能会出问题。by Xc 20231220
return this.countAbjointRowAsync(entity, selection as ED[T]['Selection'], context, option);
}
}

View File

@ -563,7 +563,7 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
const rs = grouped[uuid];
const { [TriggerDataAttribute]: triggerData } = rs[0];
const { name, cxtStr, option } = triggerData!;
await context.initialize(JSON.parse(cxtStr));
// await context.initialize(JSON.parse(cxtStr)); // 这里token有可能过期用户注销先用root态模拟吧
await this.execVolatileTrigger(entity, name, rs.map(ele => ele.id!), context, option);
}
await context.commit();

View File

@ -83,7 +83,7 @@ export class SimpleConnector<ED extends EntityDict, FrontCxt extends SyncContext
});
} catch (err) {
// fetch返回异常一定是网络异常
throw new OakNetworkException();
throw new OakNetworkException(`请求[${this.serverAspectUrl}],发生网络异常`);
}
if (response.status > 299) {
const err = new OakServerProxyException(

View File

@ -1,8 +1,12 @@
import { generateNewId } from '../src/utils/uuid';
/* import { generateNewId } from '../src/utils/uuid';
let iter = 20;
while (iter > 0) {
console.log(generateNewId());
iter --;
}
} */
import { join } from 'path';
import { buildRouter } from '../src/compiler/routerBuilder';
buildRouter(join(process.cwd(), '..', 'taicang'), 'web', true);