diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..dc51819f9 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,29 @@ +module.exports = { + "env": { + "browser": true, + "es2021": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-control-regex": "off", + "@typescript-eslint/no-unsafe-finally": "off", + } +} diff --git a/.gitignore b/.gitignore index 97c33bf29..31e7346b5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,11 @@ es2015 fesm2015 fesm5 package-lock.json - +yarn.lock +log-caches +log +myipctmp +simples/data +big-temp.json +**/*.apk diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..32748be8e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,566 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Core Sample", + "args": [ + "${workspaceRoot}/simples/core/main.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "Launch Boot Sample", + "args": [ + "${workspaceRoot}/simples/boot/main.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test ioc", + "args": [ + "${workspaceRoot}/packages/ioc/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test aop", + "args": [ + "${workspaceRoot}/packages/aop/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test logger", + "args": [ + "${workspaceRoot}/packages/logger/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test core", + "args": [ + "${workspaceRoot}/packages/core/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test common http", + "args": [ + "${workspaceRoot}/packages/common/http/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test amqp service", + "args": [ + "${workspaceRoot}/packages/services/amqp/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test coap service", + "args": [ + "${workspaceRoot}/packages/services/coap/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test http service", + "args": [ + "${workspaceRoot}/packages/services/http/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test kafka service", + "args": [ + "${workspaceRoot}/packages/services/kafka/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test mqtt service", + "args": [ + "${workspaceRoot}/packages/services/mqtt/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test nats service", + "args": [ + "${workspaceRoot}/packages/services/nats/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test redis service", + "args": [ + "${workspaceRoot}/packages/services/redis/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test tcp service", + "args": [ + "${workspaceRoot}/packages/services/tcp/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test udp service", + "args": [ + "${workspaceRoot}/packages/services/udp/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test ws service", + "args": [ + "${workspaceRoot}/packages/services/ws/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test boot", + "args": [ + "${workspaceRoot}/packages/boot/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test platform-browser", + "args": [ + "${workspaceRoot}/packages/platform-browser/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test platform-server", + "args": [ + "${workspaceRoot}/packages/platform-server/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test typeorm-adapter", + "args": [ + "${workspaceRoot}/packages/typeorm-adapter/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test unit", + "args": [ + "${workspaceRoot}/packages/unit/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test compiler", + "args": [ + "${workspaceRoot}/packages/compiler/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test swagger", + "args": [ + "${workspaceRoot}/packages/swagger/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test components", + "args": [ + "${workspaceRoot}/packages/components/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test xml components", + "args": [ + "${workspaceRoot}/packages/components/xml/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test json components", + "args": [ + "${workspaceRoot}/packages/components/json/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "test activities", + "args": [ + "${workspaceRoot}/packages/activities/unit.ts" + ], + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register" + ], + "sourceMaps": true, + "cwd": "${workspaceRoot}", + + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 81a8778ce..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "deno.enable": false -} \ No newline at end of file diff --git a/README.md b/README.md index 1924bb0b8..97a14d22f 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,39 @@ -# packaged @tsdi/ioc +tsioc is application frameworks. -This repo is for distribution on `npm`. The source for this module is in the -[main repo](https://github.com/zhouhoujun/tsioc). +tsioc - TypeScript IoC Framework Overview -`@tsdi/ioc` is Ioc container, Injector, via typescript decorator. +# Core Architecture +This is a comprehensive TypeScript framework called "tsioc" (TypeScript IoC) that provides: -version 5+ of [`@ts-ioc/core`](https://www.npmjs.com/package/@ts-ioc/core) [`tsioc`](https://www.npmjs.com/package/tsioc) +- Dependency Injection (IoC): A robust IoC container that manages dependencies between components +- Aspect-Oriented Programming (AOP): Implementation of cross-cutting concerns like security and transactions +- Module System: Decorator-based module system for organizing code +- Decorators: Extensive use of TypeScript decorators for defining components -# builder +# Key Features +- HTTP Endpoints: Controller-based system with decorators like @Controller, @Get, @Post +- Security: Authorization through aspects with support for various authentication methods +- Messaging: Support for multiple protocols (AMQP, MQTT, Kafka, Nats, Redis, WS, TCP, etc.) with a unified API +- Database Integration: TypeORM integration with transaction support +- Exception Handling: Comprehensive exception system with HTTP status mapping +Interceptors: Request/response transformation and cross-cutting concerns + +# Package Structure + +The framework is organized into multiple packages: + +- @tsdi/ioc: Core IoC container +- @tsdi/aop: Aspect-Oriented Programming support +- @tsdi/core: Application core and module management +- @tsdi/endpoints: HTTP endpoint handling +- @tsdi/security: Authentication and authorization +- @tsdi/repository: Database repository pattern +- @tsdi/typeorm-adapter: TypeORM integration +- Various protocol adapters: For different communication protocols + +The framework follows patterns similar to Spring/Java, with heavy use of decorators and dependency injection. It's designed for building modular, maintainable applications with clear separation of concerns, particularly well-suited for microservices and complex backend applications. + +# source build ```shell build: npm run build @@ -22,15 +48,33 @@ npm run build -- --setvs=4.0.0-beta ``` -# Install + +# Install cli ```shell +npm i -g @tsdi/cli +``` -npm install @tsdi/ioc +# Quick start +## new project +```shell +tsdi new [application name] ``` -## add extends modules +## test project + +* use shell +```shell +tsdi test +``` +* debug test use vscode debug + + + +# publish modules + +## ioc, module `@tsdi/ioc` ### use aop @@ -54,8 +98,6 @@ container.use(AopModule); container.register(AopModule); // or container.inject(AopModule) -//or -container.injectModule(AopModule) ``` @@ -63,12 +105,12 @@ container.injectModule(AopModule) ```shell // install aop logs -npm install @tsdi/logs +npm install @tsdi/logger ``` ```ts -import { LogModule } from '@tsdi/logs'; +import { LogModule } from '@tsdi/logger'; // in server import { ContainerBuilder } from '@tsdi/platform-server' // in browser @@ -84,16 +126,7 @@ container.use(LogModule); # Documentation -## core - -### extends ioc -1. `@IocExt` class decortator, use to define the class is Ioc extends module. it will auto run after registered to helper your to setup module. -2. add service resolve. -3. module inject. - - -## Ioc - +## ioc, module `@tsdi/ioc` 1. Register one class will auto register depdence class (must has a class decorator). 2. get Instance can auto create constructor param. (must has a class decorator or register in container). @@ -101,24 +134,28 @@ container.use(LogModule); ### decorators 1. `@Abstract` abstract class decorator. -2. `@AutoRun` class, method decorator, use to define the class auto run (via a method or not) after registered. -3. `@AutoWried` property or param decorator, use to auto wried type instance or value to the instance of one class with the decorator. +2. `@Autorun` class and method decorator, use to define the class auto run (via a method or not) after registered. +3. `@Autowried` alias name `@AutoWried` property or param decorator, use to auto wried type instance or value to the instance of one class with the decorator. 4. `@Inject` property or param decorator, use to auto wried type instance or value to the instance of one class with the decorator. 5. `@Injectable` class decortator, use to define the class. it can setting provider to some token, singleton or not. -6. `@AutoWried` method decorator. +6. `@IocExt` class decortator, use to define the class is Ioc extends module. it will auto run after registered to helper your to setup module. 7. `@Param` param decorator, use to auto wried type instance or value to the instance of one class with the decorator. -8. `@Singleton` class decortator, use to define the class is singleton. -9. `@Providers` Providers decorator, for class. use to add private ref service for the class. -10. `@Refs` Refs decorator, for class. use to define the class as a service for target. - - -## AOP - +8. `@Singleton` class decortator, use to define the class is singleton in global. +9. `@Static` class decortator, use to define the class is static in injector. +10. `@Providers` Providers decorator, for class. use to add private ref service for the class. +11. `@ProviderIn` alias `@Refs` ProviderIn decorator, for class. use to define the class as a service for target. +12. `@Nullable` param decoator. define param can enable null. +13. `@Optional` Parameter decorator to be used on constructor parameters, which marks the parameter as being an optional dependency. The DI framework provides `null` if the dependency is not found. Can be used together with other parameter decorators that modify how dependency injection operates. +14. `@Self` Parameter decorator to be used on constructor parameters, which tells the DI framework to start dependency resolution from the local injector. Resolution works upward through the injector hierarchy, so the children of this class must configure their own providers or be prepared for a `null` result. +15. `@SkipSelf` Parameter decorator to be used on constructor parameters, which tells the DI framework to start dependency resolution from the parent injector. Resolution works upward through the injector hierarchy, so the local injector is not checked for a provider. +16. `@Host` Parameter decorator on a compose element provider parameter of a class constructor that tells the DI framework to resolve the view by checking injectors of child elements, and stop when reaching the host element of the current component. + +## aop, module `@tsdi/aop` It's a dynamic aop base on ioc. define a Aspect class, must with decorator: -* `@Aspect` Aspect decorator, define for class. use to define class as aspect. it can setting provider to some token, singleton or not. +* `@Aspect()` Aspect decorator, define for class. use to define class as aspect. it can setting provider to some token, singleton or not. * `@Before(matchstring|RegExp)` method decorator, aop Before advice decorator. @@ -135,14 +172,336 @@ define a Aspect class, must with decorator: see [simples](https://github.com/zhouhoujun/tsioc/tree/master/packages/aop/test/aop) +## core, module `@tsdi/core` +Application framework. + +### Decorators +Module manager, application bootstrap. base on AOP. + +* `@Module` Module decorator, use to define class as ioc Module. alias name @DIModule. +* `@ComponentScan`ComponentScan decorator, use to auto scan server or client for application. +* `@Handle` Handle decorator, for class. use to define the class as handle register in global handle queue or parent; for method as message handle, use to handle route message event, in class with decorator {@link RouteMapping}. +* `@RouteMapping` route mapping decorator, for class. use to define this class as message route. +* `@RequestPath` Request path parameter decorator for route mapping. +* `@RequestParam` Request query parameter decorator for route mapping. +* `@RequestBody` Request body parameter decorator for route mapping. +* `@Pipe` Pipe decorator, define for class. use to define the class. it can setting provider to some token, singleton or not. it will execute [`PipeLifecycle`] + +[application simple](https://github.com/zhouhoujun/type-mvc/tree/master/packages/simples) + + +### Quick start +```ts +import { Controller, Delete, Get, Post, Put, RequestParam } from '@tsdi/core'; +import { lang } from '@tsdi/ioc'; +import { InjectLog, Logger } from '@tsdi/logger'; +import { Repository, Transactional } from '@tsdi/repository'; +import { InternalServerError } from '@tsdi/endpoints'; +import { User } from '../models/models'; +import { UserRepository } from '../repositories/UserRepository'; + +@Controller('/users') +export class UserController { + + // @Inject() injector!: Injector; + // @Log() logger!: Logger; + // @InjectLog() logger!: Logger; + constructor(private usrRep: UserRepository, @InjectLog() private logger: Logger) { + + } + + + @Get('/:name') + getUser(name: string) { + this.logger.log('name:', name); + return this.usrRep.findByAccount(name); + } + + @Transactional() + @Post('/') + @Put('/') + async modify(user: User, @RequestParam({ nullable: true }) check?: boolean) { + this.logger.log(lang.getClassName(this.usrRep), user); + const val = await this.usrRep.save(user); + if(check) throw new InternalServerError('check'); + this.logger.log(val); + return val; + } + + @Transactional() + @Post('/save') + @Put('/save') + async modify2(user: User, @Repository() userRepo: UserRepository, @RequestParam({ nullable: true }) check?: boolean) { + this.logger.log(lang.getClassName(this.usrRep), user); + const val = await userRepo.save(user); + if(check) throw new InternalServerError('check'); + this.logger.log(val); + return val; + } + + @Transactional() + @Delete('/:id') + async del(id: string) { + this.logger.log('id:', id); + await this.usrRep.delete(id); + return true; + } + +} + + + +@Controller('/roles') +export class RoleController { + + constructor(@DBRepository(Role) private repo: Repository, @Log() private logger: Logger) { + + } + + @Transactional() + @Post('/') + @Put('/') + async save(role: Role, @RequestParam({ nullable: true }) check?: boolean) { + this.logger.log(role); + console.log('save isTransactionActive:', this.repo.queryRunner?.isTransactionActive); + const value = await this.repo.save(role); + if (check) throw new InternalServerError('check'); + this.logger.info(value); + return value; + } + + @Transactional() + @Post('/save2') + @Put('/save2') + async save2(role: Role, @DBRepository(Role) roleRepo: Repository, @RequestParam({ nullable: true }) check?: boolean) { + this.logger.log(role); + console.log('save2 isTransactionActive:', roleRepo.queryRunner?.isTransactionActive); + const value = await roleRepo.save(role); + if (check) throw new InternalServerError('check'); + this.logger.info(value); + return value; + } + + + @Get('/:name') + async getRole(name: string) { + this.logger.log('name:', name); + console.log('getRole isTransactionActive:', this.repo.queryRunner?.isTransactionActive); + return await this.repo.findOne({ where: { name } }); + } + + + @Transactional() + @Delete('/:id') + async del(id: string) { + this.logger.log('id:', id); + console.log('del isTransactionActive:', this.repo.queryRunner?.isTransactionActive); + await this.repo.delete(id); + return true; + } + + +} + + +``` + +### service In nodejs. + +```ts +import { Application, Module } from '@tsdi/core'; +import { LogModule } from '@tsdi/logger'; +import { ConnectionOptions, TransactionModule } from '@tsdi/repository'; +import { TypeOrmModule } from '@tsdi/typeorm-adapter'; +import { Http, HttpClientOptions, HttpModule, HttpServer } from '@tsdi/endpoints'; +import { ServerModule } from '@tsdi/platform-server'; + +const key = fs.readFileSync(path.join(__dirname, './cert/localhost-privkey.pem')); +const cert = fs.readFileSync(path.join(__dirname, './cert/localhost-cert.pem')); + +@Module({ + // baseURL: __dirname, + imports: [ + ServerModule, + LoggerModule, + HttpModule.withOption({ + majorVersion: 2, + options: { + allowHTTP1: true, + key, + cert + } + }), + TransactionModule, + TypeOrmModule.withConnection({ + name: 'xx', + type: 'postgres', + host: 'localhost', + port: 5432, + username: 'postgres', + password: 'postgres', + database: 'testdb', + synchronize: true, // 同步数据库 + logging: false // 日志, + models: ['./models/**/*.ts'], + repositories: ['./repositories/**/*.ts'], + }) + ], + declarations: [ + UserController, + RoleController + ], + bootstrap: HttpServer +}) +export class Http2ServerModule { + +} + +Application.run(Http2ServerModule); + +``` + + +### Service In Browser. + +```ts +import { Application, Module } from '@tsdi/core'; +import { LogModule } from '@tsdi/logger'; +import { ConnectionOptions, TransactionModule } from '@tsdi/repository'; +import { TypeOrmModule } from '@tsdi/typeorm-adapter'; +import { BrowserModule } from '@tsdi/platform-browser'; + +@Module({ + imports: [ + LogModule, + BrowserModule, + // import TransactionModule can enable transaction by AOP. + TransactionModule, + TypeOrmModule.withOptions({ + host: 'localhost', + port: 5432, + username: 'postgres', + password: 'postgres', + database: 'testdb', + entities: [ + Role, + User + ], + repositories: [UserRepository] + }) + ], + providers: [ + UserController, + RoleController + ] +}) +export class MockTransBootTest { + +} + +Application.run(MockTransBootTest) + +``` + + +```ts +// model flies in ./models/**/*.ts +import { Entity, Column, ManyToOne, PrimaryGeneratedColumn, OneToMany, EntityRepository, Repository, Connection } from 'typeorm'; -## boot -DI Module manager, application bootstrap. base on AOP. +@Entity() +export class Role { + @PrimaryGeneratedColumn('uuid') + id!: string; -* `@DIModule` DIModule decorator, use to define class as DI Module. -* `@Bootstrap` Bootstrap decorator, use to define class as bootstrp module. -* `@Annotation` Annotation decorator, use to define class build metadata config. -* `@Message` Message decorator, for class. use to define the class as message handle register in global message queue. + @Column({ length: 50 }) + name!: string; + + @OneToMany(type => User, user => user.role, { nullable: true }) + users!: User[] +} + + +@Entity() +export class User { + constructor() { + } + + @PrimaryGeneratedColumn('uuid') + id!: string; + + + @Column({ length: 50 }) + name!: string; + + @Column({ + unique: true + }) + account!: string; + + @Column() + password!: string; + + @Column({ nullable: true, length: 50 }) + email!: string; + + @Column({ nullable: true, length: 50 }) + phone!: string; + + @Column({ type: 'boolean', nullable: true }) + gender!: boolean; + + @Column({ type: 'int', nullable: true }) + age!: number; + + @ManyToOne(type => Role, role => role.users, { nullable: true }) + role!: Role; + +} + +``` + +```ts +// repositories in ./repositories/**/*.ts +import { EntityRepository, Repository } from 'typeorm'; +import { User } from '../models'; + +@EntityRepository(User) +export class UserRepository extends Repository { + + async findByAccount(account: string) { + return await this.findOne({ where: { account } }); + } + + search(key: string, skip?: number, take?: number) { + const keywords = `%${key}%`; + return this.createQueryBuilder('usr') + .where('usr.name = :keywords OR usr.id = :key', { keywords, key }) + .skip(skip) + .take(take) + .getManyAndCount(); + } +} + + +``` + + + +## repository, module `@tsdi/repository` +orm repository for application. + +### Decorators +application repository. base on AOP. + +* `@Repository` alias name `@DBRepository` Repository Decorator, to autowired repository for paramerter or filed. +* `@Transactional` Transactional Decorator, define transaction propagation behaviors. + +[mvc boot simple](https://github.com/zhouhoujun/type-mvc/tree/master/packages/simples) + + + +## boot, module `@tsdi/boot` +bootstrap app base on core with application configuration. [mvc boot simple](https://github.com/zhouhoujun/type-mvc/tree/master/packages/simples) @@ -158,7 +517,7 @@ export class TestService { } } -@DIModule({ +@Module({ providers: [ { provide: 'mark', useFactory: () => 'marked' }, TestService @@ -171,7 +530,7 @@ export class ModuleA { } -@Injectable +@Injectable() export class ClassSevice { @Inject('mark') mark: string; @@ -181,7 +540,7 @@ export class ClassSevice { } } -@Aspect +@Aspect() export class Logger { @Around('execution(*.start)') @@ -191,7 +550,7 @@ export class Logger { } -@DIModule({ +@Module({ imports: [ AopModule, ModuleA @@ -256,10 +615,6 @@ container.register(new Registration(Person, aliasname)); // register singleton container.registerSingleton(Person) -// bind provider -container.bindProvider -// bind providers. -container.bindProviders ``` @@ -284,7 +639,7 @@ you can use yourself `MethodAccessor` by implement IMethodAccessor, register `Me ```ts -@Injectable +@Injectable() class Person { constructor() { @@ -294,12 +649,12 @@ class Person { } } -@Injectable +@Injectable() class Child extends Person { constructor() { super(); } - say() { + override say() { return 'Mama'; } } @@ -337,7 +692,7 @@ class MethodTest3 { } } -@Injectable +@Injectable() class Geet { constructor(private name: string){ @@ -374,29 +729,29 @@ container.invoke(MethodTest3, 'sayHello'); ```ts -import { Method, ContainerBuilder, AutoWired, Injectable, Singleton, IContainer, ParameterMetadata, Param, Aspect } from '@tsdi/core'; +import { Method, ContainerBuilder, Autowired, Injectable, Singleton, IContainer, ParameterMetadata, Param, Aspect } from '@tsdi/core'; export class SimppleAutoWried { constructor() { } - @AutoWired + @Autowired() dateProperty: Date; } -@Singleton +@Singleton() export class Person { name = 'testor'; } // > v0.3.5 all class decorator can depdence. -@Singleton -// @Injectable +@Singleton() +// @Injectable() export class RoomService { constructor() { } - @AutoWired + @Autowired() current: Date; } @@ -418,14 +773,14 @@ export class MiddleSchoolStudent extends Student { constructor() { super(); } - sayHi() { + override sayHi() { return 'I am a middle school student'; } } @Injectable() export class MClassRoom { - @AutoWired(MiddleSchoolStudent) + @Autowired(MiddleSchoolStudent) leader: Student; constructor() { @@ -438,26 +793,26 @@ export class CollegeStudent extends Student { constructor() { super(); } - sayHi() { + override sayHi() { return 'I am a college student'; } } -@Injectable +@Injectable() export class CollegeClassRoom { constructor( @Param(CollegeStudent) - @AutoWired(CollegeStudent) + @Autowired(CollegeStudent) public leader: Student) { } } -@Injectable +@Injectable() export class InjMClassRoom { // @Inject(MiddleSchoolStudent) - @Inject + @Inject() // @Inject({ type: MiddleSchoolStudent }) // @Inject({ provider: MiddleSchoolStudent }) leader: Student; @@ -471,10 +826,10 @@ export interface IClassRoom { leader: Student; } -@Injectable +@Injectable() export class InjCollegeClassRoom { constructor( - // all below decorator can work, also @AutoWired, @Param is. + // all below decorator can work, also @Autowired(), @Param() is. // @Inject(new Registration(Student, 'college')) // need CollegeStudent also register. @Inject(CollegeStudent) // @Inject({ provider: CollegeStudent }) @@ -486,10 +841,10 @@ export class InjCollegeClassRoom { } } -@Injectable +@Injectable() export class InjCollegeAliasClassRoom { constructor( - // all below decorator can work, also @AutoWired, @Param is. + // all below decorator can work, also @Autowired(), @Param() is. @Inject(new Registration(Student, 'college')) // need CollegeStudent also register. // @Inject(CollegeStudent) // @Inject({ provider: CollegeStudent }) @@ -505,7 +860,7 @@ export class InjCollegeAliasClassRoom { @Injectable('StringClassRoom') export class StingMClassRoom { // @Inject(MiddleSchoolStudent) - @Inject + @Inject() // @Inject({ type: MiddleSchoolStudent }) leader: Student; constructor() { @@ -539,7 +894,7 @@ export class SymbolIdest { } } -@Injectable +@Injectable() class MethodTestPerson { say() { return 'hello word.' @@ -614,19 +969,65 @@ builder.build({ see AOP extends (https://github.com/zhouhoujun/tsioc/blob/master/packages/aop/src/AopModule.ts) You can extend yourself decorator via: + ## Documentation Documentation is available on the * [@tsdi/ioc document](https://github.com/zhouhoujun/tsioc/tree/master/packages/ioc). * [@tsdi/aop document](https://github.com/zhouhoujun/tsioc/tree/master/packages/aop). +* [@tsdi/logger document](https://github.com/zhouhoujun/tsioc/tree/master/packages/logger). +* [@tsdi/common document](https://github.com/zhouhoujun/tsioc/tree/master/packages/common). * [@tsdi/core document](https://github.com/zhouhoujun/tsioc/tree/master/packages/core). +* [@tsdi/endpoints document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport). +* [@tsdi/amqp document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport-amqp). +* [@tsdi/coap document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport-coap). +* [@tsdi/http document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport-http). +* [@tsdi/kafka document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport-kafka). +* [@tsdi/mqtt document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport-mqtt). +* [@tsdi/nats document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport-nats). +* [@tsdi/redis document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport-redis). +* [@tsdi/tcp document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport-tcp). +* [@tsdi/udp document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport-udp). +* [@tsdi/ws document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport-ws). +* [@tsdi/typeorm-adapter document](https://github.com/zhouhoujun/tsioc/tree/master/packages/typeorm-adapter). * [@tsdi/boot document](https://github.com/zhouhoujun/tsioc/tree/master/packages/boot). * [@tsdi/components document](https://github.com/zhouhoujun/tsioc/tree/master/packages/components). +* [@tsdi/compiler document](https://github.com/zhouhoujun/tsioc/tree/master/packages/compiler). * [@tsdi/activities document](https://github.com/zhouhoujun/tsioc/tree/master/packages/activities). -* [@tsdi/typeorm-adapter document](https://github.com/zhouhoujun/tsioc/tree/master/packages/typeorm-adapter). +* [@tsdi/pack document](https://github.com/zhouhoujun/tsioc/tree/master/packages/pack). * [@tsdi/unit document](https://github.com/zhouhoujun/tsioc/tree/master/packages/unit). * [@tsdi/unit-console document](https://github.com/zhouhoujun/tsioc/tree/master/packages/unit-console). * [@tsdi/cli document](https://github.com/zhouhoujun/tsioc/tree/master/packages/cli). + + +### packages +[@tsdi/cli](https://www.npmjs.com/package/@tsdi/cli) +[@tsdi/ioc](https://www.npmjs.com/package/@tsdi/ioc) +[@tsdi/aop](https://www.npmjs.com/package/@tsdi/aop) +[@tsdi/logger](https://www.npmjs.com/package/@tsdi/logger) +[@tsdi/common](https://www.npmjs.com/package/@tsdi/common) +[@tsdi/core](https://www.npmjs.com/package/@tsdi/core) +[@tsdi/endpoints](https://www.npmjs.com/package/@tsdi/endpoints) +[@tsdi/amqp](https://www.npmjs.com/package/@tsdi/amqp) +[@tsdi/coap](https://www.npmjs.com/package/@tsdi/coap) +[@tsdi/http](https://www.npmjs.com/package/@tsdi/http) +[@tsdi/kafka](https://www.npmjs.com/package/@tsdi/kafka) +[@tsdi/mqtt](https://www.npmjs.com/package/@tsdi/mqtt) +[@tsdi/nats](https://www.npmjs.com/package/@tsdi/nats) +[@tsdi/redis](https://www.npmjs.com/package/@tsdi/redis) +[@tsdi/tcp](https://www.npmjs.com/package/@tsdi/tcp) +[@tsdi/udp](https://www.npmjs.com/package/@tsdi/udp) +[@tsdi/ws](https://www.npmjs.com/package/@tsdi/ws) +[@tsdi/typeorm-adapter](https://www.npmjs.com/package/@tsdi/typeorm-adapter) +[@tsdi/boot](https://www.npmjs.com/package/@tsdi/boot) +[@tsdi/components](https://www.npmjs.com/package/@tsdi/components) +[@tsdi/compiler](https://www.npmjs.com/package/@tsdi/compiler) +[@tsdi/activities](https://www.npmjs.com/package/@tsdi/activities) +[@tsdi/pack](https://www.npmjs.com/package/@tsdi/pack) +[@tsdi/unit](https://www.npmjs.com/package/@tsdi/unit) +[@tsdi/unit-console](https://www.npmjs.com/package/@tsdi/unit-console) + + ## License MIT © [Houjun](https://github.com/zhouhoujun/) \ No newline at end of file diff --git a/cert/cet b/cert/cet new file mode 100644 index 000000000..84ebf65cc --- /dev/null +++ b/cert/cet @@ -0,0 +1 @@ +openssl req -x509 -days 3650 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' -keyout localhost-privkey.pem -out localhost-cert.pem diff --git a/cert/localhost-cert.pem b/cert/localhost-cert.pem new file mode 100644 index 000000000..3e134e9ac --- /dev/null +++ b/cert/localhost-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUOe2GaGWuTkama8MFACS2b9kLxRYwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMDUyODAyNTgwM1oXDTMyMDUy +NTAyNTgwM1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtzABZnX2Qi7M/wc7+PKpp66cyAIwAgppdY94Xx71vNvT +Ox7IiRjGR8Ep9O6nIj24MeSEPZUyUXTAhFOXJUgxZmxYJKcCbzbpjl6uPmK3RxKG +RQfriEnYwoiWID7Edy7/si1l6m5xSGUQuyE2CNZ/be8y7sBHYq5Ey/vfQOb5CNeL +a2FweVxoLDxqj/t0nCYnGxFBBsMCd9c3dt4zJ+ZZbcGuNZa/Gpv8xWL4hJnY7kOr +vLEYzwZg2VeQO/di8k+CZtDxZ3QSafMkU8wZIenqUxswuu0lqQNAk3aLBJg8IYrY +kbLkJialCWPg8y/nB+04cJwNP0Gj0R+dDf0NeZwSHwIDAQABo1MwUTAdBgNVHQ4E +FgQUJ5onmuNVwRSUbPNIoqaA9xlsucQwHwYDVR0jBBgwFoAUJ5onmuNVwRSUbPNI +oqaA9xlsucQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAY2Fq +M2DAxgcLt2XekstrGJGD5ata202aLGOyPeTJMxpOAeJzUlkgficY7SmrewzpjI92 +Cpec93ITtbAV7T5ZjEzzgUCLbHyQ6/lFXPtkUnKWd5rfqXtdy4zyiXH1Grni5K7j +RqC+fL2Bj+BFnqJWjc27SR0i6A1aTMj8jMxzJNmOOWYPqTzBl5xajwXU7R6TJS/M +554a9zLQEUFXbOx31glbhx2C+wwB8ZNuek06ieSfHTuqGdnrOrurKwpqMBjV5YX7 +m9lu4aT+azEFokcGArZKXkOzt26LqeptNj9v1h67ik8VmXU34ZZdZbi6lkPwD76n +ZzfGbMt87vaFBUTnbg== +-----END CERTIFICATE----- diff --git a/cert/localhost-privkey.pem b/cert/localhost-privkey.pem new file mode 100644 index 000000000..6c4d2b16f --- /dev/null +++ b/cert/localhost-privkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3MAFmdfZCLsz/ +Bzv48qmnrpzIAjACCml1j3hfHvW829M7HsiJGMZHwSn07qciPbgx5IQ9lTJRdMCE +U5clSDFmbFgkpwJvNumOXq4+YrdHEoZFB+uISdjCiJYgPsR3Lv+yLWXqbnFIZRC7 +ITYI1n9t7zLuwEdirkTL+99A5vkI14trYXB5XGgsPGqP+3ScJicbEUEGwwJ31zd2 +3jMn5lltwa41lr8am/zFYviEmdjuQ6u8sRjPBmDZV5A792LyT4Jm0PFndBJp8yRT +zBkh6epTGzC67SWpA0CTdosEmDwhitiRsuQmJqUJY+DzL+cH7ThwnA0/QaPRH50N +/Q15nBIfAgMBAAECggEAXm06a4xm0hq5xMP/4AWF+k+BtX+bK5IeZmD8FVwqI53U +tmbZzAGfQ6GIdR8HvBjAd8dpey0K8P4TtqI4CRm7jSZQESSdlqJhpeecPcA45UPh +Pngkqu4V88DgUeLI9v6dA0VvgXZIop6mDy/BYLDgotRoFrW6ll1qTROaMyRcPnes +6K5kUgalGiBVE1J0//cc2VkRq9e5TfkFq8/l1iKvRdcTICXG5RYw+knV53WFiGot +Lyaz1jFzsyoS3G8ztupxV6XRizYtKJong1R7/2GU6QLcUFuU3wJJ/Nf6STqlFPeo +tLtAdiRYd1efUCWgD8NMFkTt91hniAlY4Sn4BctFUQKBgQDsH445ryQshsUMHa++ +ku7MGNZ7bpGhZYckUcRzXt1clauX4P5av0eq1rtpYPmDl0g7iS7bhNKZs3LMTG5A +EX3fBLBhNIicc6w8eTX/yYh9C8+pfwvbJgaI25rtyIfDKMZqqH8hngcUdJQN/CCD +DKqYjqXbFsy8WsYKlsqRaqFduwKBgQDGm6/0HZICMwMwZ7hsMVk1NMaEgfqQ/0Jc +fl9opzfW39QPdJzyMfm/TzjvHL/0141/7E6FCBT/NJTc3Qt9613ExXNpf3HM/z1H +3vtJGlHnqKDsGl70jrlq3mJ6mBHCKbsQvnpNUd+1OxpPlFeL89azJjOTcTujXPEY +SgqCXX4k7QKBgQCp4kpB1nEAWDaNcQ5w3uZQkc4/IF5G8PU6fFoLZ8V8R3Md4xMd +12GI2NYeC1d6Be5CZ4D/D6E2VJXTx2nLTspxGr6MMW/YoKDbIZpoXmeFEGpoWK// +DGzHKrldO1nyX/Ph6TkOxtPxM68SIMSDhSl4eal65/yFyAmUtMkptr5C4QKBgQCi +k2kKLHaL5FQ0+IKH1jGPEz950qhYrwsUicHx+ggElb6SVEPxIiWti90wJtmfRB+p +Wre/wCjn65P749n3Xoaof3UFI/T0+O6h+Y67fZcUSf5IpyGZkYeISrhUYE0RInXl +s/aLenzkbpgQEn9DqfvTNG89v5AxHvCgkGNf6cZL1QKBgQCAKuoetMyJo9VqvFJi +lkSHRQzfSvXlh9S9TzGDVbDixvbEpnyUqgM49t+OYbxLpOnWUHjCBDVlbt/0DMLR +HDWJ12UqlKQSIx4X75wDQ4F3MiRHcx26rCak/CNCD2wQ2Fm0gxoldC+ElcJxjiWz +uvOJ3ooVGXKJ0awZ5d+5SLvaiQ== +-----END PRIVATE KEY----- diff --git a/deploy.cmd b/deploy.cmd index dc551b2cd..4b026ef12 100644 --- a/deploy.cmd +++ b/deploy.cmd @@ -1 +1 @@ -npm run build -- --deploy=true \ No newline at end of file +npm run build -- --deploy=true diff --git a/package.json b/package.json index 5cdba06d9..593b22824 100644 --- a/package.json +++ b/package.json @@ -31,21 +31,41 @@ "type": "git", "url": "git+https://github.com/zhouhoujun/tsioc.git" }, - "dependencies": {}, "devDependencies": { + "@rollup/plugin-commonjs": "^18.0.0", + "@rollup/plugin-inject": "^4.0.2", + "@rollup/plugin-node-resolve": "^11.2.1", + "@rollup/pluginutils": "~4.1.0", + "@types/amqp-connection-manager": "^2.0.12", + "@types/amqplib": "^0.10.1", "@types/execa": "^2.0.0", + "@types/jsonwebtoken": "^9.0.9", "@types/mocha": "^7.0.2", + "@types/pumpify": "^1.4.1", "@types/shelljs": "^0.8.0", - "@types/vinyl-fs": "^2.4.8", - "assert": "^2.0.0", - "bluebird": "^3.7.2", + "@types/swagger-ui-dist": "^3.30.1", + "@types/through2": "^2.0.36", + "@types/uuid": "^10.0.0", + "@types/ws": "^8.5.3", + "@typescript-eslint/eslint-plugin": "^5.23.0", + "@typescript-eslint/parser": "^5.23.0", + "assert": "^2.1.0", + "browser-process-hrtime": "^1.0.0", "chalk": "^4.0.0", "chokidar": "^3.4.0", - "commander": "^4.1.1", - "core-js": "^3.6.4", - "del": "^5.1.0", + "commander": "^12.1.0", + "core-js": "^3.42.0", + "css-select": "^6.0.0", + "del": "^8.0.0", + "eslint": "^8.15.0", + "eslint-config-google": "^0.14.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-n": "^15.2.0", + "eslint-plugin-promise": "^6.0.0", "execa": "^4.0.2", "expect": "^25.3.0", + "express": "^4.18.2", "findup": "^0.1.5", "globby": "^11.0.0", "gulp-filter": "^6.0.0", @@ -54,42 +74,67 @@ "gulp-replace": "^0.5.4", "gulp-rollup": "^2.16.2", "gulp-sourcemaps": "^2.6.4", - "gulp-typescript": "^5.0.1", - "gulp-uglify-es": "^2.0.0", "interpret": "^2.0.0", "json-in-place": "^1.0.1", - "liftoff": "^2.5.0", + "liftoff": "^5.0.0", "lodash": "^4.13.1", + "log4js": "^6.3.0", "minimatch": "^3.0.4", "minimist": "^1.2.5", + "mqtt": "^4.3.7", "on-change": "^1.6.2", + "parse5": "^8.0.0", "path": "^0.12.7", - "pg": "^8.0.2", + "pg": "^8.5.1", "pretty-hrtime": "^1.0.3", "reflect-metadata": "^0.1.13", - "rollup": "^1.31.1", - "rollup-plugin-commonjs": "^10.1.0", - "rollup-plugin-hypothetical": "^2.1.0", - "rollup-plugin-inject": "^3.0.1", - "rollup-plugin-node-builtins": "^2.1.2", - "rollup-plugin-node-globals": "^1.4.0", - "rollup-plugin-node-resolve": "^5.2.0", - "rollup-plugin-sourcemaps": "^0.4.2", - "rollup-plugin-uglify": "^6.0.3", - "rollup-pluginutils": "^2.8.2", - "rxjs": "^6.3.3", + "rollup": "~2.46.0", + "rxjs": "^7.4.0", "shelljs": "^0.8.3", - "through2": "^3.0.0", + "through2": "^3.0.2", "time-stamp": "^2.2.0", - "ts-node": "^8.10.1", - "tsconfig-paths": "^3.9.0", - "tslib": "^2.0.1", - "tslint": "^6.1.2", - "typeorm": "^0.2.25", - "typescript": "^4.0.2", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "tslib": "^2.8.1", + "typeorm": "^0.3.20", + "typescript": "^5.8.2", "uglify-js": "^3.9.4", - "vinyl-fs": "^3.0.3", - "zone.js": "^0.10.3" + "zone.js": "~0.11.4" }, - "homepage": "https://github.com/zhouhoujun/tsioc#readme" + "homepage": "https://github.com/zhouhoujun/tsioc#readme", + "dependencies": { + "@grpc/grpc-js": "^1.6.3", + "@grpc/proto-loader": "^0.6.9", + "@types/content-disposition": "^0.5.4", + "@types/cookies": "^0.9.0", + "@types/duplexify": "^3.6.1", + "@types/qs": "^6.9.7", + "@types/readable-stream": "^2.3.14", + "amqp-connection-manager": "^4.1.2", + "amqplib": "^0.10.3", + "axios": "^1.6.8", + "body-parser": "^1.20.0", + "class-transformer": "^0.5.1", + "coap": "^1.4.1", + "content-disposition": "^0.5.4", + "cookies": "^0.9.1", + "cross-fetch": "^4.1.0", + "csrf": "^3.1.0", + "domino": "^2.1.6", + "duplexify": "^4.1.2", + "esbuild": "^0.25.1", + "fast-xml-parser": "^5.2.3", + "form-data": "^4.0.0", + "ioredis": "^5.3.2", + "jsonwebtoken": "^9.0.2", + "kafkajs": "^2.2.4", + "koa-convert": "^2.0.0", + "modbus-serial": "^8.0.5", + "nats": "^2.9.0", + "pumpify": "^2.0.1", + "qs": "^6.10.3", + "swagger-ui-dist": "^5.3.1", + "uuid": "^8.3.2", + "ws": "^8.5.0" + } } diff --git a/packages/activities/.npmignore b/packages/activities/.npmignore index 60295857b..85448f43a 100644 --- a/packages/activities/.npmignore +++ b/packages/activities/.npmignore @@ -7,3 +7,4 @@ taskfile.ts tsconfig.json tslint.json package-lock.json +unit.ts diff --git a/packages/activities/README.md b/packages/activities/README.md index 3c7c26c54..6ffcb8840 100644 --- a/packages/activities/README.md +++ b/packages/activities/README.md @@ -229,7 +229,7 @@ export class TsBuildActivity { @Input() annotation: boolean; @Input('annotationFramework') annotationFramework: NodeExpression; @Input('beforePipes') beforePipes: ActivityType[]; - @Input('tsconfig', './tsconfig.json') tsconfig: string | ObjectMap; + @Input('tsconfig', './tsconfig.json') tsconfig: string | Record; @Input() uglify: NodeExpression; @Input('uglifyOptions') uglifyOptions: any; } @@ -288,36 +288,69 @@ Workflow.run({ }); ``` + ## Documentation Documentation is available on the * [@tsdi/ioc document](https://github.com/zhouhoujun/tsioc/tree/master/packages/ioc). * [@tsdi/aop document](https://github.com/zhouhoujun/tsioc/tree/master/packages/aop). +* [@tsdi/logger document](https://github.com/zhouhoujun/tsioc/tree/master/packages/logger). +* [@tsdi/common document](https://github.com/zhouhoujun/tsioc/tree/master/packages/common). * [@tsdi/core document](https://github.com/zhouhoujun/tsioc/tree/master/packages/core). +* [@tsdi/endpoints document](https://github.com/zhouhoujun/tsioc/tree/master/packages/transport). +* [@tsdi/amqp document](https://github.com/zhouhoujun/tsioc/tree/master/packages/amqp). +* [@tsdi/coap document](https://github.com/zhouhoujun/tsioc/tree/master/packages/coap). +* [@tsdi/http document](https://github.com/zhouhoujun/tsioc/tree/master/packages/http). +* [@tsdi/kafka document](https://github.com/zhouhoujun/tsioc/tree/master/packages/kafka). +* [@tsdi/mqtt document](https://github.com/zhouhoujun/tsioc/tree/master/packages/mqtt). +* [@tsdi/nats document](https://github.com/zhouhoujun/tsioc/tree/master/packages/nats). +* [@tsdi/redis document](https://github.com/zhouhoujun/tsioc/tree/master/packages/redis). +* [@tsdi/tcp document](https://github.com/zhouhoujun/tsioc/tree/master/packages/tcp). +* [@tsdi/udp document](https://github.com/zhouhoujun/tsioc/tree/master/packages/udp). +* [@tsdi/ws document](https://github.com/zhouhoujun/tsioc/tree/master/packages/ws). +* [@tsdi/swagger document](https://github.com/zhouhoujun/tsioc/tree/master/packages/swagger). +* [@tsdi/repository document](https://github.com/zhouhoujun/tsioc/tree/master/packages/repository). +* [@tsdi/typeorm-adapter document](https://github.com/zhouhoujun/tsioc/tree/master/packages/typeorm-adapter). * [@tsdi/boot document](https://github.com/zhouhoujun/tsioc/tree/master/packages/boot). * [@tsdi/components document](https://github.com/zhouhoujun/tsioc/tree/master/packages/components). * [@tsdi/compiler document](https://github.com/zhouhoujun/tsioc/tree/master/packages/compiler). * [@tsdi/activities document](https://github.com/zhouhoujun/tsioc/tree/master/packages/activities). * [@tsdi/pack document](https://github.com/zhouhoujun/tsioc/tree/master/packages/pack). -* [@tsdi/typeorm-adapter document](https://github.com/zhouhoujun/tsioc/tree/master/packages/typeorm-adapter). * [@tsdi/unit document](https://github.com/zhouhoujun/tsioc/tree/master/packages/unit). * [@tsdi/unit-console document](https://github.com/zhouhoujun/tsioc/tree/master/packages/unit-console). * [@tsdi/cli document](https://github.com/zhouhoujun/tsioc/tree/master/packages/cli). + ### packages [@tsdi/cli](https://www.npmjs.com/package/@tsdi/cli) [@tsdi/ioc](https://www.npmjs.com/package/@tsdi/ioc) [@tsdi/aop](https://www.npmjs.com/package/@tsdi/aop) +[@tsdi/logger](https://www.npmjs.com/package/@tsdi/logger) +[@tsdi/common](https://www.npmjs.com/package/@tsdi/common) [@tsdi/core](https://www.npmjs.com/package/@tsdi/core) +[@tsdi/endpoints](https://www.npmjs.com/package/@tsdi/endpoints) +[@tsdi/amqp](https://www.npmjs.com/package/@tsdi/amqp) +[@tsdi/coap](https://www.npmjs.com/package/@tsdi/coap) +[@tsdi/http](https://www.npmjs.com/package/@tsdi/http) +[@tsdi/kafka](https://www.npmjs.com/package/@tsdi/kafka) +[@tsdi/mqtt](https://www.npmjs.com/package/@tsdi/mqtt) +[@tsdi/nats](https://www.npmjs.com/package/@tsdi/nats) +[@tsdi/redis](https://www.npmjs.com/package/@tsdi/redis) +[@tsdi/tcp](https://www.npmjs.com/package/@tsdi/tcp) +[@tsdi/udp](https://www.npmjs.com/package/@tsdi/udp) +[@tsdi/ws](https://www.npmjs.com/package/@tsdi/ws) +[@tsdi/swagger](https://www.npmjs.com/package/@tsdi/swagger) +[@tsdi/repository](https://www.npmjs.com/package/@tsdi/repository) +[@tsdi/typeorm-adapter](https://www.npmjs.com/package/@tsdi/typeorm-adapter) [@tsdi/boot](https://www.npmjs.com/package/@tsdi/boot) [@tsdi/components](https://www.npmjs.com/package/@tsdi/components) [@tsdi/compiler](https://www.npmjs.com/package/@tsdi/compiler) [@tsdi/activities](https://www.npmjs.com/package/@tsdi/activities) [@tsdi/pack](https://www.npmjs.com/package/@tsdi/pack) -[@tsdi/typeorm-adapter](https://www.npmjs.com/package/@tsdi/typeorm-adapter) [@tsdi/unit](https://www.npmjs.com/package/@tsdi/unit) [@tsdi/unit-console](https://www.npmjs.com/package/@tsdi/unit-console) + ## License MIT © [Houjun](https://github.com/zhouhoujun/) \ No newline at end of file diff --git a/packages/activities/index.ts b/packages/activities/index.ts new file mode 100644 index 000000000..7f0e38021 --- /dev/null +++ b/packages/activities/index.ts @@ -0,0 +1,2 @@ + +export * from './src'; diff --git a/packages/activities/package.json b/packages/activities/package.json index 297b32c01..9c1ce13a9 100644 --- a/packages/activities/package.json +++ b/packages/activities/package.json @@ -3,7 +3,8 @@ "version": "6.0.31", "description": "Type workflow frameworks, base on AOP, Ioc container, via @tsdi", "scripts": { - "build": "ts-node -r tsconfig-paths/register taskfile.ts" + "build": "ts-node -r tsconfig-paths/register taskfile.ts", + "test": "ts-node -r tsconfig-paths/register unit.ts" }, "keywords": [ "activities", @@ -33,14 +34,5 @@ "url": "https://github.com/zhouhoujun/tsioc.git" }, "homepage": "https://github.com/zhouhoujun/tsioc/blob/master/packages/activities#readme", - "dependencies": {}, - "peerDependencies": { - "@tsdi/ioc": "~6.0.31", - "@tsdi/core": "~6.0.31", - "@tsdi/aop": "~6.0.31", - "@tsdi/boot": "~6.0.31", - "@tsdi/components": "~6.0.31", - "@tsdi/logs": "~6.0.31", - "tslib": "^2.0.1" - } + "dependencies": {} } \ No newline at end of file diff --git a/packages/activities/src/ActivityModule.ts b/packages/activities/src/ActivityModule.ts deleted file mode 100644 index 53518628b..000000000 --- a/packages/activities/src/ActivityModule.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - Inject, DecoratorProvider, DesignRegisterer, IocExt, TypeProviderAction -} from '@tsdi/ioc'; -import { IContainer, ContainerToken } from '@tsdi/core'; -import { BootContext, StartupDecoratorRegisterer, BuildContext, AnnoationAction } from '@tsdi/boot'; -import { - ComponentProvider, ComponentSelectorHandle, AstResolver, - DefaultComponets, ELEMENT_REF, TEMPLATE_REF, COMPONENT_REF, BindingsCache, Input, Output, RefChild, Vaildate, ComponentRegAction -} from '@tsdi/components'; -import { Task } from './decorators/Task'; -import { RunAspect } from './aop/RunAspect'; -import * as activites from './activities'; -import { ActivityProvider } from './ActivityProvider'; -import { ActivityContext } from './core/ActivityContext'; -import { ActivityExecutor } from './core/ActivityExecutor'; -import { WorkflowInstance, WorkflowContext, ActivityElementRef, ActivityTemplateRef, ActivityComponentRef, ControlActivityElementRef } from './core/WorkflowContext'; -import { ActivityDepsRegister } from './registers/ActivityDepsRegister'; - - - -/** - * setup wokflow activity module for boot application. - * - * @export - * @param {IContainer} container - */ -@IocExt() -export class ActivityModule { - constructor() { - } - - setup(@Inject(ContainerToken) container: IContainer) { - - let actInjector = container.getActionInjector(); - - actInjector.getInstance(DesignRegisterer) - .register(Task, 'Class', TypeProviderAction, AnnoationAction, ComponentRegAction, ActivityDepsRegister); - - actInjector.regAction(ComponentSelectorHandle); - actInjector.getInstance(StartupDecoratorRegisterer) - .register(Task, 'TranslateTemplate', ComponentSelectorHandle); - - container.inject(WorkflowContext, ActivityContext, ActivityExecutor, WorkflowInstance, RunAspect); - - actInjector.getValue(DefaultComponets).push('@Task'); - - actInjector.getInstance(DecoratorProvider) - .bindProviders(Task, - ControlActivityElementRef, - { provide: BootContext, useClass: WorkflowContext }, - { provide: BuildContext, useClass: ActivityContext }, - { provide: AstResolver, useFactory: (prd) => new AstResolver(prd), deps: [ComponentProvider] }, - { provide: ComponentProvider, useClass: ActivityProvider }, - { provide: ELEMENT_REF, useClass: ActivityElementRef }, - { provide: TEMPLATE_REF, useClass: ActivityTemplateRef }, - { provide: COMPONENT_REF, useClass: ActivityComponentRef }, - { - provide: BindingsCache, - useFactory: () => new BindingsCache() - .register(Input) - .register(Output) - .register(RefChild) - .register(Vaildate) - } - ); - - - container.use(activites); - } -} diff --git a/packages/activities/src/ActivityProvider.ts b/packages/activities/src/ActivityProvider.ts deleted file mode 100644 index 56a8e2e2d..000000000 --- a/packages/activities/src/ActivityProvider.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Type, Singleton, SymbolType, ClassType } from '@tsdi/ioc'; -import { ICoreInjector } from '@tsdi/core'; -import { AnnoationContext } from '@tsdi/boot'; -import { ComponentProvider, ITemplateOption, ITemplateContext, CONTEXT_REF, NATIVE_ELEMENT, IElementRef, IComponentRef } from '@tsdi/components'; -import { SequenceActivity } from './activities'; -import { Activity } from './core/Activity'; -import { IActivityElementRef, ControlActivityElementRef, ActivityElementRef, ActivityComponentRef } from './core/WorkflowContext'; -import { ActivityContext, ActivityTemplateContext } from './core/ActivityContext'; -import { ControlActivity } from './core/ControlActivity'; - - -const attrSelPrefix = /^ACT_ATTR_/; -const seletPrefix = /^ACT_SELT_/; -/** - * activity provider. - * - * @export - * @class ActivityProvider - * @extends {ComponentProvider} - */ -@Singleton() -export class ActivityProvider extends ComponentProvider { - isElementRef(target: any): target is IElementRef { - return target instanceof ActivityElementRef; - } - isComponentRef(target: any): target is IComponentRef { - return target instanceof ActivityComponentRef; - } - - isElementRefType(target: ClassType): boolean { - return target === ActivityElementRef; - } - - isComponentRefType(target: ClassType): boolean { - return target === ActivityComponentRef; - } - - getSelectorKey(): string { - return 'activity'; - } - - getRefSelectKey(): string { - return 'refId'; - } - - getDefaultCompose(): Type { - return SequenceActivity; - } - - parseRef = true; - - isTemplateContext(context: AnnoationContext): boolean { - return context instanceof ActivityContext; - } - - createTemplateContext(injector: ICoreInjector, options?: ITemplateOption): ITemplateContext { - return ActivityTemplateContext.parse(injector, options); - } - - createElementRef(context: ActivityContext, target: Activity): IActivityElementRef { - if (target instanceof ControlActivity) { - return this.getProviders().getInstance(ControlActivityElementRef, - { provide: CONTEXT_REF, useValue: context }, - { provide: NATIVE_ELEMENT, useValue: target }) - } - return super.createElementRef(context, target) as IActivityElementRef; - } - - toSelectorToken(selector: string): SymbolType { - return seletPrefix.test(selector) ? selector : `ACT_SELT_${selector}`; - } - - toAttrSelectorToken(selector: string): SymbolType { - return attrSelPrefix.test(selector) ? selector : `ACT_ATTR_${selector}`; - } - - isElementType(element: ClassType): boolean { - return element?.ρCT === 'activity'; - } -} diff --git a/packages/activities/src/Workflow.ts b/packages/activities/src/Workflow.ts index 77e5b19f8..58735881e 100644 --- a/packages/activities/src/Workflow.ts +++ b/packages/activities/src/Workflow.ts @@ -1,126 +1,118 @@ -import { Type, isClass, isArray } from '@tsdi/ioc'; -import { LoadType } from '@tsdi/core'; -import { AopModule } from '@tsdi/aop'; -import { LogModule } from '@tsdi/logs'; -import { BootApplication, ContextInit, checkBootArgs } from '@tsdi/boot'; -import { ComponentsModule } from '@tsdi/components'; -import { ActivityModule } from './ActivityModule'; -import { SequenceActivity } from './activities'; -import { ActivityOption } from './core/ActivityOption'; -import { WorkflowInstance, WorkflowContext } from './core/WorkflowContext'; -import { ActivityType } from './core/ActivityMetadata'; -import { UUIDToken, RandomUUIDFactory } from './core/uuid'; -import { WorkflowContextToken } from './core/IWorkflowContext'; - -/** - * workflow builder. - * - * @export - * @class Workflow - * @extends {BootApplication} - */ -export class Workflow extends BootApplication implements ContextInit { - - protected onInit(target: Type | ActivityOption | T) { - if (!isClass(target)) { - if (!target.type) { - let options = target instanceof WorkflowContext ? target.getOptions() : target; - options.type = SequenceActivity; - options.template = isArray(options.template) ? options.template : [options.template]; - } - } - super.onInit(target); - } - - - getWorkflow(workflowId: string): WorkflowInstance { - return this.getContainer().get(workflowId); - } +import { Injectable, AbstractType } from '@tsdi/ioc'; +import { Activity, ActivityContext, ActivityResult } from './activities/Activity'; +export interface WorkflowDefinition { /** - * run sequence. - * - * @static - * @template T - * @param {T} ctx - * @returns {Promise} - * @memberof Workflow + * 工作流名称 */ - static async sequence(ctx: T): Promise; + name: string; /** - * run sequence. - * - * @static - * @template T - * @param {Type} type - * @returns {Promise} - * @memberof Workflow + * 工作流描述 */ - static async sequence(type: Type): Promise; + description?: string; /** - * run sequence. - * - * @static - * @template T - * @param {...ActivityType[]} activities - * @returns {Promise} - * @memberof Workflow + * 工作流版本 */ - static async sequence(...activities: ActivityType[]): Promise; - static async sequence(...activities: any[]): Promise { - if (activities.length === 1) { - let actType = activities[0]; - if (isClass(actType)) { - return await Workflow.run(actType); - } else if (actType instanceof WorkflowContext) { - return await Workflow.run(actType as T); - } else { - return await Workflow.run((actType && actType.template) ? actType : { template: actType }); - } - } else if (activities.length > 1) { - let option = { template: activities, type: SequenceActivity, staticSeq: true } as ActivityOption; - return await Workflow.run(option); - } - } - + version?: string; /** - * run activity. - * - * @static - * @template T - * @param {(T | Type | ActivityOption)} target - * @param {(LoadType[] | LoadType | string)} [deps] workflow run depdences. - * @param {...string[]} args - * @returns {Promise} - * @memberof Workflow + * 是否启用工作流历史记录 */ - static async run(target: T | Type | ActivityOption, deps?: LoadType[] | LoadType | string, ...args: string[]): Promise { - let { deps: depmds, args: envs } = checkBootArgs(deps, ...args); - return await new Workflow(target, depmds).run(...envs) as T; - } + enableHistory?: boolean; + /** + * 是否启用工作流监控 + */ + enableMonitoring?: boolean; + activities: AbstractType[]; + transitions: WorkflowTransition[]; + initialState: string; + finalStates: string[]; +} + +export interface WorkflowTransition { + from: string; + to: string; + condition?: (context: ActivityContext) => boolean; +} - onContextInit(ctx: T) { - ctx.id = ctx.id || this.createUUID(); - super.onContextInit(ctx); - } +export interface WorkflowState { + name: string; + activity: Activity; +} - protected bindContextToken(ctx: T) { - this.getContainer().setValue(WorkflowContextToken, ctx); - } +// @Injectable() +// export class WorkflowInstance { +// private definition: WorkflowDefinition; +// private currentState: string; +// private context: ActivityContext; - protected getBootDeps() { - let deps = super.getBootDeps(); - if (this.getContainer().has(ActivityModule)) { - return deps; - } - return [AopModule, LogModule, ComponentsModule, ActivityModule, ...deps]; - } +// constructor(definition: WorkflowDefinition) { +// this.definition = definition; +// this.currentState = definition.initialState; +// this.context = {}; +// } - protected createUUID() { - let container = this.getContainer(); - if (!container.has(UUIDToken)) { - container.registerType(RandomUUIDFactory); - } - return container.getInstance(UUIDToken).generate(); - } -} +// async start(initialContext: ActivityContext = {}): Promise { +// this.context = { ...initialContext }; +// return this.executeState(this.currentState); +// } + +// private async executeState(stateName: string): Promise { +// const activity = this.findActivityByState(stateName); +// if (!activity) { +// throw new Error(`No activity found for state: ${stateName}`); +// } + +// try { +// const result = await activity.execute(this.context); + +// if (result.success) { +// const nextState = this.getNextState(stateName); +// if (nextState && !this.definition.finalStates.includes(stateName)) { +// this.currentState = nextState; +// return this.executeState(nextState); +// } +// } + +// return result; +// } catch (error) { +// await this.compensate(); +// throw error; +// } +// } + +// private async compensate(): Promise { +// const executedActivities = this.getExecutedActivities(); +// for (const activity of executedActivities.reverse()) { +// if (activity.compensate) { +// await activity.compensate(this.context); +// } +// } +// } + +// private findActivityByState(stateName: string): Activity | undefined { +// return this.definition.activities.find(a => a.name === stateName); +// } + +// private getNextState(currentState: string): string | undefined { +// const transition = this.definition.transitions.find(t => +// t.from === currentState && +// (!t.condition || t.condition(this.context)) +// ); +// return transition?.to; +// } + +// private getExecutedActivities(): Activity[] { +// const activities: Activity[] = []; +// let state = this.definition.initialState; + +// while (state && state !== this.currentState) { +// const activity = this.findActivityByState(state); +// if (activity) { +// activities.push(activity); +// } +// state = this.getNextState(state) as string; +// } + +// return activities; +// } +// } \ No newline at end of file diff --git a/packages/activities/src/activities/Activity.ts b/packages/activities/src/activities/Activity.ts new file mode 100644 index 000000000..7a3961af3 --- /dev/null +++ b/packages/activities/src/activities/Activity.ts @@ -0,0 +1,20 @@ +import { Abstract } from '@tsdi/ioc'; + +export interface ActivityContext { + +} + +export interface ActivityResult { + success: boolean; + data?: T; + error?: Error; +} + +@Abstract() +export abstract class Activity { + + abstract execute(context: ActivityContext): Promise; + + compensate?(context: ActivityContext): Promise; + +} diff --git a/packages/activities/src/activities/Case.ts b/packages/activities/src/activities/Case.ts new file mode 100644 index 000000000..b09dddd19 --- /dev/null +++ b/packages/activities/src/activities/Case.ts @@ -0,0 +1,57 @@ +import { Attribute, Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; + + + +@Component({ selector: 'case' }) +export class CaseActivity extends Activity { + + @Attribute('case') caseFlag!: T; + @Attribute() body!: Activity; + + @Attribute() onError?: (context: ActivityContext) => Promise + + async execute(context: ActivityContext): Promise { + + if (!this.body) { + return { + success: false, + error: new Error('No activity provided for case activity') + }; + } + + try { + // 执行活动 + return await this.body.execute(context); + } catch (error) { + // 如果有自定义错误处理器,使用它 + if (this.onError) { + try { + return await this.onError(error as Error); + } catch (handlerError) { + return { + success: false, + error: handlerError as Error, + data: { + originalError: error, + handlerError: handlerError + } + }; + } + } else { + return { + success: false, + error: error as Error + } + } + + } + } + + async compensate(context: ActivityContext): Promise { + // 如果条件匹配,执行补偿操作 + if (this.body.compensate) { + await this.body.compensate(context); + } + } +} \ No newline at end of file diff --git a/packages/activities/src/activities/ConditionActivity.ts b/packages/activities/src/activities/ConditionActivity.ts deleted file mode 100644 index 1fb4c4ef2..000000000 --- a/packages/activities/src/activities/ConditionActivity.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Input } from '@tsdi/components'; -import { Expression } from '../core/ActivityMetadata'; -import { Task } from '../decorators/Task'; -import { ExpressionActivity } from './ExpressionActivity'; - - -/** - * condition activity. - * - * @export - * @class ConditionActivity - * @extends {ControlActivity} - * @template T - */ -@Task('[condition]') -export class ConditionActivity extends ExpressionActivity { - @Input('condition') expression: Expression; -} diff --git a/packages/activities/src/activities/Conditional.ts b/packages/activities/src/activities/Conditional.ts new file mode 100644 index 000000000..0bc1e3455 --- /dev/null +++ b/packages/activities/src/activities/Conditional.ts @@ -0,0 +1,22 @@ +import { Attribute, Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; + + + +@Component({ + selector: 'conditional' +}) +export class ConditionalActivity extends Activity { + + + @Attribute() + condition!: boolean; + + async execute(context: ActivityContext): Promise { + return { + success: Boolean(this.condition), + data:this.condition + }; + } + +} \ No newline at end of file diff --git a/packages/activities/src/activities/Confirm.ts b/packages/activities/src/activities/Confirm.ts index 76101deb1..40d1278e2 100644 --- a/packages/activities/src/activities/Confirm.ts +++ b/packages/activities/src/activities/Confirm.ts @@ -1,30 +1,76 @@ -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { ConditionActivity } from './ConditionActivity'; -import { ControlActivity } from '../core/ControlActivity'; -import { IActivityContext } from '../core/IActivityContext'; -import { ActivityType } from '../core/ActivityMetadata'; +import { Injectable } from '@tsdi/ioc'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; +import { Component } from '@tsdi/components'; + /** - * while control activity. - * - * @export - * @class ConfirmActivity - * @extends {ControlActivity} + * confirm context */ -@Task('confirm') -export class ConfirmActivity extends ControlActivity { + export class DirConfirmContext { + public $implicit: T = null!; + public dirConfirm: T = null!; +} + +export interface ConfirmActivityContext extends ActivityContext { + message?: string; + title?: string; + confirmCallback?: () => Promise; + cancelCallback?: () => Promise; +} + +export interface ConfirmActivityOptions { + message: string; + title?: string; + confirmText?: string; + cancelText?: string; +} + +@Component({ selector: 'confirm' }) +export class ConfirmActivity extends Activity { + + async execute(context: ConfirmActivityContext): Promise { + try { + // 如果提供了自定义确认回调,使用它 + if (context.confirmCallback) { + const confirmed = await context.confirmCallback(); + return { + success: confirmed, + data: { + confirmed, + timestamp: Date.now() + } + }; + } - @Input() condition: ConditionActivity; + // 使用默认的确认机制 + const message = context.message || this.options?.message || 'Please confirm this action'; + const title = context.title || this.options?.title || 'Confirmation'; - @Input({ bindingType: 'dynamic' }) body: ActivityType; + // 这里可以实现具体的确认UI逻辑 + // 为演示目的,我们返回一个 Promise + return new Promise((resolve) => { + const confirmed = window.confirm(`${title}\n${message}`); + resolve({ + success: confirmed, + data: { + confirmed, + timestamp: Date.now() + } + }); + }); + } catch (error) { + return { + success: false, + error: error as Error + }; + } + } - async execute(ctx: IActivityContext): Promise { - let result = await this.condition.execute(ctx); - if (result) { - await ctx.getExector().runActivity(this.body); + async compensate(context: ConfirmActivityContext): Promise { + if (context.cancelCallback) { + await context.cancelCallback(); } } } diff --git a/packages/activities/src/activities/Delay.ts b/packages/activities/src/activities/Delay.ts index f3ae1c720..c5a204a61 100644 --- a/packages/activities/src/activities/Delay.ts +++ b/packages/activities/src/activities/Delay.ts @@ -1,36 +1,52 @@ -import { PromiseUtil } from '@tsdi/ioc'; -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { ControlActivity } from '../core/ControlActivity'; -import { ActivityContext } from '../core/ActivityContext'; -import { ActivityType } from '../core/ActivityMetadata'; -import { TimerActivity } from './TimerActivity'; - - - -/** - * delay control activity. - * - * @export - * @class DelayActivity - * @extends {ControlActivity} - */ -@Task('delay') -export class DelayActivity extends ControlActivity { - - @Input() timer: TimerActivity; - - @Input({ bindingType: 'dynamic' }) body: ActivityType; - - async execute(ctx: ActivityContext): Promise { - let timeout = await this.timer.execute(ctx); - let defer = PromiseUtil.defer(); - let timmer = setTimeout(() => { - defer.resolve(); - clearTimeout(timmer); - }, timeout); - await defer.promise; - await ctx.getExector().runActivity(this.body); +import { Attribute, Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; + + + + +@Component({ selector: 'delay' }) +export class DelayActivity extends Activity { + + /** + * 延迟时间(毫秒) + */ + @Attribute() duration!: number; + + @Attribute() body: Activity | undefined; + + + private abortController: AbortController | null = null; + + + execute(context: ActivityContext): Promise { + + return new Promise((resolve, reject) => { + // 设置中断处理 + if (this.abortController) { + this.abortController.signal.addEventListener('abort', () => { + reject(new DOMException('Delay aborted', 'AbortError')); + }); + } + setTimeout(resolve, this.duration); + }) + .then(() => this.body ? this.body.execute(context) : ({ success: true, data: { completed: true } })) + .catch(error => { + return { + success: false, + data: { interrupted: true }, + error + }; + }) + .finally(() => [ + this.abortController = null + ]); } -} + async compensate(context: ActivityContext): Promise { + // 中断当前延迟 + if (this.abortController) { + this.abortController.abort(); + } + } + +} diff --git a/packages/activities/src/activities/DoWhile.ts b/packages/activities/src/activities/DoWhile.ts index 27df3e75a..5c8408c59 100644 --- a/packages/activities/src/activities/DoWhile.ts +++ b/packages/activities/src/activities/DoWhile.ts @@ -1,39 +1,138 @@ -import { AsyncHandler } from '@tsdi/ioc'; -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { ControlActivity } from '../core/ControlActivity'; -import { IActivityContext } from '../core/IActivityContext'; -import { ConditionActivity } from './ConditionActivity'; -import { IWorkflowContext } from '../core/IWorkflowContext'; -import { ActivityType } from '../core/ActivityMetadata'; +import { Injectable } from '@tsdi/ioc'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; +export interface DoWhileActivityContext extends ActivityContext { + /** + * 循环条件 + */ + condition: () => Promise | boolean; + /** + * 循环体活动 + */ + bodyActivity: Activity; + /** + * 最大迭代次数 + */ + maxIterations?: number; + /** + * 迭代间隔(毫秒) + */ + interval?: number; + /** + * 迭代回调 + */ + onIteration?: (iteration: number, result: ActivityResult) => void; +} + +export interface DoWhileActivityOptions { + /** + * 默认最大迭代次数 + */ + defaultMaxIterations?: number; + /** + * 默认迭代间隔 + */ + defaultInterval?: number; +} + +@Injectable() +export class DoWhileActivity implements Activity { + name = 'do_while'; + private isRunning = false; + constructor(private options: DoWhileActivityOptions = {}) { + this.options = { + defaultMaxIterations: 100, + defaultInterval: 0, + ...options + }; + } + + async execute(context: DoWhileActivityContext): Promise { + if (!context.bodyActivity || !context.condition) { + return { + success: false, + error: new Error('Missing required body activity or condition') + }; + } -/** - * do while control activity. - * - * @export - * @class DoWhileActivity - * @extends {ContentActivity} - */ -@Task('dowhile') -export class DoWhileActivity extends ControlActivity { + const maxIterations = context.maxIterations ?? this.options.defaultMaxIterations; + const interval = context.interval ?? this.options.defaultInterval!; + let iteration = 0; + let lastResult: ActivityResult | null = null; + this.isRunning = true; - @Input() condition: ConditionActivity; + try { + do { + // 检查最大迭代次数 + if (iteration >= maxIterations!) { + return { + success: false, + error: new Error(`Maximum iterations (${maxIterations}) reached`), + data: { + iterations: iteration, + lastResult + } + }; + } - @Input({ bindingType: 'dynamic' }) body: ActivityType; + // 执行循环体活动 + lastResult = await context.bodyActivity.execute(context); + + // 调用迭代回调 + if (context.onIteration) { + context.onIteration(iteration, lastResult); + } + + // 如果循环体执行失败,中断循环 + if (!lastResult.success) { + return { + success: false, + error: lastResult.error, + data: { + iterations: iteration, + lastResult + } + }; + } + + // 等待指定间隔 + if (interval > 0) { + await new Promise(resolve => setTimeout(resolve, interval)); + } + + iteration++; + } while (this.isRunning && await context.condition()); + + return { + success: true, + data: { + iterations: iteration, + completed: true, + lastResult + } + }; + } catch (error) { + return { + success: false, + error: error as Error, + data: { + iterations: iteration, + lastResult + } + }; + } finally { + this.isRunning = false; + } + } - private action: AsyncHandler | AsyncHandler[]; + async compensate(context: DoWhileActivityContext): Promise { + // 停止循环 + this.isRunning = false; - async execute(ctx: IActivityContext): Promise { - if (!this.action) { - this.action = ctx.getExector().parseAction(this.body); + // 如果循环体活动有补偿操作,执行它 + if (context.bodyActivity.compensate) { + await context.bodyActivity.compensate(context); } - await ctx.getExector().execAction(this.action, async () => { - let condition = await this.condition?.execute(ctx); - if (condition) { - await this.execute(ctx); - } - }); } } diff --git a/packages/activities/src/activities/Each.ts b/packages/activities/src/activities/Each.ts deleted file mode 100644 index 07eec68f2..000000000 --- a/packages/activities/src/activities/Each.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { isNullOrUndefined } from '@tsdi/ioc'; -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { Expression, ActivityType } from '../core/ActivityMetadata'; -import { ActivityContext } from '../core/ActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; -import { ParallelExecutor } from '../core/ParallelExecutor'; -import { WorkflowContext } from '../core/WorkflowContext'; - - -@Task('each') -export class EachActicity extends ControlActivity { - - @Input() each: Expression; - - @Input({ bindingType: 'dynamic' }) body: ActivityType; - - @Input() parallel: boolean; - - async execute(ctx: ActivityContext): Promise { - let items = await ctx.resolveExpression(this.each); - items = items?.filter(i => !isNullOrUndefined(i)); - if (items && items.length) { - if (this.parallel) { - if (ctx.injector.hasRegister(ParallelExecutor)) { - return await ctx.injector.getInstance(ParallelExecutor).run(v => { - return ctx.getExector().runWorkflow(this.body, v).then(c => c.result); - }, items); - } else { - let result: any = await Promise.all(items.map(v => { - return ctx.getExector().runWorkflow(this.body, v).then(c => c.result); - })); - return result as T; - } - } else { - await ctx.getExector().execAction(items.map(v => async (c: WorkflowContext, next) => { - await ctx.getExector().runActivity(this.body, v); - if (next) { - await next(); - } - })); - return ctx.getData() as T; - } - } - return null; - } -} diff --git a/packages/activities/src/activities/Else.ts b/packages/activities/src/activities/Else.ts deleted file mode 100644 index 7a9b5f826..000000000 --- a/packages/activities/src/activities/Else.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { IActivityContext } from '../core/IActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; -import { IFStateKey } from './If'; -import { ActivityType } from '../core/ActivityMetadata'; - -/** - * else activity. - * - * @export - * @class ElseActivity - * @extends {ConditionActivity} - * @template T - */ -@Task('else') -export class ElseActivity extends ControlActivity { - - @Input({ bindingType: 'dynamic' }) body: ActivityType; - - async execute(ctx: IActivityContext): Promise { - let currScope = ctx.runScope; - if (currScope.hasValue(IFStateKey) && !currScope.getValue(IFStateKey)) { - await ctx.getExector().runActivity(this.body); - } - } -} diff --git a/packages/activities/src/activities/ElseIf.ts b/packages/activities/src/activities/ElseIf.ts deleted file mode 100644 index b3d1d60b0..000000000 --- a/packages/activities/src/activities/ElseIf.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Task } from '../decorators/Task'; -import { IActivityContext } from '../core/IActivityContext'; -import { IfActivity, IFStateKey } from './If'; - -/** - * else if activity. - * - * @export - * @class ElseIfActivity - * @extends {IfActivity} - * @template T - */ -@Task('elseif') -export class ElseIfActivity extends IfActivity { - async execute(ctx: IActivityContext): Promise { - let currScope = ctx.runScope; - if (currScope.hasValue(IFStateKey) && !currScope.getValue(IFStateKey)) { - await this.tryExec(ctx); - } - } -} diff --git a/packages/activities/src/activities/End.ts b/packages/activities/src/activities/End.ts new file mode 100644 index 000000000..6232798e3 --- /dev/null +++ b/packages/activities/src/activities/End.ts @@ -0,0 +1,15 @@ +import { Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; + + + +@Component({ selector: 'end'}) +export class EndActivity extends Activity { + + async execute(context: ActivityContext): Promise { + return { + success: true, + data: { endTime: Date.now() } + }; + } +} \ No newline at end of file diff --git a/packages/activities/src/activities/EqualsActivity.ts b/packages/activities/src/activities/EqualsActivity.ts deleted file mode 100644 index e40505558..000000000 --- a/packages/activities/src/activities/EqualsActivity.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { IActivityContext } from '../core/IActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; - -@Task('equals') -export class EqualsActivity extends ControlActivity { - - @Input() equals: string; - - @Input() value: any; - - async execute(ctx: IActivityContext): Promise { - let exp = ctx.getExector().eval(this.equals); - return exp === this.value; - } -} diff --git a/packages/activities/src/activities/ExecuteActivity.ts b/packages/activities/src/activities/ExecuteActivity.ts deleted file mode 100644 index 5e3aa0620..000000000 --- a/packages/activities/src/activities/ExecuteActivity.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { isFunction, isString } from '@tsdi/ioc'; -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { Activity } from '../core/Activity'; -import { IActivityContext } from '../core/IActivityContext'; - -/** - * execute activity. - * - * @export - * @abstract - * @class ExecuteActivity - * @extends {ExecuteActivity} - * @template T - */ -@Task('execute') -export class ExecuteActivity extends Activity { - - @Input('action') action: string | ((ctx: IActivityContext, activity?: Activity) => void | Promise); - - async execute(ctx: IActivityContext): Promise { - let action = isString(this.action) ? ctx.getExector().eval(this.action) : this.action; - if (isFunction(action)) { - return await action(ctx); - } - } -} diff --git a/packages/activities/src/activities/ExistsActvity.ts b/packages/activities/src/activities/ExistsActvity.ts deleted file mode 100644 index 10dc3478f..000000000 --- a/packages/activities/src/activities/ExistsActvity.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { isNullOrUndefined } from '@tsdi/ioc'; -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { IActivityContext } from '../core/IActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; - -@Task('exists') -export class ExistsActvity extends ControlActivity { - - @Input() exists: string; - - async execute(ctx: IActivityContext): Promise { - let exp = ctx.getExector().eval(this.exists); - return !isNullOrUndefined(exp); - } -} diff --git a/packages/activities/src/activities/ExpressionActivity.ts b/packages/activities/src/activities/ExpressionActivity.ts deleted file mode 100644 index 78bfb87f6..000000000 --- a/packages/activities/src/activities/ExpressionActivity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { isString } from '@tsdi/ioc'; -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { IActivityContext } from '../core/IActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; -import { Expression } from '../core/ActivityMetadata'; -import { expExp } from '../utils/exps'; - - -/** - * expression activity. - * - * @export - * @abstract - * @class ExpressionActivity - * @extends {ExecuteActivity} - * @template T - */ -@Task('[expression]') -export class ExpressionActivity extends ControlActivity { - - @Input() expression: Expression; - - async execute(ctx: IActivityContext): Promise { - let expression; - if (isString(this.expression) && expExp.test(this.expression)) { - expression = ctx.getExector().eval(this.expression); - } else { - expression = ctx.resolveExpression(this.expression); - } - return expression; - } - -} diff --git a/packages/activities/src/activities/If.ts b/packages/activities/src/activities/If.ts index 5511b8d85..3eae69348 100644 --- a/packages/activities/src/activities/If.ts +++ b/packages/activities/src/activities/If.ts @@ -1,36 +1,91 @@ -import { tokenId, TokenId } from '@tsdi/ioc'; -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { IActivityContext } from '../core/IActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; -import { ConditionActivity } from './ConditionActivity'; -import { ActivityType } from '../core/ActivityMetadata'; - - -export const IFStateKey: TokenId = tokenId('if-condition'); -/** - * if control activity. - * - * @export - * @class IfActivity - * @extends {ControlActivity} - */ -@Task('if') -export class IfActivity extends ControlActivity { - - @Input() condition: ConditionActivity; - - @Input({ bindingType: 'dynamic' }) body: ActivityType; - - async execute(ctx: IActivityContext): Promise { - await this.tryExec(ctx); +import { Attribute, Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; +import { ConditionalActivity } from './Conditional'; + + +export interface IfActivityOptions { + /** + * 默认错误处理函数 + */ + defaultErrorHandler?: (error: Error) => Promise; +} + +@Component({ + selector: 'if' +}) +export class IfActivity extends ConditionalActivity implements Activity { + + @Attribute('then') + thenActivity!: Activity; + + @Attribute('else') + elseActivity?: Activity; + + @Attribute() + onError?: (error: Error) => Promise; + + + override async execute(context: ActivityContext): Promise { + + if (!this.thenActivity) { + return { + success: false, + error: new Error('No then activity provided for if activity') + }; + } + + try { + // 根据条件选择要执行的活动 + const activityToExecute = this.condition ? this.thenActivity : this.elseActivity; + + // 如果没有 else 活动且条件为 false,返回成功结果 + if (!activityToExecute) { + return { + success: true, + data: { condition: false } + }; + } + + // 执行选定的活动 + const result = await activityToExecute.execute(context); + return { + success: result.success, + error: result.error, + data: { + condition: this.condition, + result: result.data + } + }; + } catch (error) { + // 如果有自定义错误处理器,使用它 + if (this.onError) { + try { + return await this.onError(error as Error); + } catch (handlerError) { + return { + success: false, + error: handlerError as Error, + data: { + originalError: error, + handlerError: handlerError + } + }; + } + } else { + return { + success: false, + error: error as Error + } + } + } } - protected async tryExec(ctx: IActivityContext) { - let result = await this.condition?.execute(ctx); - ctx.runScope.setValue(IFStateKey, result); - if (result) { - await ctx.getExector().runActivity(this.body); + async compensate(context: ActivityContext): Promise { + // 根据条件执行补偿操作 + const activityToCompensate = this.condition ? this.thenActivity : this.elseActivity; + + if (activityToCompensate?.compensate) { + await activityToCompensate.compensate(context); } } -} +} \ No newline at end of file diff --git a/packages/activities/src/activities/Interval.ts b/packages/activities/src/activities/Interval.ts index 535b5e270..99a6d6e40 100644 --- a/packages/activities/src/activities/Interval.ts +++ b/packages/activities/src/activities/Interval.ts @@ -1,31 +1,123 @@ -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { IActivityContext } from '../core/IActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; -import { TimerActivity } from './TimerActivity'; -import { ActivityType } from '../core/ActivityMetadata'; - -/** - * while control activity. - * - * @export - * @class IntervalActivity - * @extends {ControlActivity} - */ -@Task('interval') -export class IntervalActivity extends ControlActivity { - - @Input() timer: TimerActivity; - - @Input({ bindingType: 'dynamic' }) body: ActivityType; - - async execute(ctx: IActivityContext): Promise { +import { Attribute, Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; + + + + +@Component({ selector: 'interval' }) +export class IntervalActivity extends Activity { + + private isRunning = false; + private timeoutId: NodeJS.Timeout | null = null; + + + /** + * 间隔时间(毫秒) + */ + @Attribute() interval!: number; + /** + * 要执行的活动 + */ + @Attribute() body!: Activity; + /** + * 最大执行次数(可选,undefined表示无限执行) + */ + @Attribute() maxExecutions: number | undefined; + /** + * 是否立即执行第一次 + */ + @Attribute() immediate = false; + + + async execute(context: ActivityContext): Promise { if (!this.body) { - return; + return { + success: false, + error: new Error('No action activity provided') + }; + } + + if (this.interval < 0) { + return { + success: false, + error: new Error('Invalid interval value') + }; + } + + this.isRunning = true; + let executionCount = 0; + let lastResult: ActivityResult | null = null; + + try { + return await new Promise((resolve, reject) => { + const executeAction = async () => { + if (!this.isRunning) { + this.cleanup(); + resolve({ + success: true, + data: { + executions: executionCount, + interrupted: true, + lastResult + } + }); + return; + } + + try { + lastResult = await this.body.execute(context); + executionCount++; + + // 检查是否达到最大执行次数 + if (this.maxExecutions && executionCount >= this.maxExecutions) { + this.cleanup(); + resolve({ + success: true, + data: { + executions: executionCount, + completed: true, + lastResult + } + }); + return; + } + + // 设置下一次执行 + this.timeoutId = setTimeout(executeAction, this.interval); + } catch (error) { + this.cleanup(); + reject(error); + } + }; + + // 是否立即执行第一次 + if (this.immediate) { + executeAction(); + } else { + this.timeoutId = setTimeout(executeAction, this.interval); + } + }); + } catch (error) { + return { + success: false, + error: error as Error, + data: { + executions: executionCount, + lastResult + } + }; + } + } + + async compensate(context: ActivityContext): Promise { + this.cleanup(); + } + + private cleanup() { + this.isRunning = false; + if (this.timeoutId) { + clearTimeout(this.timeoutId); + this.timeoutId = null; } - let interval = await this.timer.execute(ctx); - setInterval(() => { - ctx.getExector().runActivity(this.body); - }, interval); } } diff --git a/packages/activities/src/activities/Invoke.ts b/packages/activities/src/activities/Invoke.ts index d815938b1..842e82a9d 100644 --- a/packages/activities/src/activities/Invoke.ts +++ b/packages/activities/src/activities/Invoke.ts @@ -1,33 +1,109 @@ -import { Token, Provider } from '@tsdi/ioc'; -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { Expression } from '../core/ActivityMetadata'; -import { IActivityContext } from '../core/IActivityContext'; -import { Activity } from '../core/Activity'; - - -/** - * while control activity. - * - * @export - * @class InvokeActivity - * @extends {ControlActivity} - */ -@Task('invoke') -export class InvokeActivity extends Activity { - - @Input() target: Expression; - - @Input() method: Expression; - - @Input() args: Expression; - - async execute(ctx: IActivityContext): Promise { - let target = await ctx.resolveExpression(this.target); - let method = await ctx.resolveExpression(this.method); - let args = await ctx.resolveExpression(this.args); - if (target && method) { - return ctx.injector.invoke(target, method, ...(args || [])); +import { Invocation, isFunction, isString, AbstractType } from '@tsdi/ioc'; +import { Attribute, Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; + +export type InvokeFn = (context: ActivityContext, ...args: any[]) => Promise; + + +@Component({ selector: 'invoke' }) +export class InvokeActivity extends Activity { + + + @Attribute() target: AbstractType | Invocation| undefined; + @Attribute() invoke!: string | InvokeFn; + @Attribute() maxAttempts!: number; + @Attribute() delay!: number; + @Attribute() backoff!: number; + + + + async execute(context: ActivityContext): Promise { + + let attempt = 0; + let lastError: Error | null = null; + + while (attempt < this.maxAttempts!) { + try { + // 如果不是第一次尝试,等待指定延迟 + if (attempt > 0) { + const delay = this.delay! * Math.pow(this.backoff!, attempt - 1); + await new Promise(resolve => setTimeout(resolve, delay)); + } + + const result = await this.invokeTarget(context); + return { + success: true, + data: result + }; + } catch (error) { + lastError = error as Error; + attempt++; + + // // 如果有自定义错误处理器,使用它 + // if (context.errorHandler) { + // try { + // const handledResult = await context.errorHandler(lastError); + // if (handledResult) { + // return handledResult; + // } + // } catch (handlerError) { + // // 错误处理器也失败了,继续重试 + // console.error('Error handler failed:', handlerError); + // } + // } + + // 如果是最后一次尝试,返回错误 + if (attempt >= this.maxAttempts!) { + return { + success: false, + error: lastError, + data: { + attempts: attempt, + lastError + } + }; + } + } } + + // 这里正常不会执行到,为了 TypeScript 类型检查 + return { + success: false, + error: new Error('Unexpected execution path') + }; } + + private async invokeTarget(context: ActivityContext): Promise { + let result: any; + + if (this.target && isString(this.invoke)) { + const invocation = this.target as Invocation; + // 调用活动 + const activityResult = await invocation.invoke(this.invoke, context); + if (!activityResult.success) { + throw activityResult.error || new Error('Activity execution failed'); + } + result = activityResult.data; + } else if(isFunction(this.invoke)) { + // 调用函数 + result = await this.invoke(context); + } + + // // 如果有结果转换函数,使用它 + // if (context.resultMapper) { + // result = context.resultMapper(result); + // } + + return result; + } + + // private isActivity(target: Activity | InvokeFn): target is Activity { + // return typeof (target as Activity).execute === 'function'; + // } + + // async compensate(context: ActivityContext): Promise { + // if (this.isActivity(context.target) && context.target.compensate) { + // await context.target.compensate(context); + // } + // } } diff --git a/packages/activities/src/activities/Parallel.ts b/packages/activities/src/activities/Parallel.ts index 5fb01adc5..e0a8d2644 100644 --- a/packages/activities/src/activities/Parallel.ts +++ b/packages/activities/src/activities/Parallel.ts @@ -1,35 +1,175 @@ -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { ActivityType } from '../core/ActivityMetadata'; -import { IActivityContext } from '../core/IActivityContext'; -import { ParallelExecutor } from '../core/ParallelExecutor'; -import { ControlActivity } from '../core/ControlActivity'; - - -/** - * parallel activity. - * - * @export - * @class ParallelActivity - * @extends {ControlActivity} - */ -@Task('parallel') -export class ParallelActivity extends ControlActivity { - - @Input() activities: ActivityType[]; - - /** - * execute parallel. - * - * @protected - * @returns {Promise} - * @memberof ParallelActivity - */ - async execute(ctx: IActivityContext): Promise { - if (ctx.injector.hasRegister(ParallelExecutor)) { - return await ctx.injector.getInstance(ParallelExecutor).run(act => ctx.getExector().runWorkflow(act).then(c => c.result), this.activities) - } else { - return await Promise.all(this.activities.map(act => ctx.getExector().runWorkflow(act).then(c => c.result))); +import { Injectable } from '@tsdi/ioc'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; +import { Attribute, Component } from '@tsdi/components'; + +export interface ParallelActivityContext extends ActivityContext { + /** + * 要并行执行的活动列表 + */ + activities: Activity[]; + /** + * 最大并发数 + */ + maxConcurrent?: number; + /** + * 是否等待所有活动完成 + */ + waitAll?: boolean; + /** + * 活动执行回调 + */ + onActivityComplete?: (activity: Activity, result: ActivityResult) => void; + /** + * 错误处理策略 + */ + errorStrategy?: 'continue' | 'stop' | 'throw'; +} + +export interface ParallelActivityOptions { + /** + * 默认最大并发数 + */ + defaultMaxConcurrent?: number; + /** + * 默认错误处理策略 + */ + defaultErrorStrategy?: 'continue' | 'stop' | 'throw'; +} + +@Component({ selector: 'parallel' }) +export class ParallelActivity extends Activity { + + /** + * 要并行执行的活动列表 + */ + @Attribute() activities: Activity[] = []; + + /** + * 最大并发数 + */ + @Attribute() maxConcurrent!: number; + /** + * 是否等待所有活动完成 + */ + @Attribute() waitAll = true; + /** + * 错误处理策略 + */ + @Attribute() errorStrategy: 'continue' | 'stop' | 'throw' = 'continue'; + + async execute(context: ParallelActivityContext): Promise { + if (!context.activities || context.activities.length === 0) { + return { + success: true, + data: { completed: true } + }; + } + + const maxConcurrent = this.maxConcurrent ?? 5; + const errorStrategy = this.errorStrategy; + const waitAll = this.waitAll; + + const results: Map = new Map(); + const errors: Error[] = []; + let completedCount = 0; + + try { + // 创建活动执行队列 + const queue = [...this.activities]; + const running = new Set>(); + + while (queue.length > 0 || running.size > 0) { + // 填充运行中的活动直到达到最大并发数 + while (queue.length > 0 && running.size < maxConcurrent!) { + const activity = queue.shift()!; + const promise = this.executeActivity(activity, context, results); + running.add(promise); + + promise.then(() => { + running.delete(promise); + completedCount++; + }).catch(error => { + running.delete(promise); + completedCount++; + errors.push(error); + }); + } + + // 等待至少一个活动完成 + if (running.size > 0) { + await Promise.race(running); + } + + // 根据错误策略决定是否继续 + if (errors.length > 0) { + switch (errorStrategy) { + case 'stop': + return { + success: false, + error: errors[0], + data: { + completed: false, + results, + errors + } + }; + case 'throw': + throw errors[0]; + } + } + } + + // 检查是否所有活动都完成 + const allCompleted = completedCount === context.activities.length; + const hasErrors = errors.length > 0; + + return { + success: !hasErrors, + data: { + completed: allCompleted, + results, + errors: hasErrors ? errors : undefined + } + }; + } catch (error) { + return { + success: false, + error: error as Error, + data: { + completed: false, + results, + errors: [error as Error] + } + }; } } + + private async executeActivity( + activity: Activity, + context: ParallelActivityContext, + results: Map + ): Promise { + try { + const result = await activity.execute(context); + results.set(activity, result); + context.onActivityComplete?.(activity, result); + } catch (error) { + const errorResult: ActivityResult = { + success: false, + error: error as Error + }; + results.set(activity, errorResult); + context.onActivityComplete?.(activity, errorResult); + throw error; + } + } + + async compensate(context: ParallelActivityContext): Promise { + // 并行执行所有活动的补偿操作 + const compensations = context.activities + .filter(activity => activity.compensate) + .map(activity => activity.compensate!(context)); + + await Promise.all(compensations); + } } diff --git a/packages/activities/src/activities/Process.ts b/packages/activities/src/activities/Process.ts new file mode 100644 index 000000000..714a56367 --- /dev/null +++ b/packages/activities/src/activities/Process.ts @@ -0,0 +1,83 @@ +import { Attribute, Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; + + + +@Component({ selector: 'process' }) +export class ProcessActivity extends Activity { + + /** + * 批处理大小 + */ + batchSize?: number; + /** + * 处理超时时间(毫秒) + */ + timeout?: number; + /** + * 进度回调函数 + */ + @Attribute() onProgress?: (progress: number, current: number, total: number) => void; + /** + * 错误处理函数 + */ + @Attribute() onError?: (error: Error) => Promise; + /** + * 验证函数 + */ + @Attribute() validator?: (data: any) => Promise; + + @Attribute() body!: Activity; + + + async execute(context: ActivityContext): Promise { + + try { + // 验证数据 + if (this.validator) { + const isValid = await this.validator(context); + if (!isValid) { + return { + success: false, + error: new Error('Data validation failed'), + data: { validationFailed: true } + }; + } + } + this.onProgress?.(0, 0, 1); + // 处理数据 + const result = await this.body.execute(context); + return { + success: true, + data: result + }; + } catch (error) { + // 如果有自定义错误处理器,使用它 + if (this.onError) { + try { + return await this.onError(error as Error); + } catch (handlerError) { + return { + success: false, + error: handlerError as Error, + data: { + originalError: error, + handlerError: handlerError + } + }; + } + } + + return { + success: false, + error: error as Error, + data: { + processed: false, + error: error as Error + } + }; + } + } + + +} \ No newline at end of file diff --git a/packages/activities/src/activities/Sequence.ts b/packages/activities/src/activities/Sequence.ts index 5c10e1aa6..8d372802a 100644 --- a/packages/activities/src/activities/Sequence.ts +++ b/packages/activities/src/activities/Sequence.ts @@ -1,23 +1,137 @@ -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { ActivityType } from '../core/ActivityMetadata'; -import { ControlActivity } from '../core/ControlActivity'; -import { IActivityContext } from '../core/IActivityContext'; - -/** - * sequence activity. - * - * @export - * @class SequenceActivity - * @extends {ControlActivity} - */ -@Task('sequence') -export class SequenceActivity extends ControlActivity { - - @Input() activities: ActivityType[]; - - async execute(ctx: IActivityContext): Promise { - await ctx.getExector().runActivity(this.activities); - return ctx.getData(); +import { Attribute, Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; + + +@Component({ selector: 'sequence' }) +export class SequenceActivity extends Activity { + + /** + * 要按顺序执行的活动列表 + */ + @Attribute() activities: Activity[] = []; + /** + * 是否在错误时继续执行 + */ + @Attribute() continueOnError?: boolean; + + @Attribute() onError?: (error: Error) => Promise; + + async execute(context: ActivityContext): Promise { + if (!this.activities || this.activities.length === 0) { + return { + success: true, + data: { completed: true } + }; + } + + const continueOnError = this.continueOnError; + const results: Map = new Map(); + const errors: Error[] = []; + let currentIndex = 0; + + try { + for (const activity of this.activities) { + try { + const result = await activity.execute(context); + results.set(activity, result); + + // 如果活动执行失败且不继续执行,返回错误 + if (!result.success && !continueOnError) { + return { + success: false, + error: result.error, + data: { + completed: false, + results, + errors: [result.error!], + lastActivity: activity + } + }; + } + + // 如果活动执行失败且继续执行,收集错误 + if (!result.success) { + errors.push(result.error!); + } + + currentIndex++; + } catch (error) { + // 如果有自定义错误处理器,使用它 + if (this.onError) { + try { + const handledResult = await this.onError(error as Error); + results.set(activity, handledResult); + + if (!handledResult.success && !continueOnError) { + return { + success: false, + error: handledResult.error, + data: { + completed: false, + results, + errors: [handledResult.error!], + lastActivity: activity + } + }; + } + } catch (handlerError) { + errors.push(handlerError as Error); + } + } else { + errors.push(error as Error); + } + + // 如果不继续执行,返回错误 + if (!continueOnError) { + return { + success: false, + error: error as Error, + data: { + completed: false, + results, + errors, + lastActivity: activity + } + }; + } + } + } + + // 检查是否所有活动都完成 + const allCompleted = currentIndex === this.activities.length; + const hasErrors = errors.length > 0; + + return { + success: !hasErrors, + data: { + completed: allCompleted, + results, + errors: hasErrors ? errors : undefined, + lastActivity: this.activities[currentIndex - 1] + } + }; + } catch (error) { + return { + success: false, + error: error as Error, + data: { + completed: false, + results, + errors: [error as Error], + lastActivity: this.activities[currentIndex] + } + }; + } + } + + + async compensate(context: ActivityContext): Promise { + // 按相反顺序执行所有活动的补偿操作 + const compensations =this.activities + .reverse() + .filter(activity => activity.compensate) + .map(activity => activity.compensate!(context)); + + await Promise.all(compensations); } } diff --git a/packages/activities/src/activities/Start.ts b/packages/activities/src/activities/Start.ts new file mode 100644 index 000000000..f8744a754 --- /dev/null +++ b/packages/activities/src/activities/Start.ts @@ -0,0 +1,14 @@ +import { Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; + + +@Component({ selector: 'start'}) +export class StartActivity extends Activity { + + async execute(context: ActivityContext): Promise { + return { + success: true, + data: { startTime: Date.now() } + }; + } +} diff --git a/packages/activities/src/activities/Switch.ts b/packages/activities/src/activities/Switch.ts index 578b0455b..5b4e4644a 100644 --- a/packages/activities/src/activities/Switch.ts +++ b/packages/activities/src/activities/Switch.ts @@ -1,47 +1,110 @@ -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { Expression, ActivityType } from '../core/ActivityMetadata'; -import { IActivityContext } from '../core/IActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; +import { Attribute, Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; +import { CaseActivity } from './Case'; -@Task('case') -export class CaseActivity extends ControlActivity { +@Component({ selector: 'switch' }) +export class SwitchActivity extends Activity { - @Input() caseKey: any; - @Input({ bindingType: 'dynamic' }) body: ActivityType; - async execute(ctx: IActivityContext): Promise { - await ctx.getExector().runActivity(this.body); - } -} + /** + * case 活动列表 + */ + @Attribute() cases: CaseActivity[] = []; + /** + * 默认活动(当所有 case 都不匹配时执行) + */ + defaultActivity?: Activity; + /** + * 错误处理函数 + */ + errorHandler?: (error: Error) => Promise; + /** + * 是否在第一个匹配的 case 后停止执行 + */ + breakOnMatch?: boolean; + + + async execute(context: ActivityContext): Promise { + if (!this.cases || this.cases.length === 0) { + return { + success: false, + error: new Error('No cases provided for switch activity') + }; + } + + const breakOnMatch = this.breakOnMatch; + const results: ActivityResult[] = []; + const errors: Error[] = []; + let matched = false; + + try { + // 执行所有 case + for (const caseActivity of this.cases) { + const result = await caseActivity.execute(context); + results.push(result); -/** - * Switch control activity. - * - * @export - * @class SwitchActivity - * @extends {ControlActivity} - */ -@Task('switch') -export class SwitchActivity extends ControlActivity { + // 检查是否有匹配的 case + if (result.data?.matched) { + matched = true; + if (breakOnMatch) { + break; + } + } - @Input() switch: Expression; + // 如果 case 执行失败,收集错误 + if (!result.success) { + errors.push(result.error!); + } + } - @Input(CaseActivity) cases: CaseActivity[]; + // 如果没有匹配的 case 且有默认活动,执行默认活动 + if (!matched && this.defaultActivity) { + const defaultResult = await this.defaultActivity.execute(context); + results.push(defaultResult); - @Input({ bindingType: 'dynamic' }) defaults: ActivityType; + if (!defaultResult.success) { + errors.push(defaultResult.error!); + } + } - async execute(ctx: IActivityContext): Promise { - let matchkey = await ctx.resolveExpression(this.switch); + // 返回执行结果 + return { + success: errors.length === 0, + error: errors.length > 0 ? errors[0] : undefined, + data: { + matched, + results, + errors: errors.length > 0 ? errors : undefined + } + }; + } catch (error) { + // 如果有自定义错误处理器,使用它 + return { + success: false, + error: error as Error + } + } + } + + async compensate(context: ActivityContext): Promise { + // 按相反顺序执行所有匹配的 case 的补偿操作 + const compensations: Promise[] = []; - let activity = this.cases.find(c => c.caseKey === matchkey); + // 找到最后一个匹配的 case + let lastMatchedCase: CaseActivity | undefined; - if (activity) { - await activity.execute(ctx); - } else if (this.defaults) { - await ctx.getExector().runActivity(this.defaults); + // 如果有匹配的 case,执行其补偿操作 + if (lastMatchedCase?.compensate) { + compensations.push(lastMatchedCase.compensate(context)); } + + // 如果有默认活动且没有匹配的 case,执行其补偿操作 + if (!lastMatchedCase && this.defaultActivity?.compensate) { + compensations.push(this.defaultActivity.compensate(context)); + } + + await Promise.all(compensations); } -} +} \ No newline at end of file diff --git a/packages/activities/src/activities/Throw.ts b/packages/activities/src/activities/Throw.ts index 0addcc216..90836dd9e 100644 --- a/packages/activities/src/activities/Throw.ts +++ b/packages/activities/src/activities/Throw.ts @@ -1,22 +1,70 @@ -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { Expression } from '../core/ActivityMetadata'; -import { IActivityContext } from '../core/IActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; - -/** - * throw control activity. - * - * @export - * @class ThrowActivity - * @extends {ControlActivity} - */ -@Task('[throw]') -export class ThrowActivity extends ControlActivity { - - @Input('throw') error: Expression; - - execute(ctx: IActivityContext): Promise { - return ctx.resolveExpression(this.error); +import { Injectable } from '@tsdi/ioc'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; +import { Component } from '@tsdi/components'; + +export interface ThrowActivityContext extends ActivityContext { + /** + * 要抛出的错误 + */ + error: Error | string; + /** + * 错误详情 + */ + details?: any; + /** + * 错误代码 + */ + code?: string | number; + /** + * 是否在抛出错误前执行补偿操作 + */ + compensateBeforeThrow?: boolean; +} + +export interface ThrowActivityOptions { + /** + * 默认错误代码 + */ + defaultErrorCode?: string | number; +} + +@Component({ selector: 'throw' }) +export class ThrowActivity extends Activity { + + async execute(context: ThrowActivityContext): Promise { + if (!context.error) { + return { + success: false, + error: new Error('No error specified to throw') + }; + } + + // 创建错误对象 + const error = typeof context.error === 'string' + ? new Error(context.error) + : context.error; + + // 添加错误代码和详情 + if (context.code || this.options.defaultErrorCode) { + (error as any).code = context.code ?? this.options.defaultErrorCode; + } + if (context.details) { + (error as any).details = context.details; + } + + return { + success: false, + error, + data: { + thrown: true, + timestamp: Date.now(), + code: (error as any).code, + details: (error as any).details + } + }; + } + + async compensate(context: ThrowActivityContext): Promise { + // ThrowActivity 不需要补偿操作 } } diff --git a/packages/activities/src/activities/Timer.ts b/packages/activities/src/activities/Timer.ts new file mode 100644 index 000000000..40a5c83ae --- /dev/null +++ b/packages/activities/src/activities/Timer.ts @@ -0,0 +1,260 @@ +import { Component, Attribute } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; + + +@Component({ selector: 'timer' }) +export class TimerActivity extends Activity { + + private timerId: any = null; + private intervalId: any = null; + private isRunning = false; + + /** + * 定时器类型:'timeout' | 'interval' | 'date' + */ + @Attribute() type!: 'timeout' | 'interval' | 'date'; + /** + * 延迟时间(毫秒,用于 timeout 和 interval) + */ + @Attribute() delay?: number; + /** + * 目标日期(用于 date 类型) + */ + @Attribute() targetDate?: Date; + /** + * 重复间隔(毫秒,用于 interval) + */ + @Attribute() interval?: number; + /** + * 最大重复次数(用于 interval) + */ + @Attribute() maxRepeats?: number; + /** + * 是否立即执行第一次(用于 interval) + */ + @Attribute() immediate?: boolean; + + + @Attribute() body!: Activity; + + async execute(context: ActivityContext): Promise { + if (!this.type) { + return { + success: false, + error: new Error('Timer type is required') + }; + } + + this.isRunning = true; + const executionCount = 0; + + try { + switch (this.type) { + case 'timeout': + return await this.executeTimeout(context); + case 'interval': + return await this.executeInterval(context, executionCount); + case 'date': + return await this.executeDate(context); + default: + return { + success: false, + error: new Error(`Unsupported timer type: ${this.type}`) + }; + } + } catch (error) { + return { + success: false, + error: error as Error, + data: { + type: this.type, + executionCount + } + }; + } + } + + private async executeTimeout(context: ActivityContext): Promise { + const delay = this.delay; + + return new Promise((resolve) => { + this.timerId = setTimeout(async () => { + try { + // if (context.callback) { + // await context.callback(context); + // } + // context.onComplete?.(); + resolve({ + success: true, + data: { + type: 'timeout', + delay, + executed: true + } + }); + } catch (error) { + resolve({ + success: false, + error: error as Error, + data: { + type: 'timeout', + delay, + executed: false + } + }); + } + }, delay); + }); + } + + private async executeInterval( + context: ActivityContext, + executionCount: number + ): Promise { + const interval = this.interval; + const immediate = this.immediate; + const maxRepeats = this.maxRepeats; + + return new Promise((resolve) => { + const executeInterval = async () => { + if (!this.isRunning) { + this.cleanup(); + resolve({ + success: true, + data: { + type: 'interval', + interval, + executionCount, + interrupted: true + } + }); + return; + } + + try { + await this.body.execute(context); + executionCount++; + + if (maxRepeats && executionCount >= maxRepeats) { + this.cleanup(); + // context.onComplete?.(); + resolve({ + success: true, + data: { + type: 'interval', + interval, + executionCount, + completed: true + } + }); + return; + } + + this.intervalId = setTimeout(executeInterval, interval); + } catch (error) { + this.cleanup(); + resolve({ + success: false, + error: error as Error, + data: { + type: 'interval', + interval, + executionCount + } + }); + } + }; + + if (immediate) { + executeInterval(); + } else { + this.intervalId = setTimeout(executeInterval, interval); + } + }); + } + + private async executeDate(context: ActivityContext): Promise { + if (!this.targetDate) { + return { + success: false, + error: new Error('Target date is required for date timer type') + }; + } + + const now = Date.now(); + const targetTime = this.targetDate.getTime(); + const delay = Math.max(0, targetTime - now); + + if (delay === 0) { + try { + // if (context.callback) { + // await context.callback(context); + // } + // context.onComplete?.(); + return { + success: true, + data: { + type: 'date', + targetDate: this.targetDate, + executed: true + } + }; + } catch (error) { + return { + success: false, + error: error as Error, + data: { + type: 'date', + targetDate: this.targetDate, + executed: false + } + }; + } + } + + return new Promise((resolve) => { + this.timerId = setTimeout(async () => { + try { + // if (context.callback) { + // await context.callback(context); + // } + // context.onComplete?.(); + resolve({ + success: true, + data: { + type: 'date', + targetDate: this.targetDate, + executed: true + } + }); + } catch (error) { + resolve({ + success: false, + error: error as Error, + data: { + type: 'date', + targetDate: this.targetDate, + executed: false + } + }); + } + }, delay); + }); + } + + async compensate(context: ActivityContext): Promise { + this.cleanup(); + } + + private cleanup() { + this.isRunning = false; + if (this.timerId) { + clearTimeout(this.timerId); + this.timerId = null; + } + if (this.intervalId) { + clearInterval(this.intervalId); + this.intervalId = null; + } + } +} diff --git a/packages/activities/src/activities/TimerActivity.ts b/packages/activities/src/activities/TimerActivity.ts deleted file mode 100644 index dd00b0c78..000000000 --- a/packages/activities/src/activities/TimerActivity.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { Expression } from '../core/ActivityMetadata'; -import { IActivityContext } from '../core/IActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; - - - -@Task('[timer]') -export class TimerActivity extends ControlActivity { - - @Input('timer') time: Expression; - - async execute(ctx: IActivityContext): Promise { - return await ctx.getExector().resolveExpression(this.time); - } -} diff --git a/packages/activities/src/activities/TryCatch.ts b/packages/activities/src/activities/TryCatch.ts index e781ec6cd..1ccde6fc2 100644 --- a/packages/activities/src/activities/TryCatch.ts +++ b/packages/activities/src/activities/TryCatch.ts @@ -1,56 +1,190 @@ -import { lang, Type } from '@tsdi/ioc'; -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { IActivityContext } from '../core/IActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; -import { ActivityType } from '../core/ActivityMetadata'; +import { Attribute, Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; -@Task('catch') -export class CatchActivity extends ControlActivity { +export interface TryCatchActivityContext extends ActivityContext { + /** + * try 块中的活动 + */ + tryActivity: Activity; + /** + * catch 块中的活动 + */ + catchActivity?: Activity|null; + /** + * finally 块中的活动 + */ + finallyActivity?: Activity|null; + /** + * 错误类型过滤器 + */ + errorTypes?: (new (...args: any[]) => Error)[]; + /** + * 错误处理函数 + */ + errorHandler?: (error: Error) => Promise; + /** + * 是否在 catch 块中重新抛出错误 + */ + rethrow?: boolean; +} + +export interface TryCatchActivityOptions { + /** + * finally 块中的活动 + */ + finallyActivity?: Activity|null; + /** + * 默认错误类型过滤器 + */ + defaultErrorTypes?: (new (...args: any[]) => Error)[]; + /** + * 默认是否重新抛出错误 + */ + defaultRethrow?: boolean; +} - @Input() error: Type; +@Component({ selector: 'try_catch'}) +export class TryCatchActivity extends Activity { - @Input({ bindingType: 'dynamic' }) body: ActivityType; + /** + * try 块中的活动 + */ + @Attribute() tryActivity!: Activity; + /** + * catch 块中的活动 + */ + @Attribute() catchActivity?: Activity|null; + /** + * finally 块中的活动 + */ + @Attribute() finallyActivity?: Activity|null; + /** + * 错误类型过滤器 + */ + @Attribute() errorTypes?: (new (...args: any[]) => Error)[]; - async execute(ctx: IActivityContext): Promise { - let err = ctx.getData(); - if (this.error && err && lang.getClass(err) === this.error) { - ctx.getExector().runActivity(this.body); - } else if (!this.error) { - ctx.getExector().runActivity(this.body); + + async execute(context: ActivityContext): Promise { + if (!this.tryActivity) { + return { + success: false, + error: new Error('Try activity is required') + }; } - } -} -/** - * while control activity. - * - * @export - * @class TryCatchActivity - * @extends {ControlActivity} - */ -@Task('try') -export class TryCatchActivity extends ControlActivity { + let tryResult: ActivityResult | null = null; + let catchResult: ActivityResult | null = null; + let finallyResult: ActivityResult | null = null; + let finallyErrorResult: ActivityResult | null = null; - @Input({ bindingType: 'dynamic' }) try: ActivityType; + try { + // 执行 try 块 + tryResult = await this.tryActivity.execute(context); + } catch (error) { + const caughtError = error as Error; - @Input('catchs', CatchActivity) - catchs: CatchActivity[]; + // 检查错误类型是否匹配 + const errorTypes = this.errorTypes; + const shouldCatch = errorTypes?.some(errorType => + caughtError instanceof errorType + ); - @Input({ bindingType: 'dynamic' }) finallies: ActivityType; + if (shouldCatch && this.catchActivity) { + // 执行 catch 块 + try { + catchResult = await this.catchActivity.execute({ + ...context, + error: caughtError + }); - async execute(ctx: IActivityContext): Promise { - try { - await ctx.getExector().runActivity(this.try); - } catch (err) { - if (this.catchs) { - await ctx.getExector().runActivity(this.catchs, err); + // // 如果配置了重新抛出,则抛出错误 + // if (this.rethrow ?? this.options.defaultRethrow) { + // throw caughtError; + // } + } catch (catchError) { + return { + success: false, + error: catchError as Error, + data: { + tryResult, + catchError: catchError as Error + } + }; + } + } else { + // 错误类型不匹配或没有 catch 块,重新抛出错误 + throw caughtError; } } finally { - if (this.finallies) { - await ctx.getExector().runActivity(this.finallies); + // 执行 finally 块 + if (this.finallyActivity) { + try { + finallyResult = await this.finallyActivity.execute(context); + } catch (finallyError) { + finallyErrorResult = { + success: false, + error: finallyError as Error, + data: { + tryResult, + catchResult, + finallyError: finallyError as Error + } + } as ActivityResult; + } } } + if(finallyErrorResult) { + return finallyErrorResult; + } + + // // 如果有自定义错误处理器,使用它处理任何错误 + // if (context.errorHandler) { + // const error = tryResult?.error || catchResult?.error || finallyResult?.error; + // if (error) { + // try { + // return await context.errorHandler(error); + // } catch (handlerError) { + // return { + // success: false, + // error: handlerError as Error, + // data: { + // tryResult, + // catchResult, + // finallyResult, + // handlerError: handlerError as Error + // } + // }; + // } + // } + // } + + // 返回执行结果 + return { + success: Boolean(tryResult?.success || catchResult?.success), + error: tryResult?.error || catchResult?.error || finallyResult?.error, + data: { + tryResult, + catchResult, + finallyResult + } + }; + } + + async compensate(context: TryCatchActivityContext): Promise { + // 按相反顺序执行补偿操作 + const compensations: Promise[] = []; + + if (context.finallyActivity?.compensate) { + compensations.push(context.finallyActivity.compensate(context)); + } + if (context.catchActivity?.compensate) { + compensations.push(context.catchActivity.compensate(context)); + } + if (context.tryActivity?.compensate) { + compensations.push(context.tryActivity.compensate(context)); + } + + await Promise.all(compensations); } } diff --git a/packages/activities/src/activities/While.ts b/packages/activities/src/activities/While.ts index 5e31e7b76..b29f020f4 100644 --- a/packages/activities/src/activities/While.ts +++ b/packages/activities/src/activities/While.ts @@ -1,40 +1,195 @@ -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { IActivityContext } from '../core/IActivityContext'; -import { ControlActivity } from '../core/ControlActivity'; -import { ConditionActivity } from './ConditionActivity'; -import { ActivityType } from '../core/ActivityMetadata'; -import { AsyncHandler } from '@tsdi/ioc'; -import { WorkflowContext } from '../core/WorkflowContext'; - -/** - * while control activity. - * - * @export - * @class WhileActivity - * @extends {ControlActivity} - */ -@Task('while') -export class WhileActivity extends ControlActivity { - - @Input() condition: ConditionActivity; - - @Input({ bindingType: 'dynamic' }) body: ActivityType; - - private action: AsyncHandler | AsyncHandler[]; - - async execute(ctx: IActivityContext): Promise { - let condition = await this.condition?.execute(ctx); - if (condition) { - if (!this.action) { - this.action = ctx.getExector().parseAction(this.body); - } - await ctx.getExector().execAction(this.action, async () => { - condition = await this.condition?.execute(ctx); - if (condition) { - await this.execute(ctx); +import { Attribute, Component } from '@tsdi/components'; +import { Activity, ActivityContext, ActivityResult } from './Activity'; + + +// export interface WhileActivityContext extends ActivityContext { +// /** +// * 循环条件函数 +// */ +// condition: (context: ActivityContext) => Promise; +// /** +// * 循环体活动 +// */ +// body: Activity; +// /** +// * 最大迭代次数 +// */ +// maxIterations?: number; +// /** +// * 迭代间隔(毫秒) +// */ +// interval?: number; +// /** +// * 迭代回调函数 +// */ +// onIteration?: (iteration: number, result: ActivityResult) => void; +// /** +// * 错误处理函数 +// */ +// errorHandler?: (error: Error, iteration: number) => Promise; +// /** +// * 是否在错误时继续执行 +// */ +// continueOnError?: boolean; +// /** +// * 是否在条件不满足时抛出错误 +// */ +// throwOnConditionFalse?: boolean; +// } + +// export interface WhileActivityOptions { +// /** +// * 默认最大迭代次数 +// */ +// defaultMaxIterations?: number; +// /** +// * 默认迭代间隔 +// */ +// defaultInterval?: number; +// /** +// * 默认是否在错误时继续执行 +// */ +// defaultContinueOnError?: boolean; +// /** +// * 默认是否在条件不满足时抛出错误 +// */ +// defaultThrowOnConditionFalse?: boolean; +// } + +@Component({ selector: 'while' }) +export class WhileActivity extends Activity { + private isRunning = false; // 添加运行状态标志 + + + /** + * 循环条件函数 + */ + @Attribute() condition!: (context: ActivityContext) => Promise; + /** + * 循环体活动 + */ + @Attribute() body!: Activity; + /** + * 最大迭代次数 + */ + @Attribute() maxIterations?: number; + /** + * 迭代间隔(毫秒) + */ + @Attribute() interval?: number; + + async execute(context: ActivityContext): Promise { + if (!this.condition) { + return { + success: false, + error: new Error('No condition provided for while loop') + }; + } + + if (!this.body) { + return { + success: false, + error: new Error('No body activity provided for while loop') + }; + } + + this.isRunning = true; + + const maxIterations = this.maxIterations; + const interval = this.interval ?? 0; + // const continueOnError = this.continueOnError ?? this.options.defaultContinueOnError; + // const throwOnConditionFalse = this.throwOnConditionFalse ?? this.options.defaultThrowOnConditionFalse; + + let iteration = 0; + const results: ActivityResult[] = []; + const errors: Error[] = []; + + try { + while (this.isRunning && iteration < maxIterations!) { + // 检查循环条件 + const shouldContinue = await this.condition(context); + if (!shouldContinue) { + // if (throwOnConditionFalse) { + // throw new Error(`Loop condition returned false at iteration ${iteration}`); + // } + break; } - }); + + try { + const result = await this.body.execute(context); + results.push(result); + // context.onIteration?.(iteration, result); + + // 优化错误处理逻辑 + if (!result.success) { + errors.push(result.error!); + // if (!continueOnError) { + // return this.createResult(false, result.error, iteration, results, errors); + // } + } + + // 等待间隔 + if (interval > 0 && this.isRunning) { + await new Promise(resolve => setTimeout(resolve, interval)); + } + + iteration++; + } catch (error) { + // 统一错误处理 + const err = error as Error; + errors.push(err); + + // if (context.errorHandler) { + // try { + // const handledResult = await context.errorHandler(err, iteration); + // results.push(handledResult); + // if (!handledResult.success && !continueOnError) { + // return this.createResult(false, handledResult.error, iteration, results, errors); + // } + // } catch (handlerError) { + // errors.push(handlerError as Error); + // } + // } + + // if (!continueOnError) { + // return this.createResult(false, err, iteration, results, errors); + // } + } + } + + // 统一结果返回 + if (iteration >= maxIterations!) { + return this.createResult(false, new Error(`Maximum iterations (${maxIterations}) reached`), iteration, results, errors); + } + + return this.createResult(errors.length === 0, undefined, iteration, results, errors.length ? errors : undefined); + } finally { + this.isRunning = false; + } + } + + private createResult( + success: boolean, + error?: Error, + iteration?: number, + results?: ActivityResult[], + errors?: Error[] + ): ActivityResult { + return { + success, + error, + data: { + iteration, + results, + errors + } + }; + } + + async compensate(context: ActivityContext): Promise { + this.isRunning = false; // 停止循环 + if (this.body?.compensate) { + await this.body.compensate(context); } } } diff --git a/packages/activities/src/activities/index.ts b/packages/activities/src/activities/index.ts index e2f5fbb65..815107ef7 100644 --- a/packages/activities/src/activities/index.ts +++ b/packages/activities/src/activities/index.ts @@ -1,25 +1,19 @@ -export * from './EqualsActivity'; -export * from './ExistsActvity'; - -export * from './ConditionActivity'; -export * from './TimerActivity'; - +export * from './Activity'; +export * from './Start'; +export * from './End'; +export * from './Conditional'; +export * from './Confirm'; +export * from './Process'; +export * from './Timer'; +export * from './TryCatch'; +export * from './While'; export * from './Parallel'; export * from './Sequence'; -export * from './ExpressionActivity'; -export * from './ExecuteActivity'; - -export * from './Confirm'; export * from './Delay'; export * from './Interval'; export * from './DoWhile'; -export * from './If'; -export * from './ElseIf'; -export * from './Else'; export * from './Invoke'; -export * from './Switch'; export * from './Throw'; -export * from './TryCatch'; -export * from './While'; -export * from './Each'; - +export * from './If'; +export * from './Case'; +export * from './Switch'; diff --git a/packages/activities/src/aop/RunAspect.ts b/packages/activities/src/aop/RunAspect.ts index 1c8bc52e9..8f87cc769 100644 --- a/packages/activities/src/aop/RunAspect.ts +++ b/packages/activities/src/aop/RunAspect.ts @@ -1,5 +1,6 @@ -import { Aspect, Joinpoint, AfterReturning } from '@tsdi/aop'; -import { RunState, WorkflowContext, ActivityRef } from '../core/WorkflowContext'; +import { Aspect, JoinPoint, AfterReturning } from '@tsdi/aop'; +import { Activity } from '../activities/Activity'; + /** @@ -9,7 +10,7 @@ import { RunState, WorkflowContext, ActivityRef } from '../core/WorkflowContext' * @class TaskLogAspect */ @Aspect({ - within: ActivityRef, + within: Activity, singleton: true }) export class RunAspect { @@ -19,19 +20,16 @@ export class RunAspect { } @AfterReturning('execution(*.run)') - afterRun(joinPoint: Joinpoint) { - - let ctx = joinPoint.args[0] as WorkflowContext; - let startup = ctx.getStartup(); - if (!startup) { - return; - } - switch (startup.state) { - case RunState.pause: - throw new Error('workflow paused!'); - case RunState.stop: - throw new Error('workflow stop!'); - } + afterRun(joinPoint: JoinPoint) { + + // const actRef = joinPoint.target as ActivityRef; + + // switch (actRef.state) { + // case RunState.pause: + // throw new Error('workflow paused!'); + // case RunState.stop: + // throw new Error('workflow stop!'); + // } } diff --git a/packages/activities/src/core/Activity.ts b/packages/activities/src/core/Activity.ts deleted file mode 100644 index b02bc7d01..000000000 --- a/packages/activities/src/core/Activity.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { isClass, Type, Abstract, ClassTypes } from '@tsdi/ioc'; -import { Input } from '@tsdi/components'; -import { Task } from '../decorators/Task'; -import { IActivityContext } from './IActivityContext'; -import { ActivityMetadata } from './ActivityMetadata'; - -@Abstract() -export abstract class Activity { - static ρCT: ClassTypes = 'activity'; - /** - * activity display name. - * - * @type {string} - * @memberof Activity - */ - @Input() name: string; - - abstract execute(ctx: TCtx): Promise; -} - - -/** - * target is activity class. - * - * @export - * @param {*} target - * @returns {target is Type} - */ -export function isAcitvityClass(target: any, ext?: (meta: ActivityMetadata) => boolean): target is Type { - if (!isClass(target)) { - return false; - } - let key = Task.toString(); - if (Reflect.hasOwnMetadata(key, target)) { - if (ext) { - return Reflect.getOwnMetadata(key, target).some(meta => meta && ext(meta)); - } - return true; - } - return false; -} diff --git a/packages/activities/src/core/ActivityContext.ts b/packages/activities/src/core/ActivityContext.ts deleted file mode 100644 index a40a851b7..000000000 --- a/packages/activities/src/core/ActivityContext.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { Injectable, Type, Refs, isToken, Token, SymbolType, isDefined } from '@tsdi/ioc'; -import { ICoreInjector } from '@tsdi/core'; -import { BuildContext, createContext } from '@tsdi/boot'; -import { ComponentContext, ITemplateContext } from '@tsdi/components'; -import { ActivityOption } from './ActivityOption'; -import { Activity } from './Activity'; -import { ActivityMetadata, Expression } from './ActivityMetadata'; -import { ACTIVITY_DATA, ACTIVITY_INPUT, ACTIVITY_ORIGIN_DATA } from './IActivityRef'; -import { ActivityExecutorToken, IActivityExecutor } from './IActivityExecutor'; -import { IActivityContext, CTX_BASEURL, CTX_RUN_SCOPE, CTX_RUN_PARENT } from './IActivityContext'; -import { IWorkflowContext, WorkflowContextToken } from './IWorkflowContext'; - - - -/** - * activity execute context. - * - * @export - * @class ActivityContext - */ -@Injectable -@Refs(Activity, BuildContext) -export class ActivityContext extends ComponentContext implements IActivityContext { - - /** - * activity input data. - */ - getInput(): T { - return this.context.getValue(ACTIVITY_INPUT) - ?? this.getParent()?.getContextValue(ACTIVITY_INPUT, input => this.setValue(ACTIVITY_INPUT, input)); - } - - /** - * activity process data. - */ - getData(): T { - return this.context.getValue(ACTIVITY_DATA) ?? this.getProcessData(); - } - - protected getProcessData() { - let data = this.context.getValue(CTX_RUN_PARENT)?.getValue(ACTIVITY_DATA) ?? this.runScope?.getValue(ACTIVITY_DATA) - isDefined(data) && this.setValue(ACTIVITY_DATA, data); - return data; - } - - /** - * activity parent origin process data. - */ - getOriginData(): T { - return this.context.getValue(ACTIVITY_ORIGIN_DATA) - ?? this.context.getValue(CTX_RUN_PARENT)?.getValue(ACTIVITY_ORIGIN_DATA) - ?? this.runScope?.getValue(ACTIVITY_ORIGIN_DATA) - ?? this.getParent()?.getContextValue(ACTIVITY_ORIGIN_DATA); - } - - /** - * annoation metadata. - */ - getAnnoation(): ActivityMetadata { - return super.getAnnoation(); - } - - - get baseURL() { - return this.context.getValue(CTX_BASEURL) ?? this.getBaseURL(); - } - protected getBaseURL() { - let url = this.getParent()?.getContextValue(CTX_BASEURL) ?? this.workflow?.baseURL; - url && this.setValue(CTX_BASEURL, url); - return url; - } - - get runScope(): IActivityContext { - return this.context.getValue(CTX_RUN_SCOPE) - ?? this.getParent()?.getContextValue(CTX_RUN_SCOPE, runsp => this.setValue(CTX_RUN_SCOPE, runsp)); - } - - get workflow(): IWorkflowContext { - return this.injector.getValue(WorkflowContextToken) - } - - get(token: Token): T { - let key = this.context.getTokenKey(token); - return this.getInstance(key); - } - - getInstance(key: SymbolType): T { - return this.context.getInstance(key) ?? this.workflow?.context.getInstance(key) ?? null; - } - - getValue(key: SymbolType): T { - return this.context.getValue(key) ?? this.workflow?.context.getValue(key) ?? null; - } - - private _executor: IActivityExecutor; - getExector(): IActivityExecutor { - if (!this._executor) { - this._executor = this.injector.getInstance(ActivityExecutorToken, { provide: ActivityContext, useValue: this }); - } - return this._executor; - } - - resolveExpression(express: Expression, injector?: ICoreInjector): Promise { - return this.getExector().resolveExpression(express, injector); - } - - - static parse(injector: ICoreInjector, target: Type | ActivityOption): ActivityContext { - return createContext(injector, ActivityContext, isToken(target) ? { module: target } : target); - } - -} - -export class ActivityTemplateContext extends ActivityContext implements ITemplateContext { - selector?: Type; -} diff --git a/packages/activities/src/core/ActivityExecutor.ts b/packages/activities/src/core/ActivityExecutor.ts deleted file mode 100644 index 270bc8613..000000000 --- a/packages/activities/src/core/ActivityExecutor.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { - Injectable, isArray, Type, isClass, isFunction, isPromise, ObjectMap, isDefined, - AsyncHandler, chain, isString -} from '@tsdi/ioc'; -import { ICoreInjector } from '@tsdi/core'; -import { BuilderServiceToken } from '@tsdi/boot'; -import { ComponentBuilderToken, ELEMENT_REFS } from '@tsdi/components'; -import { ActivityType, Expression } from './ActivityMetadata'; -import { IActivityRef, ACTIVITY_INPUT, ACTIVITY_DATA } from './IActivityRef'; -import { ActivityExecutorToken, IActivityExecutor } from './IActivityExecutor'; -import { ActivityOption } from './ActivityOption'; -import { isAcitvityRef, ActivityElementRef, IActivityElementRef, ActivityRef } from './WorkflowContext'; -import { ActivityContext } from './ActivityContext'; -import { Activity } from './Activity'; -import { IWorkflowContext } from './IWorkflowContext'; -import { CTX_RUN_PARENT } from './IActivityContext'; - - -/** - * activity executor. - * - * @export - * @class ActivityExecutor - * @implements {IActivityExecutor} - */ -@Injectable(ActivityExecutorToken) -export class ActivityExecutor implements IActivityExecutor { - - constructor(private context: ActivityContext) { - - } - - /** - * run activity in sub workflow. - * - * @template T - * @param {T} ctx - * @param ActivityType} activity - * @returns {Promise} - * @memberof IActivityExecutor - */ - runWorkflow(activity: ActivityType, data?: any): Promise { - let ctx = this.context.workflow; - let injector = ctx.injector; - if (isAcitvityRef(activity)) { - let nctx = ctx.clone() as T; - activity.context.setValue(ACTIVITY_INPUT, data); - return activity.run(nctx).then(() => nctx); - } else if (activity instanceof Activity) { - let actx = this.context.clone(); - return activity.execute(actx) - .then(v => { - let nctx = ctx.clone(); - nctx.setValue(ACTIVITY_DATA, v); - return nctx as T; - }); - } else if (isClass(activity)) { - return injector.getInstance(BuilderServiceToken).run({ type: activity, data: data }); - } else if (isFunction(activity)) { - let nctx = ctx.clone() as T; - return activity(nctx).then(() => nctx); - } else { - let md: Type; - if (!isString(activity) && isClass(activity.activity)) { - md = activity.activity; - } - - let option = { - type: md, - template: activity, - injector: injector, - parent: ctx, - data: data - }; - - return injector.getInstance(BuilderServiceToken).run(option); - } - } - - eval(expression: string, envOptions?: ObjectMap) { - if (!expression) { - return expression; - } - envOptions = envOptions || {}; - envOptions['ctx'] = this.context; - return this.context.componentProvider.getAstResolver() - .resolve(expression, this.context.injector, envOptions); - } - - async resolveExpression(express: Expression, injector?: ICoreInjector): Promise { - let ctx = this.context; - injector = injector || this.context.injector; - if (isClass(express)) { - let aref = await ctx.injector.getInstance(ComponentBuilderToken).resolve(express); - if (isAcitvityRef(aref)) { - await aref.run(ctx.workflow); - return aref.context.getData(); - } else { - return aref; - } - } else if (isFunction(express)) { - return await express(ctx); - } else if (isAcitvityRef(express)) { - await express.run(ctx.workflow); - return express.context.getData(); - } else if (isPromise(express)) { - return await express; - } - return express; - } - - async runActivity(activities: ActivityType | ActivityType[], input?: any, next?: () => Promise): Promise { - await this.execAction(this.parseAction(activities, input), next); - return this.context.getData(); - } - - async execAction(actions: AsyncHandler | AsyncHandler[], next?: () => Promise): Promise { - if (!isArray(actions)) { - return await actions(this.context.workflow as T, next); - } - if (actions.length < 1) { - if (next) { - return await next(); - } - return; - } - await chain(actions.filter(f => f), this.context.workflow, next); - } - - parseAction(activity: ActivityType | ActivityType[], input?: any): AsyncHandler | AsyncHandler[] { - if (isArray(activity)) { - return activity.filter(a => a).map(act => async (ctx: T, next?: () => Promise) => { - let handle = await this.buildActivity(act, input); - await handle(ctx, next); - }); - } else { - return async (ctx: T, next?: () => Promise) => { - let handle = await this.buildActivity(activity, input); - await handle(ctx, next); - } - } - } - - protected async buildActivity(activity: ActivityType, input: any): Promise> { - let ctx = this.context; - if (isAcitvityRef(activity)) { - activity.context.setValue(CTX_RUN_PARENT, ctx); - isDefined(input) && activity.context.setValue(ACTIVITY_INPUT, input); - return activity.toAction(); - } else if (activity instanceof Activity) { - let ref = this.context.injector.getValue(ELEMENT_REFS).get(activity) as IActivityElementRef; - if (ref instanceof ActivityRef) { - ref.context.setValue(CTX_RUN_PARENT, ctx); - } else { - ref = new ActivityElementRef(this.context, activity); - } - isDefined(input) && ref.context.setValue(ACTIVITY_INPUT, input); - return ref.toAction(); - } else if (isClass(activity)) { - let aref = await ctx.injector.getInstance(ComponentBuilderToken).resolve(activity) as IActivityRef; - aref.context.setValue(CTX_RUN_PARENT, ctx); - isDefined(input) && aref.context.setValue(ACTIVITY_INPUT, input); - return aref.toAction(); - } else if (isFunction(activity)) { - return activity; - } else if (activity) { - let md: Type; - if (!isString(activity) && isClass(activity.activity)) { - md = activity.activity; - } - let option = { - type: md, - template: activity, - parent: ctx - }; - let aref = await ctx.injector.getInstance(ComponentBuilderToken).resolve(option) as IActivityRef; - aref.context.setValue(CTX_RUN_PARENT, ctx); - isDefined(input) && aref.context.setValue(ACTIVITY_INPUT, input); - return aref.toAction(); - } - } -} diff --git a/packages/activities/src/core/ActivityMetadata.ts b/packages/activities/src/core/ActivityMetadata.ts deleted file mode 100644 index e4f1111e4..000000000 --- a/packages/activities/src/core/ActivityMetadata.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { Type, Token, Provider, ObjectMap, tokenId, AsyncHandler, TokenId } from '@tsdi/ioc'; -import { Binding, ElementTemplate, IComponentMetadata } from '@tsdi/components'; -import { Activity } from './Activity'; -import { IActivityContext } from './IActivityContext'; -import { IWorkflowContext } from './IWorkflowContext'; -import { IActivityRef } from './IActivityRef'; -import { Template } from '@tsdi/boot'; - - - -/** - * activity configuration. - * - * @export - * @interface ActivityConfigure - * @extends {RunnableConfigure} - * @template T - */ -export interface ActivityMetadata extends IComponentMetadata { - /** - * action name. - * - * @type {string} - * @memberof ActivityMetadata - */ - name?: string; - /** - * base url. - */ - baseURL?: string, - /** - * activities component template scope. - * - * @type {ActivityTemplate} - * @memberof ActivityMetadata - */ - template?: ActivityTemplate; - - /** - * activity deps types. - */ - deps?: Type[] - -} - -/** - * workflow id. - */ -export const WorkflowId: TokenId = tokenId('WORKFLOW_ID'); - -/** - * selectors. - * - * @export - * @enum {number} - */ -export enum Activities { - if = 'if', - elseif = 'elseif', - else = 'else', - dowhile = 'dowhile', - while = 'while', - switch = 'switch', - throw = 'throw', - try = 'try', - catch = 'catch', - invoke = 'invoke', - sequence = 'sequence', - parallel = 'parallel', - interval = 'interval', - each = 'each', - execute = 'execute' -} - -/** - * template option. - * - * @export - * @interface TemplateOption - * @template T - */ -export interface TemplateOption extends ElementTemplate, ObjectMap { - /** - * activity selector math the template option tag. - * - * @type {string} - * @memberof ConditionOption - */ - activity: string | Activities | Type; - - /** - * action name. - * - * @type {Expression} - * @memberof TemplateOption - */ - name?: Binding; - - /** - * component external attrs. - */ - externals?: Expression<{ - /** - * input data. - * - * @type {string} - * @memberof TemplateOption - */ - input?: Binding; - - /** - * input execute data. - * - * @type {string} - * @memberof TemplateOption - */ - data?: Binding; - }> -} - - -export interface InvokeTemplate extends TemplateOption { - target: Binding, - method: Binding, - args: Binding -} - -export interface ExecuteOption extends TemplateOption { - action: Binding<(ctx: IActivityContext, bind?: IActivityContext) => void | Promise>; -} - - -export interface BodyTemplate extends TemplateOption { - body: Binding; -} - -/** - * condition option. - * - * @export - * @interface ConditionTemplate - * @extends {ActivityOption} - */ -export interface ConditionTemplate extends TemplateOption, IConditionTemplate { -} - -export interface IExpressionTemplate { - /** - * expression - * - * @type {Expression} - * @memberof ExpressionOption - */ - expression: Binding; -} - -/** - * expression option. - * - * @export - * @interface ExpressionTemplate - * @extends {ActivityOption} - */ -export interface ExpressionTemplate extends TemplateOption, IExpressionTemplate { -} - - -export interface IConditionTemplate { - /** - * condition - * - * @type {Expression} - * @memberof ConditionOption - */ - condition: Binding>; -} - -export interface EachTeamplate extends BodyTemplate { - parallel?: Binding; - each: Binding>; -} - -/** - * timer template. - * - * @export - * @interface TimerTemplate - * @extends {BodyTemplate} - */ -export interface TimerTemplate extends BodyTemplate { - /** - * time. - * - * @type {Binding>} - * @memberof TimerOption - */ - time: Binding>; -} - - - -/** - * throw template. - * - * @export - * @interface ThrowTemplate - * @extends {TemplateOption} - */ -export interface ThrowTemplate extends TemplateOption { - throw: Binding>; -} - -export interface SwitchTemplate extends TemplateOption { - switch: Binding>; - cases: Binding; - defaults?: Binding; -} - -/** - * case template. - */ -export interface CaseTemplate extends BodyTemplate { - /** - * case - * - * @type {Binding} - * @memberof CaseTemplate - */ - case: Binding; -} - -export interface CatchTemplate extends BodyTemplate { - /** - * to catch typeof this error. - * - * @type {Type} - * @memberof CatchTemplate - */ - error: Binding>; -} - -export interface TryTemplate extends TemplateOption { - try: Binding; - catchs?: Binding; - finally?: Binding; -} - -export type ControlTemplate = Required | TemplateOption | ExecuteOption | ConditionTemplate | ExpressionTemplate | EachTeamplate | InvokeTemplate - | TimerTemplate | ThrowTemplate | SwitchTemplate | TryTemplate; - - -export type TemplateType = Template | Type | T | AsyncHandler; - -/** - * activity type. - */ -export type ActivityType = IActivityRef | Activity | TemplateType; - -/** - * activity template. - */ -export type ActivityTemplate = TemplateType | TemplateType[]; - -/** - * context expression. - */ -export type CtxExpression = T | Promise | Type> | IActivityRef | Type | ((ctx: TC, bind?: IActivityContext) => T | Promise) - -/** - * expression. - */ -export type Expression = CtxExpression; - diff --git a/packages/activities/src/core/ActivityOption.ts b/packages/activities/src/core/ActivityOption.ts deleted file mode 100644 index 74aded00e..000000000 --- a/packages/activities/src/core/ActivityOption.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { IBuildOption } from '@tsdi/boot'; -import { ActivityTemplate } from './ActivityMetadata'; - - -/** - * activity option. - * - * @export - * @interface ActivityOption - * @extends {BootOption} - */ -export interface ActivityOption extends IBuildOption { - /** - * name. - */ - name?: string; - /** - * activities component template scope. - * - * @type {ActivityTemplate} - * @memberof ActivityConfigure - */ - template?: ActivityTemplate; - - /** - * input data - */ - data?: any; -} diff --git a/packages/activities/src/core/ControlActivity.ts b/packages/activities/src/core/ControlActivity.ts deleted file mode 100644 index d0d10efd9..000000000 --- a/packages/activities/src/core/ControlActivity.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Activity } from './Activity'; - - -/** - * control - * - * @export - * @abstract - * @class ControlActivity - * @extends {Activity} - * @template T - */ -export abstract class ControlActivity extends Activity { - -} - diff --git a/packages/activities/src/core/IActivityContext.ts b/packages/activities/src/core/IActivityContext.ts deleted file mode 100644 index f57536a37..000000000 --- a/packages/activities/src/core/IActivityContext.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { tokenId, TokenId } from '@tsdi/ioc'; -import { IAnnoationContext } from '@tsdi/boot'; -import { IComponentContext } from '@tsdi/components'; -import { ActivityOption } from './ActivityOption'; -import { IWorkflowContext } from './IWorkflowContext'; -import { IActivityExecutor } from './IActivityExecutor'; -import { ICoreInjector } from '@tsdi/core'; -import { Expression, ActivityMetadata } from './ActivityMetadata'; - - -export const CTX_RUN_PARENT: TokenId = tokenId('CTX_RUN_PARENT'); -export const CTX_RUN_SCOPE = tokenId('CTX_RUN_SCOPE'); -export const CTX_BASEURL = tokenId('CTX_BASEURL'); - - -export interface IActivityContext extends IComponentContext { - /** - * activity input data. - */ - getInput(): T; - - /** - * activity process data. - */ - getData(): T; - - /** - * activity parent origin process data. - */ - getOriginData(): T; - - readonly baseURL: string; - - readonly runScope: IActivityContext; - - /** - * annoation metadata. - */ - getAnnoation(): ActivityMetadata; - - readonly workflow: IWorkflowContext; - - getExector(): IActivityExecutor; - - resolveExpression(express: Expression, injector?: ICoreInjector): Promise - -} diff --git a/packages/activities/src/core/IActivityExecutor.ts b/packages/activities/src/core/IActivityExecutor.ts deleted file mode 100644 index 2c3925a1d..000000000 --- a/packages/activities/src/core/IActivityExecutor.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { tokenId, AsyncHandler, TokenId } from '@tsdi/ioc'; -import { ICoreInjector } from '@tsdi/core'; -import { Expression, ActivityType } from './ActivityMetadata'; -import { WorkflowContext } from './WorkflowContext'; - -/** - * activity executor. - * - * @export - * @interface IActivityExecutor - */ -export interface IActivityExecutor { - /** - * eval expression. - * - * @param {string} expression - * @returns {*} - * @memberof IActivityExecutor - */ - eval(expression: string): any; - /** - * run activity in sub workflow. - * - * @template T - * @param {ActivityType} activities - * @returns {Promise} - * @memberof IActivityExecutor - */ - runWorkflow(activities: ActivityType, data?: any): Promise; - /** - * resolve expression. - * - * @template TVal - * @param {Expression} express - * @param {ICoreInjector} [injector] - * @returns {Promise} - * @memberof IActivityExecutor - */ - resolveExpression(express: Expression, injector?: ICoreInjector): Promise; - /** - * run activities. - * - * @param {(ActivityType | ActivityType[])} activities - * @param {*} [input] - * @param {() => Promise} [next] - * @returns {Promise} - * @memberof IActivityExecutor - */ - runActivity(activities: ActivityType | ActivityType[], input?: any, next?: () => Promise): Promise; - /** - * execute actions. - * - * @template T - * @param {(AsyncHandler | AsyncHandler[])} actions - * @param {() => Promise} [next] - * @returns {Promise} - * @memberof IActivityExecutor - */ - execAction(actions: AsyncHandler | AsyncHandler[], next?: () => Promise): Promise; - - /** - * parse activites to actions. - * - * @template T - * @param {(ActivityType | ActivityType[])} activities - * @param {*} [input] - * @returns {AsyncHandler} - * @memberof IActivityExecutor - */ - parseAction(activities: ActivityType | ActivityType[], input?: any): AsyncHandler | AsyncHandler[]; -} - -export const ActivityExecutorToken: TokenId = tokenId('ActivityExecutor'); diff --git a/packages/activities/src/core/IActivityRef.ts b/packages/activities/src/core/IActivityRef.ts deleted file mode 100644 index 40f33148e..000000000 --- a/packages/activities/src/core/IActivityRef.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AsyncHandler, IDestoryable, tokenId, TokenId } from '@tsdi/ioc'; -import { ActivityContext } from './ActivityContext'; -import { IWorkflowContext } from './IWorkflowContext'; -export const ACTIVITY_INPUT: TokenId = tokenId('ACTIVITY_INPUT'); -export const ACTIVITY_DATA = tokenId('ACTIVITY_DATA'); -export const ACTIVITY_ORIGIN_DATA = tokenId('ACTIVITY_ORIGIN_DATA'); - -export interface IActivityRef extends IDestoryable { - name?: string; - readonly isScope?: boolean; - readonly context: ActivityContext; - run(ctx: IWorkflowContext): Promise; - toAction(): AsyncHandler; -} diff --git a/packages/activities/src/core/IWorkflowContext.ts b/packages/activities/src/core/IWorkflowContext.ts deleted file mode 100644 index b2b15b6dc..000000000 --- a/packages/activities/src/core/IWorkflowContext.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { tokenId, TokenId } from '@tsdi/ioc'; -import { IBootContext } from '@tsdi/boot'; -import { ActivityOption } from './ActivityOption'; - -/** - * workflow context token. - */ -export const WorkflowContextToken: TokenId = tokenId('WorkflowContext'); - - -export interface IWorkflowContext extends IBootContext { - /** - * workflow id. - * - * @type {string} - * @memberof ActivityContext - */ - id: string; - /** - * action name. - * - * @type {string} - * @memberof ActivityOption - */ - name: string; - - readonly result: any; - -} diff --git a/packages/activities/src/core/ParallelExecutor.ts b/packages/activities/src/core/ParallelExecutor.ts deleted file mode 100644 index e5cd4a9ea..000000000 --- a/packages/activities/src/core/ParallelExecutor.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Abstract } from '@tsdi/ioc'; - -/** - * parallel executor. - * - * @export - * @abstract - * @class ParallelExecutor - */ -@Abstract() -export abstract class ParallelExecutor { - /** - * run parallel. - * - * @abstract - * @template T - * @param {(item: T) => any} func - * @param {T[]} items - * @param {...any[]} args - * @memberof ParallelExecutor - */ - abstract run(func: (item: T) => any, items: T[], ...args: any[]); -} diff --git a/packages/activities/src/core/WorkflowContext.ts b/packages/activities/src/core/WorkflowContext.ts deleted file mode 100644 index 145d3003c..000000000 --- a/packages/activities/src/core/WorkflowContext.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { lang, Abstract, IDestoryable, isFunction, Type, Inject, isString, Injectable, Refs, isDefined, tokenId, AsyncHandler, TokenId } from '@tsdi/ioc'; -import { CTX_TEMPLATE, CTX_ELEMENT_NAME, Service, Startup, BootContext } from '@tsdi/boot'; -import { - IElementRef, ITemplateRef, IComponentRef, ContextNode, ELEMENT_REFS, COMPONENT_REFS, - NodeSelector, CONTEXT_REF, NATIVE_ELEMENT, ROOT_NODES, COMPONENT_TYPE, COMPONENT_INST, TEMPLATE_REF, REFCHILD_SELECTOR -} from '@tsdi/components'; -import { CTX_RUN_SCOPE, CTX_RUN_PARENT, CTX_BASEURL } from './IActivityContext'; -import { ActivityContext, ActivityTemplateContext } from './ActivityContext'; -import { IActivityRef, ACTIVITY_DATA, ACTIVITY_INPUT, ACTIVITY_ORIGIN_DATA } from './IActivityRef'; -import { Activity } from './Activity'; -import { ControlActivity } from './ControlActivity'; -import { expExp } from '../utils/exps'; -import { ActivityOption } from './ActivityOption'; -import { TemplateOption } from './ActivityMetadata'; -import { IWorkflowContext } from './IWorkflowContext'; - - -/** - * each body token. - */ -export const CTX_CURR_ACT_REF: TokenId = tokenId('CTX_CURR_ACT_REF'); -/** - * each body token. - */ -export const CTX_CURR_ACTSCOPE_REF = tokenId('CTX_CURR_ACTSCOPE_REF'); - -/** - *run state. - * - * @export - * @enum {number} - */ -export enum RunState { - /** - * activity init. - */ - init, - /** - * runing. - */ - running, - /** - * activity parused. - */ - pause, - /** - * activity stopped. - */ - stop, - /** - * activity complete. - */ - complete -} - - - - -@Injectable -@Refs(Activity, BootContext) -export class WorkflowContext extends BootContext implements IWorkflowContext { - /** - * workflow id. - * - * @type {string} - * @memberof ActivityContext - */ - id: string; - /** - * action name. - * - * @type {string} - * @memberof ActivityOption - */ - name: string; - - get result() { - return this.getValue(ACTIVITY_DATA); - } - - getStartup(): WorkflowInstance { - return super.getStartup() as WorkflowInstance; - } - - setOptions(options: ActivityOption) { - if (!options) { - return this; - } - if (isDefined(options.data)) { - this.setValue(ACTIVITY_INPUT, options.data); - } - return super.setOptions(options); - } -} - - - - - -export interface IActivityElementRef extends IElementRef, IActivityRef { - -} - -export interface IActivityTemplateRef extends ITemplateRef, IActivityRef { - -} - -export interface IActivityComponentRef extends IComponentRef, IActivityRef { - -} - - -export type ActivityNodeType = Activity | IActivityElementRef; // | IActivityTemplateRef | IActivityComponentRef; - -@Abstract() -export abstract class ActivityRef extends ContextNode implements IActivityRef { - isScope?: boolean; - abstract readonly name: string; - protected abstract execute(ctx: IWorkflowContext): Promise; - - /** - * run activity. - * @param ctx root context. - */ - async run(ctx: IWorkflowContext): Promise { - let externals = await this.context.resolveExpression(this.context.getTemplate()?.externals); - let orginData = this.context.getData(); - if (externals) { - let input = externals.input; - if (isDefined(input)) { - if (isString(input) && expExp.test(input)) { - input = this.context.getExector().eval(input); - } - this.context.setValue(ACTIVITY_INPUT, input); - } - let data = externals.data; - if (isDefined(data)) { - this.context.setValue(ACTIVITY_ORIGIN_DATA, orginData); - if (isString(data) && expExp.test(data)) { - data = this.context.getExector().eval(data); - } - this.context.setValue(ACTIVITY_DATA, data); - this.context.setValue(CTX_RUN_SCOPE, this.context); - } - } - let result = await this.execute(ctx); - if (isDefined(result)) { - this.context.setValue(ACTIVITY_DATA, result); - if (this.context.hasValue(ACTIVITY_ORIGIN_DATA)) { - this.context.remove(CTX_RUN_SCOPE); - } else { - // console.log(this.context.name, 'set data to', this.context.getValue(CTX_RUN_PARENT)?.name); - // console.log(this.context.name, 'set data to', this.context.runScope?.name); - this.context.getValue(CTX_RUN_PARENT)?.setValue(ACTIVITY_DATA, result); - this.context.runScope?.setValue(ACTIVITY_DATA, result); - } - } - } - - private _actionFunc: AsyncHandler; - toAction(): AsyncHandler { - if (!this._actionFunc) { - this._actionFunc = async (ctx: IWorkflowContext, next?: () => Promise) => { - await this.run(ctx); - if (next) { - await next() - } - } - } - return this._actionFunc; - } -} - -@Injectable -export class ActivityElementRef extends ActivityRef implements IActivityElementRef { - - get name(): string { - return this.context.name; - } - - constructor( - @Inject(CONTEXT_REF) context: ActivityContext, - @Inject(NATIVE_ELEMENT) public readonly nativeElement: T) { - super(context); - let injector = context.injector; - if (!injector.has(ELEMENT_REFS)) { - injector.setValue(ELEMENT_REFS, new WeakMap()); - } - if (!this.context.name) { - this.context.setValue(CTX_ELEMENT_NAME, this.nativeElement.name ?? lang.getClassName(this.nativeElement)); - } - injector.getValue(ELEMENT_REFS).set(nativeElement, this); - this.onDestroy(() => injector.getValue(ELEMENT_REFS)?.delete(nativeElement)); - } - - /** - * run activity. - * @param ctx root context. - */ - protected execute(ctx: WorkflowContext): Promise { - return this.nativeElement.execute(this.context); - } - - protected destroying(): void { - let element = this.nativeElement as T & IDestoryable; - if (element && isFunction(element.destroy)) { - element.destroy(); - } - super.destroying(); - } -} - -@Injectable -export class ControlActivityElementRef extends ActivityElementRef { - -} - -@Injectable -export class ActivityTemplateRef extends ActivityRef implements IActivityTemplateRef { - readonly isScope = true; - get name(): string { - return `${this.context.name || ''}.template`; - } - - get template() { - return this.context.getValue(CTX_TEMPLATE); - } - - private _rootNodes: T[] - get rootNodes(): T[] { - return this._rootNodes; - } - - constructor( - @Inject(CONTEXT_REF) context: ActivityTemplateContext, - @Inject(ROOT_NODES) nodes: T[]) { - super(context); - this._rootNodes = nodes; - } - - - /** - * run activity. - * @param ctx root context. - */ - protected async execute(ctx: WorkflowContext): Promise { - this.context.setValue(CTX_RUN_SCOPE, this.context); - await this.context.getExector().runActivity(this.rootNodes); - this.context.remove(CTX_RUN_SCOPE); - return this.context.getData(); - } - - protected destroying(): void { - this.rootNodes - .forEach((node: T & IDestoryable) => { - if (node && isFunction(node.destroy)) { - node.destroy(); - } - }); - this._rootNodes = []; - super.destroying(); - } -} - - -/** - * activity ref for runtime. - */ -@Injectable -export class ActivityComponentRef extends ActivityRef implements IActivityComponentRef { - - get name(): string { - return this.context?.name; - } - - get selector() { - return this.context.getValue(REFCHILD_SELECTOR); - } - - constructor( - @Inject(COMPONENT_TYPE) public readonly componentType: Type, - @Inject(COMPONENT_INST) public readonly instance: T, - @Inject(CONTEXT_REF) context: ActivityContext, - @Inject(TEMPLATE_REF) public readonly nodeRef: IActivityTemplateRef - ) { - super(context); - if (!context.injector.has(COMPONENT_REFS)) { - context.injector.setValue(COMPONENT_REFS, new WeakMap()); - } - if (!context.name) { - context.set(CTX_ELEMENT_NAME, lang.getClassName(this.componentType)); - } - let baseURL = context.getAnnoation()?.baseURL; - baseURL && context.setValue(CTX_BASEURL, baseURL); - let injector = context.injector; - injector.getValue(COMPONENT_REFS).set(instance, this); - this.onDestroy(() => injector.getValue(COMPONENT_REFS)?.delete(this.instance)); - } - - getNodeSelector(): NodeSelector { - return new NodeSelector(this.nodeRef); - } - - /** - * run activity. - * @param ctx root context. - * @param next next work. - */ - protected async execute(ctx: WorkflowContext): Promise { - await this.nodeRef.run(ctx); - } -} - - - -/** - * is acitivty ref instance or not. - * - * @export - * @param {*} target - * @returns {target is Activity} - */ -export function isAcitvityRef(target: any): target is IActivityRef { - return target instanceof ActivityRef; -} - -/** - * task runner. - * - * @export - * @class TaskRunner - * @implements {ITaskRunner} - */ -@Injectable -@Refs(ActivityRef, Startup) -export class WorkflowInstance extends Service { - - async configureService(ctx: WorkflowContext): Promise { - this.context = ctx; - } - - getContext(): WorkflowContext { - return this.context as WorkflowContext; - } - - get result(): any { - return this.context.getValue(ACTIVITY_DATA); - } - - state: RunState; - - async start(data?: any): Promise { - let context = this.getContext(); - let injector = context.injector; - if (isDefined(data)) { - context.setValue(ACTIVITY_INPUT, data); - } - context.setValue(WorkflowInstance, this); - - if (context.id && !injector.has(context.id)) { - injector.setValue(context.id, this); - } - - let target = this.getBoot() as IActivityRef; - target.context.setValue(CTX_RUN_PARENT, context); - await target.run(context); - this.state = RunState.complete; - target.destroy(); - } - - async stop(): Promise { - this.state = RunState.stop; - } - - async pause(): Promise { - this.state = RunState.pause; - } - -} - diff --git a/packages/activities/src/core/uuid.ts b/packages/activities/src/core/uuid.ts deleted file mode 100644 index 27adee96b..000000000 --- a/packages/activities/src/core/uuid.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Singleton, tokenId, TokenId } from '@tsdi/ioc'; - -/** - * uuid factory. - * - * @export - * @interface UUIDFactory - */ -export interface UUIDFactory { - /** - * generate uuid. - * - * @returns {string} - * @memberof UUID - */ - generate(): string; -} - -/** - * uuid factory token. - */ -export const UUIDToken: TokenId = tokenId('uuid_factory'); - -/** - * random uuid factory. - * - * @export - * @class RandomUUIDFactory - * @implements {UUIDFactory} - */ -@Singleton(UUIDToken) -export class RandomUUIDFactory implements UUIDFactory { - constructor() { - - } - /** - * generate uuid. - * - * @returns {string} - * @memberof RandomUUID - */ - generate(): string { - return (this.randomS4() + this.randomS4() + '-' + this.randomS4() + '-' + this.randomS4() + '-' + this.randomS4() + '-' + this.randomS4() + this.randomS4() + this.randomS4()); - } - - protected randomS4() { - return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); - } -} diff --git a/packages/activities/src/decorators/Task.ts b/packages/activities/src/decorators/Task.ts deleted file mode 100644 index 5503080dc..000000000 --- a/packages/activities/src/decorators/Task.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { isString, createClassDecorator, isClass, lang, ClassType } from '@tsdi/ioc'; -import { ActivityMetadata } from '../core/ActivityMetadata'; - - -/** - * task decorator, use to define class is a task element. - * - * @export - * @interface ITaskDecorator - * @template T - */ -export interface ITaskDecorator { - /** - * Activity decorator, use to define class as Activity element. - * - * @Task - * - * @param {ActivityMetadata} [metadata] Activity metadate configure. - */ - (metadata?: ActivityMetadata): ClassDecorator; - - /** - * Activity decorator, use to define class as Activity element. - * - * @Task - * @param {string} selector metadata selector. - */ - (selector: string): ClassDecorator; - - /** - * task decorator, use to define class as task element. - * - * @Task - */ - (target: ClassType): void; -} - -/** - * task decorator, use to define class is a task element. - * - * @Task - */ -export const Task: ITaskDecorator = createClassDecorator('Task', - [ - (ctx, next) => { - if (isString(ctx.currArg)) { - ctx.metadata.selector = ctx.currArg; - ctx.next(next); - } - } - ], - metadata => { - if (!metadata.name && isClass(metadata.type)) { - metadata.name = lang.getClassName(metadata.type); - } - return metadata; - }) as ITaskDecorator; - diff --git a/packages/activities/src/decorators/confirm.decorator.ts b/packages/activities/src/decorators/confirm.decorator.ts new file mode 100644 index 000000000..e58ae91bc --- /dev/null +++ b/packages/activities/src/decorators/confirm.decorator.ts @@ -0,0 +1,49 @@ +import { createDecorator } from '@tsdi/ioc'; +import { ConfirmActivity, ConfirmActivityContext, ConfirmActivityOptions } from '../activities/Confirm'; + +export interface ConfirmDecoratorOptions extends Partial { + +} + +export const Confirm = createDecorator('Confirm', {}) + +// export function Confirm(options: ConfirmDecoratorOptions = {}) { +// return function ( +// target: any, +// propertyKey: string, +// descriptor: PropertyDescriptor +// ) { +// const originalMethod = descriptor.value; +// const activity = new ConfirmActivity(options as ConfirmActivityOptions); + +// descriptor.value = async function ( +// message: string, +// methodOptions?: { +// confirmText?: string; +// cancelText?: string; +// timeout?: number; +// defaultChoice?: boolean; +// onConfirm?: (confirmed: boolean) => void; +// onTimeout?: () => void; +// errorHandler?: (error: Error) => Promise; +// } +// ) { +// const context: ConfirmActivityContext = { +// message, +// options: { +// confirmText: methodOptions?.confirmText, +// cancelText: methodOptions?.cancelText, +// timeout: methodOptions?.timeout, +// defaultChoice: methodOptions?.defaultChoice +// }, +// onConfirm: methodOptions?.onConfirm, +// onTimeout: methodOptions?.onTimeout, +// errorHandler: methodOptions?.errorHandler +// }; + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/delay.decorator.ts b/packages/activities/src/decorators/delay.decorator.ts new file mode 100644 index 000000000..a02878c2c --- /dev/null +++ b/packages/activities/src/decorators/delay.decorator.ts @@ -0,0 +1,35 @@ +import { createDecorator } from '@tsdi/ioc'; +import { DelayActivityOptions } from '../activities/Delay'; + + + +export const Delay = createDecorator('Delay', {}); + +// export function Delay(options: DelayDecoratorOptions = {}) { +// return function ( +// target: any, +// propertyKey: string, +// descriptor: PropertyDescriptor +// ) { +// const originalMethod = descriptor.value; +// const activity = new DelayActivity(options); + +// descriptor.value = async function ( +// delay: number, +// methodOptions?: { +// interruptible?: boolean; +// onComplete?: () => void; +// } +// ) { +// const context: DelayActivityContext = { +// delay, +// interruptible: methodOptions?.interruptible, +// onComplete: methodOptions?.onComplete +// }; + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/do-while.decorator.ts b/packages/activities/src/decorators/do-while.decorator.ts new file mode 100644 index 000000000..e1257a74d --- /dev/null +++ b/packages/activities/src/decorators/do-while.decorator.ts @@ -0,0 +1,57 @@ +import { createDecorator } from '@tsdi/ioc'; +import { DoWhileActivityOptions } from '../activities/DoWhile'; + +export const DoWhile = createDecorator('DoWhile', {}); + + +// export interface DoWhileDecoratorOptions extends DoWhileActivityOptions { +// /** +// * 默认最大迭代次数 +// */ +// defaultMaxIterations?: number; +// /** +// * 默认迭代间隔 +// */ +// defaultInterval?: number; +// /** +// * 默认是否在错误时继续执行 +// */ +// defaultContinueOnError?: boolean; +// } + +// export function DoWhile(options: DoWhileDecoratorOptions = {}) { +// return function ( +// target: any, +// propertyKey: string, +// descriptor: PropertyDescriptor +// ) { +// const originalMethod = descriptor.value; +// const activity = new DoWhileActivity(options); + +// descriptor.value = async function ( +// body: Activity, +// condition: (context: ActivityContext) => Promise, +// methodOptions?: { +// maxIterations?: number; +// interval?: number; +// continueOnError?: boolean; +// onIteration?: (iteration: number, result: any) => void; +// errorHandler?: (error: Error, iteration: number) => Promise; +// } +// ) { +// const context: DoWhileActivityContext = { +// body, +// condition, +// maxIterations: methodOptions?.maxIterations, +// interval: methodOptions?.interval, +// continueOnError: methodOptions?.continueOnError, +// onIteration: methodOptions?.onIteration, +// errorHandler: methodOptions?.errorHandler +// }; + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/if.decorator.ts b/packages/activities/src/decorators/if.decorator.ts new file mode 100644 index 000000000..8d461b8e1 --- /dev/null +++ b/packages/activities/src/decorators/if.decorator.ts @@ -0,0 +1,37 @@ +import { createDecorator } from '@tsdi/ioc'; +import { IfActivityOptions } from '../activities/If'; + +export const If = createDecorator('If', {}); + +// export function If(options: IfActivityOptions = {}) { +// return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { +// const originalMethod = descriptor.value; + +// descriptor.value = async function (...args: any[]) { +// const activity = new IfActivity(options); +// const context: IfActivityContext = { +// condition: args[0], +// thenActivity: args[1], +// elseActivity: args[2], +// errorHandler: args[3]?.errorHandler +// }; + +// // 验证参数 +// if (typeof context.condition !== 'function') { +// throw new Error('First argument must be a condition function'); +// } + +// if (!context.thenActivity || typeof context.thenActivity.execute !== 'function') { +// throw new Error('Second argument must be an Activity instance for then branch'); +// } + +// if (context.elseActivity && typeof context.elseActivity.execute !== 'function') { +// throw new Error('Third argument must be an Activity instance for else branch'); +// } + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/index.ts b/packages/activities/src/decorators/index.ts new file mode 100644 index 000000000..d4b95b77e --- /dev/null +++ b/packages/activities/src/decorators/index.ts @@ -0,0 +1,15 @@ +export * from './workflow.decorator'; +export * from './invoke.decorator'; +export * from './parallel.decorator'; +export * from './sequence.decorator'; +export * from './throw.decorator'; +export * from './timer.decorator'; +export * from './try-catch.decorator'; +export * from './process.decorator'; +export * from './while.decorator'; +export * from './switch.decorator'; +export * from './if.decorator'; +export * from './confirm.decorator'; +export * from './delay.decorator'; +export * from './interval.decorator'; +export * from './do-while.decorator'; diff --git a/packages/activities/src/decorators/interval.decorator.ts b/packages/activities/src/decorators/interval.decorator.ts new file mode 100644 index 000000000..19056a0f6 --- /dev/null +++ b/packages/activities/src/decorators/interval.decorator.ts @@ -0,0 +1,85 @@ +import { createDecorator } from '@tsdi/ioc'; +import { IntervalActivityOptions } from '../activities/Interval'; + +export const Interval = createDecorator('Interval', {}); + +// export interface IntervalDecoratorOptions extends IntervalActivityOptions { +// /** +// * 默认执行间隔(毫秒) +// */ +// defaultInterval?: number; +// /** +// * 默认是否立即执行 +// */ +// defaultImmediate?: boolean; +// } + +// export function Interval(options: IntervalDecoratorOptions = {}) { +// return function ( +// target: any, +// propertyKey: string, +// descriptor: PropertyDescriptor +// ) { +// const originalMethod = descriptor.value; +// const activity = new IntervalActivity(options); + +// descriptor.value = async function ( +// action: Activity, +// methodOptions?: { +// interval?: number; +// maxExecutions?: number; +// immediate?: boolean; +// onExecution?: (execution: number, result: any) => void; +// onComplete?: () => void; +// } +// ) { +// const context: IntervalActivityContext = { +// action, +// interval: methodOptions?.interval, +// maxExecutions: methodOptions?.maxExecutions, +// immediate: methodOptions?.immediate, +// onExecution: methodOptions?.onExecution, +// onComplete: methodOptions?.onComplete +// }; + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } + +// export function createIntervalDecorator() { +// return function ( +// target: any, +// propertyKey: string, +// descriptor: PropertyDescriptor +// ) { +// const originalMethod = descriptor.value; +// const activity = new IntervalActivity(); + +// descriptor.value = async function ( +// action: Activity, +// options?: { +// interval?: number; +// maxExecutions?: number; +// immediate?: boolean; +// onExecution?: (execution: number, result: any) => void; +// onComplete?: () => void; +// } +// ) { +// const context: IntervalActivityContext = { +// action, +// interval: options?.interval, +// maxExecutions: options?.maxExecutions, +// immediate: options?.immediate, +// onExecution: options?.onExecution, +// onComplete: options?.onComplete +// }; + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/invoke.decorator.ts b/packages/activities/src/decorators/invoke.decorator.ts new file mode 100644 index 000000000..b57481876 --- /dev/null +++ b/packages/activities/src/decorators/invoke.decorator.ts @@ -0,0 +1,44 @@ +import { createDecorator } from '@tsdi/ioc'; +import { InvokeActivityOptions } from '../activities/Invoke'; + + +export const Invoke = createDecorator('Invoke', {}); + +// export function Invoke(options: InvokeActivityOptions = {}) { +// return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { +// const originalMethod = descriptor.value; + +// descriptor.value = async function (...args: any[]) { +// const activity = new InvokeActivity(options); +// const context: InvokeActivityContext = { +// target: args[0], +// args: args[1], +// resultMapper: args[2]?.resultMapper, +// errorHandler: args[2]?.errorHandler, +// retry: args[2]?.retry +// }; + +// // 验证参数 +// if (!context.target) { +// throw new Error('First argument must be an Activity instance or function'); +// } + +// // 验证重试配置 +// if (context.retry) { +// if (typeof context.retry.maxAttempts !== 'number' || context.retry.maxAttempts < 1) { +// throw new Error('Retry maxAttempts must be a positive number'); +// } +// if (typeof context.retry.delay !== 'number' || context.retry.delay < 0) { +// throw new Error('Retry delay must be a non-negative number'); +// } +// if (context.retry.backoff !== undefined && (typeof context.retry.backoff !== 'number' || context.retry.backoff < 1)) { +// throw new Error('Retry backoff must be a positive number'); +// } +// } + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/parallel.decorator.ts b/packages/activities/src/decorators/parallel.decorator.ts new file mode 100644 index 000000000..d30848a34 --- /dev/null +++ b/packages/activities/src/decorators/parallel.decorator.ts @@ -0,0 +1,48 @@ +import { createDecorator } from '@tsdi/ioc'; +import { ParallelActivityOptions } from '../activities/Parallel'; + + + +export const Parallel = createDecorator('Parallel', {}); + +// export function Parallel(options: ParallelActivityOptions = {}) { +// return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { +// const originalMethod = descriptor.value; + +// descriptor.value = async function (...args: any[]) { +// const activity = new ParallelActivity(options); +// const context: ParallelActivityContext = { +// activities: args[0], +// maxConcurrent: args[1]?.maxConcurrent, +// waitAll: args[1]?.waitAll, +// onActivityComplete: args[1]?.onActivityComplete, +// errorStrategy: args[1]?.errorStrategy +// }; + +// // 验证参数 +// if (!Array.isArray(context.activities)) { +// throw new Error('First argument must be an array of Activity instances'); +// } + +// if (context.activities.length === 0) { +// throw new Error('Activities array cannot be empty'); +// } + +// // 验证所有活动 +// context.activities.forEach((activity, index) => { +// if (!activity || typeof activity.execute !== 'function') { +// throw new Error(`Activity at index ${index} must be a valid Activity instance`); +// } +// }); + +// // 验证错误策略 +// if (context.errorStrategy && !['continue', 'stop', 'throw'].includes(context.errorStrategy)) { +// throw new Error('Error strategy must be one of: continue, stop, throw'); +// } + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/process.decorator.ts b/packages/activities/src/decorators/process.decorator.ts new file mode 100644 index 000000000..f28cd213b --- /dev/null +++ b/packages/activities/src/decorators/process.decorator.ts @@ -0,0 +1,36 @@ +import { createDecorator } from '@tsdi/ioc'; +import { ProcessActivityOptions } from '../activities/Process'; + + +export const Process = createDecorator('Process', {}); + +// export function Process(options: ProcessActivityOptions = {}) { +// return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { +// const originalMethod = descriptor.value; + +// descriptor.value = async function (...args: any[]) { +// const activity = new ProcessActivity(options); +// const context: ProcessActivityContext = { +// data: args[0], +// processor: args[1], +// onProgress: args[2]?.onProgress, +// errorHandler: args[2]?.errorHandler, +// validator: args[2]?.validator, +// options: args[2]?.options +// }; + +// // 验证参数 +// if (context.data === undefined) { +// throw new Error('First argument must be the data to process'); +// } + +// if (typeof context.processor !== 'function') { +// throw new Error('Second argument must be a processor function'); +// } + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/sequence.decorator.ts b/packages/activities/src/decorators/sequence.decorator.ts new file mode 100644 index 000000000..9c8d04807 --- /dev/null +++ b/packages/activities/src/decorators/sequence.decorator.ts @@ -0,0 +1,40 @@ +import { createDecorator } from '@tsdi/ioc'; +import { SequenceActivityOptions } from '../activities/Sequence'; + +export const Sequence = createDecorator('Sequence', {}); + +// export function Sequence(options: SequenceActivityOptions = {}) { +// return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { +// const originalMethod = descriptor.value; + +// descriptor.value = async function (...args: any[]) { +// const activity = new SequenceActivity(options); +// const context: SequenceActivityContext = { +// activities: args[0], +// continueOnError: args[1]?.continueOnError, +// onActivityComplete: args[1]?.onActivityComplete, +// errorHandler: args[1]?.errorHandler +// }; + +// // 验证参数 +// if (!Array.isArray(context.activities)) { +// throw new Error('First argument must be an array of Activity instances'); +// } + +// if (context.activities.length === 0) { +// throw new Error('Activities array cannot be empty'); +// } + +// // 验证所有活动 +// context.activities.forEach((activity, index) => { +// if (!activity || typeof activity.execute !== 'function') { +// throw new Error(`Activity at index ${index} must be a valid Activity instance`); +// } +// }); + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/switch.decorator.ts b/packages/activities/src/decorators/switch.decorator.ts new file mode 100644 index 000000000..64294dda8 --- /dev/null +++ b/packages/activities/src/decorators/switch.decorator.ts @@ -0,0 +1,72 @@ +import { createDecorator } from '@tsdi/ioc'; +import { SwitchActivityOptions } from '../activities/Switch'; +import { CaseActivityOptions } from '../activities/Case'; + +export const Switch = createDecorator('Switch', {}); + +export const Case = createDecorator('Case', {}); + +// export function Switch(options: SwitchActivityOptions = {}) { +// return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { +// const originalMethod = descriptor.value; + +// descriptor.value = async function (...args: any[]) { +// const activity = new SwitchActivity(options); +// const context: SwitchActivityContext = { +// cases: args[0] || [], +// defaultActivity: args[1], +// breakOnMatch: args[2]?.breakOnMatch, +// errorHandler: args[2]?.errorHandler +// }; + +// // 验证参数 +// if (!Array.isArray(context.cases)) { +// throw new Error('First argument must be an array of CaseActivity instances'); +// } + +// if (context.cases.length === 0 && !context.defaultActivity) { +// throw new Error('At least one case or a default activity must be provided'); +// } + +// // 验证所有 case 活动 +// context.cases.forEach((caseActivity, index) => { +// if (!(caseActivity instanceof CaseActivity)) { +// throw new Error(`Case at index ${index} must be a CaseActivity instance`); +// } +// }); + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } + +// export function Case(options: CaseActivityOptions = {}) { +// return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { +// const originalMethod = descriptor.value; + +// descriptor.value = async function (...args: any[]) { +// if (!args[0] || typeof args[0].execute !== 'function') { +// throw new Error('First argument must be an Activity instance'); +// } + +// const activity = new CaseActivity( +// async (ctx: any) => { +// try { +// return await originalMethod.apply(this, [ctx.data]); +// } catch (error) { +// console.error(`Error in case condition: ${error}`); +// return false; +// } +// }, +// args[0], +// options +// ); + +// return activity; +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/throw.decorator.ts b/packages/activities/src/decorators/throw.decorator.ts new file mode 100644 index 000000000..7abe86647 --- /dev/null +++ b/packages/activities/src/decorators/throw.decorator.ts @@ -0,0 +1,29 @@ +import { createDecorator } from '@tsdi/ioc'; +import { ThrowActivityOptions } from '../activities/Throw'; + +export const Throw = createDecorator('Throw', {}); + +// export function Throw(options: ThrowActivityOptions = {}) { +// return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { +// const originalMethod = descriptor.value; + +// descriptor.value = async function (...args: any[]) { +// const activity = new ThrowActivity(options); +// const context: ThrowActivityContext = { +// error: args[0], +// details: args[1], +// code: args[2], +// compensateBeforeThrow: args[3] +// }; + +// // 验证参数 +// if (!context.error) { +// throw new Error('First argument must be an error or error message'); +// } + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/timer.decorator.ts b/packages/activities/src/decorators/timer.decorator.ts new file mode 100644 index 000000000..be71d3087 --- /dev/null +++ b/packages/activities/src/decorators/timer.decorator.ts @@ -0,0 +1,46 @@ +import { createDecorator } from '@tsdi/ioc'; +import { TimerActivityOptions } from '../activities/Timer'; + +export const Timer = createDecorator('Timer', {}); + + +// export function Timer(options: TimerActivityOptions = {}) { +// return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { +// const originalMethod = descriptor.value; + +// descriptor.value = async function (...args: any[]) { +// const activity = new TimerActivity(options); +// const context: TimerActivityContext = { +// type: args[0], +// delay: args[1], +// targetDate: args[2], +// interval: args[3], +// maxRepeats: args[4], +// callback: args[5], +// immediate: args[6], +// onComplete: args[7] +// }; + +// // 验证参数 +// if (!context.type || !['timeout', 'interval', 'date'].includes(context.type)) { +// throw new Error('First argument must be a valid timer type: timeout, interval, or date'); +// } + +// if (context.type === 'date' && !context.targetDate) { +// throw new Error('Target date is required for date timer type'); +// } + +// if ((context.type === 'timeout' || context.type === 'interval') && !context.delay) { +// throw new Error('Delay is required for timeout and interval timer types'); +// } + +// if (context.type === 'interval' && context.maxRepeats !== undefined && context.maxRepeats < 0) { +// throw new Error('Max repeats must be a non-negative number'); +// } + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/try-catch.decorator.ts b/packages/activities/src/decorators/try-catch.decorator.ts new file mode 100644 index 000000000..8b2012c96 --- /dev/null +++ b/packages/activities/src/decorators/try-catch.decorator.ts @@ -0,0 +1,39 @@ +import { createDecorator } from '@tsdi/ioc'; +import { TryCatchActivityOptions } from '../activities/TryCatch'; + +export const TryCatch = createDecorator('TryCatch', {}); + +// export function TryCatch(options: TryCatchActivityOptions = {}) { +// return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { +// const originalMethod = descriptor.value; + +// descriptor.value = async function (...args: any[]) { +// const activity = new TryCatchActivity(options); +// const context: TryCatchActivityContext = { +// tryActivity: args[0], +// catchActivity: args[1], +// finallyActivity: args[2], +// errorTypes: args[3]?.errorTypes, +// errorHandler: args[3]?.errorHandler, +// rethrow: args[3]?.rethrow +// }; + +// // 验证参数 +// if (!context.tryActivity || typeof context.tryActivity.execute !== 'function') { +// throw new Error('First argument must be an Activity instance for try block'); +// } + +// if (context.catchActivity && typeof context.catchActivity.execute !== 'function') { +// throw new Error('Second argument must be an Activity instance for catch block'); +// } + +// if (context.finallyActivity && typeof context.finallyActivity.execute !== 'function') { +// throw new Error('Third argument must be an Activity instance for finally block'); +// } + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/while.decorator.ts b/packages/activities/src/decorators/while.decorator.ts new file mode 100644 index 000000000..5224f96bb --- /dev/null +++ b/packages/activities/src/decorators/while.decorator.ts @@ -0,0 +1,37 @@ +import { createDecorator } from '@tsdi/ioc'; +import { WhileActivityOptions } from '../activities/While'; + +export const While = createDecorator('While', {}); + +// export function While(options: WhileActivityOptions = {}) { +// return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { +// const originalMethod = descriptor.value; + +// descriptor.value = async function (...args: any[]) { +// const activity = new WhileActivity(options); +// const context: WhileActivityContext = { +// condition: args[0], +// body: args[1], +// maxIterations: args[2]?.maxIterations, +// interval: args[2]?.interval, +// onIteration: args[2]?.onIteration, +// errorHandler: args[2]?.errorHandler, +// continueOnError: args[2]?.continueOnError, +// throwOnConditionFalse: args[2]?.throwOnConditionFalse +// }; + +// // 验证参数 +// if (typeof context.condition !== 'function') { +// throw new Error('First argument must be a condition function'); +// } + +// if (!context.body || typeof context.body.execute !== 'function') { +// throw new Error('Second argument must be an Activity instance for loop body'); +// } + +// return await activity.execute(context); +// }; + +// return descriptor; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/decorators/workflow.decorator.ts b/packages/activities/src/decorators/workflow.decorator.ts new file mode 100644 index 000000000..e28759ac9 --- /dev/null +++ b/packages/activities/src/decorators/workflow.decorator.ts @@ -0,0 +1,33 @@ +import { createDecorator } from '@tsdi/ioc'; +import { WorkflowDefinition } from '../Workflow'; + + +export const Workflow = createDecorator>('Workflow', {}); + +// export function Workflow(options: WorkflowOptions = {}) { +// return function (target: any) { +// // 添加元数据 +// Reflect.defineMetadata('workflow', { +// name: options.name || target.name, +// description: options.description, +// version: options.version || '1.0.0', +// enableHistory: options.enableHistory ?? true, +// enableMonitoring: options.enableMonitoring ?? true +// }, target); + +// // 验证类是否实现了必要的方法 +// const prototype = target.prototype; +// const requiredMethods = ['execute', 'compensate']; + +// requiredMethods.forEach(method => { +// if (typeof prototype[method] !== 'function') { +// throw new Error(`Workflow class must implement ${method} method`); +// } +// }); + +// // 添加 Injectable 装饰器 +// Injectable()(target); + +// return target; +// }; +// } \ No newline at end of file diff --git a/packages/activities/src/index.ts b/packages/activities/src/index.ts index 59c5dbbf6..57c20da7c 100644 --- a/packages/activities/src/index.ts +++ b/packages/activities/src/index.ts @@ -1,27 +1,12 @@ -export * from './Workflow'; -export * from './ActivityProvider'; -export * from './ActivityModule'; -export * from './decorators/Task'; -export * from './utils/types'; -// core -export * from './core/IActivityContext'; -export * from './core/IWorkflowContext'; -export * from './core/IActivityRef'; -export * from './core/Activity'; -export * from './core/ControlActivity'; -export * from './core/IActivityExecutor'; -export * from './core/ActivityExecutor'; -export * from './core/ParallelExecutor'; -export * from './core/ActivityOption'; -export * from './core/ActivityMetadata'; -export * from './core/WorkflowContext'; -export * from './core/ActivityContext'; -export * from './core/uuid'; -export * from './registers/ActivityDepsRegister'; // aop export * from './aop/RunAspect'; export * from './activities'; +export * from './workflow.module'; + +export * from './Workflow'; +export * from './decorators'; +export * from './services'; \ No newline at end of file diff --git a/packages/activities/src/registers/ActivityDepsRegister.ts b/packages/activities/src/registers/ActivityDepsRegister.ts deleted file mode 100644 index b5f241fc8..000000000 --- a/packages/activities/src/registers/ActivityDepsRegister.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { DesignContext } from '@tsdi/ioc'; -import { ActivityMetadata } from '../core/ActivityMetadata'; - -export const ActivityDepsRegister = function (ctx: DesignContext, next: () => void): void { - let metas = ctx.reflects.getMetadata(ctx.currDecor, ctx.type); - let injector = ctx.injector; - if (metas.length) { - metas.forEach(m => { - if (m.deps && m.deps.length) { - injector.inject(...m.deps); - } - }); - } - next(); -}; - diff --git a/packages/activities/src/services/index.ts b/packages/activities/src/services/index.ts new file mode 100644 index 000000000..8e0ce30a0 --- /dev/null +++ b/packages/activities/src/services/index.ts @@ -0,0 +1,2 @@ +export * from './workflow.service'; +export * from './workflow.manager'; diff --git a/packages/activities/src/services/workflow.manager.ts b/packages/activities/src/services/workflow.manager.ts new file mode 100644 index 000000000..93cd86528 --- /dev/null +++ b/packages/activities/src/services/workflow.manager.ts @@ -0,0 +1,215 @@ +import { Injectable } from '@tsdi/ioc'; +import { WorkflowService, WorkflowExecutionContext } from './workflow.service'; +import { ActivityContext, ActivityResult } from '../activities/Activity'; +import { WorkflowDefinition } from '../Workflow'; + +export interface WorkflowStats { + totalWorkflows: number; + activeWorkflows: number; + completedWorkflows: number; + failedWorkflows: number; + averageExecutionTime: number; +} + +export interface WorkflowFilter { + status?: 'active' | 'completed' | 'failed'; + startTime?: Date; + endTime?: Date; + workflowId?: string; +} + +@Injectable() +export class WorkflowManager { + private workflowHistory: Map = new Map(); + private maxHistorySize = 1000; + + constructor(private workflowService: WorkflowService) {} + + /** + * 启动工作流 + * @param workflowDefinition 工作流定义类 + * @param context 工作流上下文 + * @returns 工作流执行结果 + */ + async startWorkflow( + workflowDefinition: new () => any, + context: T + ): Promise { + const result = await this.workflowService.startWorkflow(workflowDefinition, context); + + // 记录工作流历史 + if (result.data?.workflowId) { + const executionContext = this.workflowService.getActiveWorkflow(result.data.workflowId); + if (executionContext) { + this.addToHistory(executionContext); + } + } + + return result; + } + + /** + * 获取工作流统计信息 + */ + getWorkflowStats(): WorkflowStats { + const activeWorkflows = this.workflowService.getAllActiveWorkflows(); + const completedWorkflows = Array.from(this.workflowHistory.values()) + .filter(wf => wf.endTime && !wf.error); + const failedWorkflows = Array.from(this.workflowHistory.values()) + .filter(wf => wf.endTime && wf.error); + + const totalExecutionTimes = completedWorkflows + .map(wf => (wf.endTime! - wf.startTime!)) + .filter(time => time > 0); + + const averageExecutionTime = totalExecutionTimes.length > 0 + ? totalExecutionTimes.reduce((a, b) => a + b, 0) / totalExecutionTimes.length + : 0; + + return { + totalWorkflows: this.workflowHistory.size, + activeWorkflows: activeWorkflows.length, + completedWorkflows: completedWorkflows.length, + failedWorkflows: failedWorkflows.length, + averageExecutionTime + }; + } + + /** + * 查询工作流历史 + * @param filter 查询条件 + */ + queryWorkflows(filter: WorkflowFilter = {}): WorkflowExecutionContext[] { + let workflows = Array.from(this.workflowHistory.values()); + + if (filter.status) { + workflows = workflows.filter(wf => { + switch (filter.status) { + case 'active': + return !wf.endTime; + case 'completed': + return wf.endTime && !wf.error; + case 'failed': + return wf.endTime && wf.error; + } + }); + } + + if (filter.startTime) { + workflows = workflows.filter(wf => + wf.startTime && wf.startTime >= filter.startTime!.getTime() + ); + } + + if (filter.endTime) { + workflows = workflows.filter(wf => + wf.endTime && wf.endTime <= filter.endTime!.getTime() + ); + } + + if (filter.workflowId) { + workflows = workflows.filter(wf => wf.workflowId === filter.workflowId); + } + + return workflows; + } + + /** + * 获取工作流详情 + * @param workflowId 工作流ID + */ + getWorkflowDetails(workflowId: string): WorkflowExecutionContext | undefined { + return this.workflowHistory.get(workflowId) || + this.workflowService.getActiveWorkflow(workflowId); + } + + /** + * 取消工作流 + * @param workflowId 工作流ID + */ + async cancelWorkflow(workflowId: string): Promise { + await this.workflowService.cancelWorkflow(workflowId); + } + + /** + * 重试失败的工作流 + * @param workflowId 工作流ID + */ + async retryWorkflow(workflowId: string): Promise { + const workflow = this.workflowHistory.get(workflowId); + if (!workflow) { + throw new Error(`Workflow ${workflowId} not found in history`); + } + + if (!workflow.error) { + throw new Error(`Workflow ${workflowId} did not fail`); + } + + // 创建新的上下文,保留原始数据但重置状态 + const newContext: WorkflowExecutionContext = { + ...workflow, + workflowId: undefined, // 将生成新的ID + startTime: undefined, + endTime: undefined, + error: undefined, + currentState: undefined, + states: new Map() + }; + + return this.startWorkflow(workflow.constructor as new () => any, newContext); + } + + /** + * 清理过期的工作流历史 + * @param maxAge 最大保留时间(毫秒) + */ + cleanupHistory(maxAge: number = 24 * 60 * 60 * 1000): void { + const now = Date.now(); + for (const [id, workflow] of this.workflowHistory.entries()) { + if (workflow.endTime && (now - workflow.endTime > maxAge)) { + this.workflowHistory.delete(id); + } + } + } + + /** + * 导出工作流历史 + * @param format 导出格式 + */ + exportHistory(format: 'json' | 'csv' = 'json'): string { + const workflows = Array.from(this.workflowHistory.values()); + + if (format === 'csv') { + const headers = ['workflowId', 'startTime', 'endTime', 'status', 'error']; + const rows = workflows.map(wf => [ + wf.workflowId, + wf.startTime, + wf.endTime, + wf.error ? 'failed' : 'completed', + wf.error?.message || '' + ]); + + return [ + headers.join(','), + ...rows.map(row => row.join(',')) + ].join('\n'); + } + + return JSON.stringify(workflows, null, 2); + } + + private addToHistory(workflow: WorkflowExecutionContext): void { + if (workflow.workflowId) { + this.workflowHistory.set(workflow.workflowId, workflow); + + // 如果历史记录超过最大限制,删除最旧的记录 + if (this.workflowHistory.size > this.maxHistorySize) { + const oldestWorkflow = Array.from(this.workflowHistory.entries()) + .sort(([, a], [, b]) => (a.startTime || 0) - (b.startTime || 0))[0]; + if (oldestWorkflow) { + this.workflowHistory.delete(oldestWorkflow[0]); + } + } + } + } +} \ No newline at end of file diff --git a/packages/activities/src/services/workflow.service.ts b/packages/activities/src/services/workflow.service.ts new file mode 100644 index 000000000..564686e1d --- /dev/null +++ b/packages/activities/src/services/workflow.service.ts @@ -0,0 +1,151 @@ +import { Injectable } from '@tsdi/ioc'; +import { Activity, ActivityContext, ActivityResult } from '../activities/Activity'; +import { WorkflowDefinition } from '../Workflow'; + +export interface WorkflowExecutionContext extends ActivityContext { + workflowId?: string; + startTime?: number; + endTime?: number; + currentState?: string; + states: Map; +} + +@Injectable() +export class WorkflowService { + private activeWorkflows: Map = new Map(); + + async startWorkflow( + workflowDefinition: new () => any, + context: T + ): Promise { + const workflow = new workflowDefinition(); + const workflowId = this.generateWorkflowId(); + const executionContext: WorkflowExecutionContext = { + ...context, + workflowId, + startTime: Date.now(), + currentState: workflowDefinition.prototype.initialState, + states: new Map() + }; + + this.activeWorkflows.set(workflowId, executionContext); + + try { + const result = await this.executeWorkflow(workflow, executionContext); + executionContext.endTime = Date.now(); + return result; + } catch (error) { + return { + success: false, + error: error as Error, + data: { + workflowId, + executionContext + } + }; + } finally { + this.activeWorkflows.delete(workflowId); + } + } + + private async executeWorkflow( + workflow: any, + context: WorkflowExecutionContext + ): Promise { + const definition = this.getWorkflowDefinition(workflow); + let currentState = context.currentState; + + while (currentState) { + const activity = this.getActivity(workflow, currentState); + if (!activity) { + throw new Error(`No activity found for state: ${currentState}`); + } + + const result = await activity.execute(context); + context.states.set(currentState, result); + + if (!result.success) { + return result; + } + + const nextState = this.getNextState(definition, currentState, context); + if (!nextState) { + return { + success: true, + data: { + workflowId: context.workflowId, + states: context.states, + completed: true + } + }; + } + + currentState = nextState; + } + + return { + success: true, + data: { + workflowId: context.workflowId, + states: context.states, + completed: true + } + }; + } + + private getWorkflowDefinition(workflow: any): WorkflowDefinition { + const definition = Reflect.getMetadata('workflow', workflow.constructor); + if (!definition) { + throw new Error('Invalid workflow definition'); + } + return definition; + } + + private getActivity(workflow: any, state: string): Activity | undefined { + const definition = this.getWorkflowDefinition(workflow); + const activityClass = definition.activities.find(a => a.name === state); + if (!activityClass) { + return undefined; + } + + return workflow[state.toLowerCase()]; + } + + private getNextState( + definition: WorkflowDefinition, + currentState: string, + context: WorkflowExecutionContext + ): string | undefined { + const transition = definition.transitions.find(t => + t.from === currentState && + (!t.condition || t.condition(context)) + ); + return transition?.to; + } + + private generateWorkflowId(): string { + return `wf_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + getActiveWorkflow(workflowId: string): WorkflowExecutionContext | undefined { + return this.activeWorkflows.get(workflowId); + } + + getAllActiveWorkflows(): WorkflowExecutionContext[] { + return Array.from(this.activeWorkflows.values()); + } + + async cancelWorkflow(workflowId: string): Promise { + const workflow = this.activeWorkflows.get(workflowId); + if (workflow) { + // 执行补偿操作 + const currentState = workflow.currentState; + if (currentState) { + const activity = this.getActivity(workflow, currentState); + if (activity?.compensate) { + await activity.compensate(workflow); + } + } + } + } +} diff --git a/packages/activities/src/utils/exps.ts b/packages/activities/src/utils/exps.ts deleted file mode 100644 index ea6c7d758..000000000 --- a/packages/activities/src/utils/exps.ts +++ /dev/null @@ -1 +0,0 @@ -export const expExp = /(\w+\.\w+)|(\s\|\s\w+$)/; diff --git a/packages/activities/src/utils/types.ts b/packages/activities/src/utils/types.ts deleted file mode 100644 index 8cda2c3ea..000000000 --- a/packages/activities/src/utils/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { IContainer } from '@tsdi/core'; - - -/** - * source - */ -export type Src = string | string[]; - -/** - * task source. - */ -export type TaskSrc = Src | ((container?: IContainer) => Src); - -/** - * node callback - * - * @export - * @interface NodeCabllback - */ -export interface NodeCabllback { - (err: Error, data?: any): Promise | void -} diff --git a/packages/activities/src/utils/util.ts b/packages/activities/src/utils/util.ts new file mode 100644 index 000000000..20f2f793c --- /dev/null +++ b/packages/activities/src/utils/util.ts @@ -0,0 +1,15 @@ +import { isFunction } from '@tsdi/ioc'; +import { lastValueFrom, Observable } from 'rxjs'; +import { ActivityContext } from '../activities/Activity'; + + +export async function evaluateValue(value: T | Promise | Observable | ((context: ActivityContext) => T | Promise | Observable), context: ActivityContext): Promise { + value = isFunction(value) ? value(context) : value; + if (value instanceof Promise) { + return await value; + } else if (value instanceof Observable) { + return await lastValueFrom(value); + } else { + return value as T; + } +} diff --git a/packages/activities/src/workflow.module.ts b/packages/activities/src/workflow.module.ts new file mode 100644 index 000000000..7485c607e --- /dev/null +++ b/packages/activities/src/workflow.module.ts @@ -0,0 +1,61 @@ +import { Module } from '@tsdi/ioc'; +import { AopModule } from '@tsdi/aop'; +import { ComponentsModule } from '@tsdi/components'; +import { RunAspect } from './aop/RunAspect'; +import { WorkflowService } from './services/workflow.service'; +import { + ConfirmActivity, DelayActivity, DoWhileActivity, EndActivity, IntervalActivity, + InvokeActivity, ParallelActivity, SequenceActivity, StartActivity, ThrowActivity, TimerActivity, + TryCatchActivity, WhileActivity +} from './activities'; + + + +/** + * setup wokflow activity module for application. + * + * @export + */ +@Module({ + imports: [ + AopModule, + ComponentsModule + ], + providers: [ + WorkflowService, + RunAspect + ], + declarations: [ + StartActivity, + EndActivity, + ConfirmActivity, + DelayActivity, + WhileActivity, + DoWhileActivity, + IntervalActivity, + InvokeActivity, + ParallelActivity, + SequenceActivity, + ThrowActivity, + TimerActivity, + TryCatchActivity + ], + exports: [ + StartActivity, + EndActivity, + ConfirmActivity, + DelayActivity, + WhileActivity, + DoWhileActivity, + IntervalActivity, + InvokeActivity, + ParallelActivity, + SequenceActivity, + ThrowActivity, + TimerActivity, + TryCatchActivity + ] +}) +export class WorkflowModule { + +} diff --git a/packages/activities/taskfile.ts b/packages/activities/taskfile.ts index 914364956..80e9463f5 100644 --- a/packages/activities/taskfile.ts +++ b/packages/activities/taskfile.ts @@ -1,33 +1,58 @@ import { PackModule, LibPackBuilderOption } from '@tsdi/pack'; -import { Workflow, Task } from '@tsdi/activities'; -import { ServerActivitiesModule } from '@tsdi/platform-server-activities'; +import { ServerActivitiesModule } from '@tsdi/platform-server/activities'; +import { Component } from '@tsdi/components'; +import { Application } from '@tsdi/core'; +import { ActivityModule } from './src/workflow.module'; +import { WorkflowInstance } from './src/Workflow'; -@Task({ - deps: [ - PackModule, - ServerActivitiesModule - ], - baseURL: __dirname, - template: { - activity: 'libs', - outDir: '../../dist/activities', - src: 'src/**/*.ts', - test: 'test/**/*.ts', - annotation: true, - // sourcemap: true, - bundles: [ - { target: 'es5', targetFolder: 'src', dtsMain: 'index.d.ts'}, - { input: 'src/index.js', moduleName: 'main', moduleFolder: 'bundles', outputFile: 'activities.umd.js', format: 'umd', uglify: true }, - { input: 'src/index.js', moduleName: ['fesm5', 'esm5'], outputFile: 'activities.js', format: 'cjs' }, - { target: 'es2017', input: 'es2017/index.js', moduleName: ['fesm2017', 'esm2017'], outputFile: 'activities.js', format: 'cjs' } - ] - } -}) -export class ActivitiesBuilder { -} +// @Module({ +// imports: [ +// PackModule, +// ServerActivitiesModule, +// ActivityModule.withOptions({ +// template: [ +// { +// activity: 'libs', +// outDir: '../../dist/activities', +// src: 'src/**/*.ts', +// test: 'test/**/*.ts', +// annotation: true, +// // sourcemap: true, +// bundles: [ +// { target: 'es5', targetFolder: 'src', dtsMain: 'index.d.ts' }, +// { input: 'src/index.js', moduleName: 'main', moduleFolder: 'bundles', outputFile: 'activities.umd.js', format: 'umd', uglify: true }, +// { input: 'src/index.js', moduleName: ['fesm5', 'esm5'], outputFile: 'activities.js', format: 'cjs' }, +// { target: 'es2017', input: 'es2017/index.js', moduleName: ['fesm2017', 'esm2017'], outputFile: 'activities.js', format: 'cjs' } +// ] +// } +// ] +// }) +// ], +// baseURL: __dirname, +// }) +// export class ActivitiesBuilder { +// } if (process.cwd() === __dirname) { - Workflow.run(ActivitiesBuilder); + WorkflowInstance.run({ + imports:[PackModule, ServerActivitiesModule], + template: [ + { + activity: 'libs', + outDir: '../../dist/activities', + src: 'src/**/*.ts', + test: 'test/**/*.ts', + annotation: true, + // sourcemap: true, + bundles: [ + { target: 'es5', targetFolder: 'src', dtsMain: 'index.d.ts' }, + { input: 'src/index.js', moduleName: 'main', moduleFolder: 'bundles', outputFile: 'activities.umd.js', format: 'umd', uglify: true }, + { input: 'src/index.js', moduleName: ['fesm5', 'esm5'], outputFile: 'activities.js', format: 'cjs' }, + { target: 'es2017', input: 'es2017/index.js', moduleName: ['fesm2017', 'esm2017'], outputFile: 'activities.js', format: 'cjs' } + ] + } + ] + }); } diff --git a/packages/activities/test/simples.task.ts b/packages/activities/test/simples.task.ts index 1738b1be8..58b5a376c 100644 --- a/packages/activities/test/simples.task.ts +++ b/packages/activities/test/simples.task.ts @@ -1,124 +1,169 @@ -import { Task, Activity, ActivityContext, Activities } from '../src'; -import { IContainer, ContainerToken } from '@tsdi/core'; -import { Inject, isString, isFunction, Token } from '@tsdi/ioc'; -import { ServerActivitiesModule } from '@tsdi/platform-server-activities'; -import { Input } from '@tsdi/components'; - -@Task('stest') -export class SimpleTask extends Activity { - async execute(ctx: ActivityContext): Promise { - // console.log('before simple task:', this.name); - return await Promise.resolve('simple task') - .then(val => { - console.log('return simple task:', val); - return val; - }); +import { Inject, isString, isFunction, Token, InvocationContext } from '@tsdi/ioc'; +import { AfterViewInit, Component, EventEmitter, OnInit } from '@tsdi/components'; + +@Component({ + selector: 'stest', + template: ` + + `} +) +export class SimpleTask implements OnInit, AfterViewInit { + + text!: string; + + textChange = new EventEmitter(); + + onInit(): void { + this.text = 'simple task'; + this.textChange.emit(this.text); + } + onAfterViewInit(): void { + console.log('return simple task:', this.text); } } -@Task('loaddata') -export class LoadData extends Activity { - @Input() service: Token; - @Input() action: string; - @Input() getParams: string | ((ctx: ActivityContext) => any[]); - @Input() params: any[]; - async execute(ctx: ActivityContext): Promise { - let service = ctx.getContextValue(this.service); - if (service && service[this.action]) { +@Component({ + selector: '[loaddata]' +}) +export class LoadData implements OnInit { + service?: Token; + action?: string; + params?: any[]; + + loaddata = new EventEmitter(); + + constructor(private ctx: InvocationContext) { + + } + + onInit(): void { + this.invokeService(); + } + + async invokeService(): Promise { + const ctx = this.ctx; + const service = ctx.get(this.service!); + if (service && service[this.action!]) { let params: any[]; - if (this.params && this.params.length) { + if (this.params) { params = this.params; - } else if (this.getParams) { - let getFunc = isString(this.getParams) ? ctx.getExector().eval(this.getParams) : this.getParams; - params = isFunction(getFunc) ? getFunc(ctx) : []; + } else { + params = []; } - return await service[this.action](...params); + const data = await service[this.action!](...params); + this.loaddata.emit(data); } } } -@Task('setdata') -export class SetData extends Activity { - @Input() func: string | Function; - async execute(ctx: ActivityContext): Promise { - let func = isString(this.func) ? ctx.getExector().eval(this.func) : this.func; - if (isFunction(func)) { - func(ctx); - } + +@Component({ + selector: 'comowork', + template: ` + ` +}) +export class WorkTask implements AfterViewInit { + public text!: string; + + textChange = new EventEmitter(); + + onInit(): void { + this.text = 'component task'; + this.textChange.emit(this.text); } -} -@Task('comowork') -export class WorkTask extends Activity { - async execute(ctx: ActivityContext): Promise { - // console.log('before simple task:', this.name); - return await Promise.resolve('component task') - .then(val => { - console.log('return component work task:', val); - return val; - }); + onAfterViewInit(): void { + console.log('return component work task:', this.text); } } -@Task({ - deps: [ - WorkTask - ], +@Component({ selector: 'comptest', - template: [ - { activity: Activities.if, condition: (ctx) => !!ctx.workflow.args[0], body: [] }, - { - activity: Activities.else, - body: [ - // WorkTask - { - activity: Activities.switch, - switch: (ctx) => ctx.workflow.args.length, - cases: [ - { case: 0, body: [] } - ] - }, - { - activity: 'comowork' - }, - // { - // activity: 'setdata', - // func: `ctx => ctx.getConext('xxxxx').setResult(ctx.result)` - // } - ] - }, - // { - // activity: 'comowork' - // } - ] + template: ` + +