npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@celljs/core

v3.2.0

Published

## 概览

Downloads

308

Readme

Cell - Core Component

概览

Cell 核心组件是一个轻量级的应用框架,用于构建现代化的应用程序。通过依赖注入、AOP 面向切面编程、配置属性、日志等功能,提供了一套简单易用的 API 接口,支持类对象的注册、解析和生命周期管理。Cell 核心组件是基于 InversifyJS 构建的,支持 TypeScript 语言。

特性

  • 依赖注入
  • 面向切面编程
  • 配置属性
  • 日志

安装

使用 npm 安装 Cell 核心组件:

npm install @celljs/core

或者使用 yarn:

yarn add @celljs/core

快速开始

以下是一个简单的依赖注入示例,展示如何使用 Cell 核心组件将一个类对象注入到 IoC 容器中:

import { Component } from '@celljs/core';

@Component()
export class HelloWorld {
    greet() {
        return 'Hello, World!';
    }
}

然后,在另一个对象中使用 @Autowired 注解来注入 HelloWorld 类对象:

import { Autowired, ApplicationFactory } from '@celljs/core';
import { HelloWorld } from './hello-world';

export class SampleService {
    @Autowired()
    helloWorld: HelloWorld;

    run() {
        console.log(this.helloWorld.greet());
    }
}

接着,在 IoC 容器模块中注册 HelloWorldSampleService 类对象:

import { ApplicationFactory } from '@celljs/core';
import { HelloWorld } from './hello-world';
import { SampleService } from './sample-service';

export const SampleModule = autoBind();

最后,通过 ApplicationFactory 创建应用实例,加载 IoC 容器模块,组装成一个完整的 IoC 容器:

import { ApplicationFactory } from '@celljs/core';
import { SampleService } from './sample-service';
import { SampleModule } from './sample-module';

(async () => {
    const appProps = {};
    const app = await ApplicationFactory.create(appProps, SampleModule);
    app.start();
})();

依赖注入

Cell 核心组件提供了依赖注入功能,支持将类对象注入到 IoC 容器中,并在其他类对象中使用 @Autowired 注解来注入依赖对象。以下是一个简单的依赖注入示例:

import { Component, Autowired } from '@celljs/core';

@Component()
export class HelloWorld {
    greet() {
        return 'Hello, World!';
    }
}

@Component()
export class SampleService {
    @Autowired()
    helloWorld: HelloWorld;

    run() {
        console.log(this.helloWorld.greet());
    }
}

属性注入

Cell 核心组件支持属性注入功能,通过 @Value 注解可以将配置属性注入到类对象中。以下是一个简单的属性注入示例:

import { Component, Value } from '@celljs/core';

@Component()
export class SampleService {
    @Value('app.name')
    appName: string;

    run() {
        console.log(`App Name: ${this.appName}`);
    }
}

可以被注入的属性是在应用启动时通过 ApplicationFactorycreate 方法传入的 appProps 对象中定义的。例如:

import { ApplicationFactory } from '@celljs/core';
import { SampleService } from './sample-service';

(async () => {
    const appProps = {
        app: { name: 'MyApp' }
    };
    const app = await ApplicationFactory.create(appProps, SampleModule);
    app.start();
})();

属性注入时,可以使用 . 来访问嵌套属性,例如 @Value('app.name')

除此之外,@Value 的字符串才是支持 EL 表达式的,底层是基于 jexl 实现的。例如:

import { Component, Value } from '@celljs/core';

@Component()
export class SampleService {

    // 对象访问
    @Value('app.name')
    appName: string;

    // 三元表达式
    @Value('app.name == "MyApp" ? "Hello, MyApp!" : "Hello, World!"')
    greeting: string; 
    
    // 提供默认值
    @Value('app.version ?: "1.0.0"')
    appVersion: string;

    // 数学运算
    @Value('app.port + 1')
    port: number;

    // 逻辑运算
    @Value('app.debug && app.env == "dev"')
    debug: boolean;

    // 数组访问
    @Value('app.tags[0]')
    tag: string;

    // 数组过滤
    @Value('employees[.age >= 30 && .age < 40]')
    middleAgedEmployees: Employee[];

    // 变换
    // 假如定义如下两个变换:
    // jexl.addTransform('split', (val, char) => val.split(char))
    // jexl.addTransform('lower', (val) => val.toLowerCase())
    @Value('"Pam Poovey"|lower|split(' ')[1]')
    lastName: string;

    // 函数调用
    // 假如定义如下函数:
    // jexl.addFunction('min', Math.min)
    @Value('min(3, 5)')
    min: number;
    
}

IoC 容器

Cell 核心组件提供了一个轻量级的 IoC 容器,用于管理类对象的生命周期和依赖关系。通过 @Component 注解可以将类对象注册到 IoC 容器中,通过 @Autowired 注解可以在其他类对象中注入依赖对象。

IoC 容器底层实现是基于 InversifyJS 的,提供了一套简单易用的 API 接口,支持类对象的注册、解析和生命周期管理。借鉴 Spring Framework 的设计理念,提供了一套类似 Spring 的装饰器,例如 @Component@Autowired@Value 等。

IoC 容器是由一些列容器模块组成的,每个容器模块是由一些类对象组成的,通过 @Component 注解注册到容器模块。为了方便注册类对象到容器模块中,Cell 核心组件提供了 autoBind 方法,可以自动注册类对象到容器模块中。autoBind 通过文件模块依赖分析,自动注册类对象到容器模块中,无需手动注册。

以下是一个简单的 IoC 容器示例:

import { Component, Autowired, ApplicationFactory } from '@celljs/core';

@Component()
export class HelloWorld {
    greet() {
        return 'Hello, World!';
    }
}

@Component()
export class SampleService {
    @Autowired()
    helloWorld: HelloWorld;

    run() {
        console.log(this.helloWorld.greet());
    }
}

export const SampleModule = autoBind();

autoBind 方法还提供一些列的回调函数,用于自定义注册行为。例如:

import { autoBind } from '@celljs/core';

export const SampleModule = autoBind((bind, unbind, isBound, rebind) => {
    bind(HelloWorld).toSelf().inSingletonScope();
    bind(SampleService).toSelf().inSingletonScope();
});

Cell 核心组件还提供了 ContainerFactory,用于创建 IoC 容器实例。例如:

import { ContainerFactory } from '@celljs/core';
import { SampleModule } from './sample-module';

(async () => {
    const container = await ContainerFactory.create(SampleModule);
    const sampleService = container.get(SampleService);
    sampleService.run();
})();

除了使用 @Autowired 装饰器来注入依赖对象,还可以使用 ContainerUtil 工具类来手动获取容器中的对象。例如:

import { ContainerUtil } from '@celljs/core';
import { SampleService } from './sample-service';

@Component()
export class SampleService {

    run() {
        const helloWorld = ContainerUtil.get(HelloWorld);
        console.log(this.helloWorld.greet());
    }
}

注意:ContainerUtil 工具类只能容器加载完后才能使用,否则会报错。

配置属性

Cell 核心组件提供了配置属性功能,支持将配置属性注入到类对象中。通过 @Value 注解可以将配置属性注入到类对象中,支持 EL 表达式。

配置属性是在应用启动时通过 ApplicationFactorycreate 方法传入的 appProps 对象中定义的。例如:

import { ApplicationFactory } from '@celljs/core';
import { SampleService } from './sample-service';

(async () => {
    const appProps = {
        app: { name: 'MyApp' }
    };
    const app = await ApplicationFactory.create(appProps, SampleModule);
    app.start();
})();

除此之外,还提供了一个 ConfigProvider 接口,用于加载配置文件。Cell 核心组件提供了一个默认实现,开发者也可以自定义实现替换掉默认实现。例如:

import { ConfigProvider } from '@celljs/core';

@Component({ id: ConfigProvider, rebind: true })
export class MyConfigProvider implements ConfigProvider {
    get<T>(key: string, defaultValue?: T): T {
        return process.env[key] || defaultValue;
    }
}

除了前面介绍的使用 @Value 注解来注入配置属性,还可以使用 ConfigUtil 工具类来手动获取配置属性。例如:

import { ConfigUtil } from '@celljs/core';
import { SampleService } from './sample-service';

@Component()
export class SampleService {

    run() {
        const appName = ConfigUtil.get('app.name');
        console.log(`App Name: ${appName}`);
    }
}

装饰器

Cell 核心组件提供了一套类似 Spring 的装饰器,用于注册类对象到 IoC 容器中。以下是一些常用的装饰器:

  • @Component:注册类对象到 IoC 容器中。
  • @Autowired:注入依赖对象。
  • @Value:注入配置属性。
  • @PostConstruct:在类对象初始化后执行。
  • @PreDestroy:在类对象销毁前执行。
  • @Service:注册服务对象到 IoC 容器中。是 @Component 的别名。
  • @Inject:注入依赖对象。是 InversifyJSinject 的别名。
  • @Injectable:标识类对象是可注入的。是 InversifyJSinjectable 的别名。
  • @Named:指定注入对象的名称。是 InversifyJSnamed 的别名。
  • @Optional:标识注入对象是可选的。是 InversifyJSoptional 的别名。
  • @Tagged:标识注入对象是标记的。是 InversifyJStagged 的别名。
  • @TargetName:指定注入对象的名称。是 InversifyJStargetName 的别名。
  • @Unmanaged:标识类对象是不受管理的。是 InversifyJSunmanaged 的别名。
  • @Aspect:定义切面对象。提供 AOP 编程的能力。
  • @AutowiredProvider:注入依赖提供者,通过依赖提供者对象,获取一类对象列表,支持排序能力。
  • @Constant:注册常量对象到 IoC 容器中。
  • @Decorate:装饰类对象。是 InversifyJSdecorate 的别名。

除此之外,还提供了一些装饰器,用于在非托管类对象中使用(当然,也可以直接使用 ContainerUtil 工具类来手动获取容器中的对象),例如在前端的 React 类组件中使用。以下是一些常用的非托管类对象的装饰器:

  • @Autowired:注入依赖对象。
  • @Value:注入配置属性。
  • @AutowiredProvider:注入依赖提供者,通过依赖提供者对象,获取一类对象列表,支持排序能力。

例如:

import { Autowired, Value } from '@celljs/core/lib/common/annotation/detached';

export class SampleComponent extends React.Component {

    @Autowired()
    sampleService: SampleService;

    @Value('app.name')
    appName: string;

    render() {
        return <div>{this.appName}</div>;
    }
}

@Component 装饰器

@Component 装饰器用于注册类对象到 IoC 容器中。该装饰器有以下属性:

  • id:类对象的标识符,可以是一个或多个。
  • scope:类对象的作用域,默认是 Scope.Singleton
  • name:类对象的名称。
  • tag:类对象的标签。
  • default:是否是默认的类对象。当给定的服务标识符有多个绑定可用时,可以通过设置 default 属性来指定默认的类对象。当通过服务标识符获取单个对象时,则返回默认对象。
  • when:一个函数,用于判断是否应该创建该类对象。如果返回 true,则创建该类对象;否则不创建。
  • rebind:是否重新绑定类对象。默认是 false
  • proxy:是否使用代理类对象。默认是 false。当为 true 时,可以被 AOP 切面拦截。
  • sysTags:系统标签,用于标识类对象。在 AOP 切面中,可以通过系统标签来拦截类对象。
  • onActivation:在类对象激活时执行的回调函数。

例如:

import { Component, Scope } from '@celljs/core';

@Component({ id: 'HelloWorld', scope: Scope.Singleton })
export class HelloWorld {
    greet() {
        return 'Hello, World!';
    }
}

id 属性是一个数组时,表示一个类对象有多个标识符。例如:

import { Component } from '@celljs/core';

@Component({ id: ['HelloWorld', 'Hello'] })
export class HelloWorld {
    greet() {
        return 'Hello, World!';
    }
}

当不设置 id 属性时,则默认使用类作为标识符。例如:

import { Component } from '@celljs/core';

@Component()
export class HelloWorld {
    greet() {
        return 'Hello, World!';
    }
}

无论有没有设置 id 属性,装饰器所在的类永远都是一个标识符,都可以通过该类获取到类对象。例如:

import { Component } from '@celljs/core';

@Component()
export class HelloWorld {
    greet() {
        return 'Hello, World!';
    }
}

@Component()
export class SampleService {
    @Autowired()
    helloWorld: HelloWorld;

    run() {
        console.log(this.helloWorld.greet());
    }
}

@Autowired 装饰器

@Autowired 装饰器用于注入依赖对象。该装饰器有以下属性:

  • id:类对象的标识符。
  • multi:是否是多个依赖对象。默认是根据属性类型是否为数组,自动判断,在某些情况下,需要强制指定为 true
  • detached:是否是非托管类对象。默认是 false

例如:

import { Autowired } from '@celljs/core';

@Component()
export class SampleService {
    @Autowired({ id: HelloWorld })
    helloWorld: HelloWorld;

    run() {
        console.log(this.helloWorld.greet());
    }
}

当不设置 id 属性时,则默认使用属性类型作为标识符。例如:

import { Autowired } from '@celljs/core';

@Component()
export class SampleService {
    @Autowired()
    helloWorld: HelloWorld;

    run() {
        console.log(this.helloWorld.greet());
    }
}

当一个标识在容器中存在多个对象时,可以把属性类型定义为数组,表示是多个依赖对象。例如:

import { Autowired } from '@celljs/core';

@Component()
export class SampleService {
    @Autowired()
    helloWorlds: HelloWorld[];

    run() {
        console.log(this.helloWorlds.map(helloWorld => helloWorld.greet()));
    }
}

当一个标识在容器中存在多个对象时,也可以通过 @AutowiredProvider 装饰器来注入依赖提供者对象,通过依赖提供者对象,获取一类对象列表,支持排序能力。例如:

import { AutowiredProvider } from '@celljs/core';

@Component()
export class SampleService {
    @AutowiredProvider({ id: HelloWorld })
    helloWorldProvider: Provider<HelloWorld>;

    run() {
        const helloWorlds = this.helloWorldProvider.sortSync();
        console.log(helloWorlds.map(helloWorld => helloWorld.greet()));
    }
}

在使用 esbuild 编译的项目中,由于编译后的代码中没有类型信息,所以无法自动判断是否是多个依赖对象,需要强制指定为 true, 或者使用 @AutowiredProvider 装饰器。

@Value 装饰器

@Value 装饰器用于注入配置属性。该装饰器有以下属性:

  • el:配置属性的 EL 表达式。
  • detached:是否是非托管类对象。默认是 false

例如:

import { Value } from '@celljs/core';

@Component()
export class SampleService {
    @Value('app.name')
    appName: string;

    run() {
        console.log(`App Name: ${this.appName}`);
    }
}

@Constant 装饰器

@Constant 装饰器用于注册常量对象到 IoC 容器中。该装饰器有以下属性:

  • id:常量对象的标识符。
  • constantValue:常量对象的值。
  • rebind:是否重新绑定常量对象。默认是 false

例如:

import { Constant } from '@celljs/core';

@Constant('app.name', 'MyApp')
@Constant('app.version', '1.0.0')
export  default class {
}

@Component()
export class SampleService {
    @Autowired('app.name')
    name: string;

    @Autowired('app.version')
    version: string;

@PostConstruct 装饰器

@PostConstruct 装饰器用于在类对象初始化后执行。例如:

import { Component, PostConstruct } from '@celljs/core';

@Component()
export class SampleService {
    @PostConstruct()
    init() {
        console.log('SampleService initialized.');
    }
}

@PreDestroy 装饰器

@PreDestroy 装饰器用于在类对象销毁前执行。例如:

import { Component, PreDestroy } from '@celljs/core';

@Component()
export class SampleService {
    @PreDestroy()
    destroy() {
        console.log('SampleService destroyed.');
    }
}

@Named 装饰器

@Named 装饰器用于指定注入对象的名称。例如:

import { Autowired, Named } from '@celljs/core';

@component({ name: 'HelloWorld' })
export class HelloWorld {
    greet() {
        return 'Hello, World!';
    }
}

@Component()
export class SampleService {
    @Autowired()
    @Named('HelloWorld')
    helloWorld: HelloWorld;

    run() {
        console.log(this.helloWorld.greet());
    }
}

@Tagged 装饰器

@Tagged 装饰器用于标识注入对象是标记的。例如:

tag?: { tag: string | number | symbol, value: any };
import { Autowired, Tagged } from '@celljs/core';

@Component({ tag: { tag: 'tag1', value: 'HelloWorld' } })
export class HelloWorld {
    greet() {
        return 'Hello, World!';
    }
}

@Component()
export class SampleService {
    @Autowired()
    @Tagged('tag1', 'HelloWorld')
    helloWorld: HelloWorld;

    run() {
        console.log(this.helloWorld.greet());
    }
}

@Optional 装饰器

@Optional 装饰器用于标识注入对象是可选的。当类属性添加了一个 @Autowired 注解时,如果没有找到对应的类对象,会抛出一个异常。如果添加了一个 @Optional 注解,当没有找到对应的类对象时,会设置属性值为 undefined。例如:

import { Autowired, Optional } from '@celljs/core';

@Component()
export class SampleService {
    @Autowired()
    @Optional()
    helloWorld: HelloWorld;

    run() {
        console.log(this.helloWorld?.greet());
    }
}

@Aspect 装饰器

@Aspect 装饰器用于定义切面对象。提供 AOP 编程的能力。继承了 @Component 装饰器的所有属性。再此之上,添加了 pointcut 属性,用于指定切点。该装饰器有以下属性:

  • id:类对象的标识符。相较于 @Component 装饰器,id 属性不是数组,只能是一个标识符。
  • pointcut:切点。默认是 COMPONENT_TAG。表示拦截所以使用 @Component 装饰器注册的类对象。也可以自定义切点,例如 AOP_TAG

例如:

import { MethodBeforeAdvice, Autowired, Aspect, AfterReturningAdvice, Value, ConfigUtil, Injectable } from '@celljs/core';
import { AccessDecisionManager, MethodSecurityMetadataContext, ResourceNameResolver, SecurityMetadataSource, SECURITY_EXPRESSION_CONTEXT_KEY } from './access-protocol';
import { AOP_POINTCUT } from '@celljs/web';
import { SecurityContext } from '../context';
import { AuthorizeType } from '../../common';
import { Context } from '@celljs/web/lib/node';

const pointcut = ConfigUtil.getRaw().cell.security.aop?.pointcut || AOP_POINTCUT;

@Injectable()
export abstract class AbstractSecurityMethodAdivice {

    @Autowired(AccessDecisionManager)
    protected readonly accessDecisionManager: AccessDecisionManager;

    @Autowired(SecurityMetadataSource)
    protected readonly securityMetadataSource: SecurityMetadataSource;

    @Autowired(ResourceNameResolver)
    protected readonly resourceNameResolver: ResourceNameResolver;

    @Value('cell.security.enabled')
    protected readonly enabled: boolean;

    protected needAccessDecision(method: string | number | symbol) {
        return this.enabled && typeof method === 'string' && SecurityContext.getCurrent();
    }
}

@Aspect({ id: MethodBeforeAdvice, pointcut })
export class SecurityMethodBeforeAdivice extends AbstractSecurityMethodAdivice implements MethodBeforeAdvice {

    async before(method: string | number | symbol, args: any[], target: any): Promise<void> {
        if (this.needAccessDecision(method)) {
            const ctx: MethodSecurityMetadataContext = { method: method as string, args, target, authorizeType: AuthorizeType.Pre, grant: 0 };
            const securityMetadata = await this.securityMetadataSource.load(ctx);
            const resouces = await this.resourceNameResolver.resolve(ctx);
            for (const resource of resouces) {
                securityMetadata.resource = resource;
                await this.accessDecisionManager.decide(securityMetadata);
            }
        }
    }

}

@Aspect({ id: AfterReturningAdvice, pointcut })
export class SecurityAfterReturningAdvice extends AbstractSecurityMethodAdivice implements AfterReturningAdvice {

    async afterReturning(returnValue: any, method: string | number | symbol, args: any[], target: any): Promise<void> {
        if (this.needAccessDecision(method)) {
            const oldCtx = Context.getAttr<MethodSecurityMetadataContext>(SECURITY_EXPRESSION_CONTEXT_KEY);
            const newCtx = { method: method as string, args, target, returnValue, authorizeType: AuthorizeType.Post, grant: oldCtx.grant };
            const securityMetadata = await this.securityMetadataSource.load(newCtx);
            const resouces = await this.resourceNameResolver.resolve(newCtx);
            for (const resource of resouces) {
                securityMetadata.resource = resource;
                await this.accessDecisionManager.decide(securityMetadata);
            }
        }
    }

}

其他装饰器

其他非常用的装饰器,可以参考 InversifyJS

AOP 面向切面编程

Cell 核心组件提供了 AOP 面向切面编程的能力,支持在类对象的方法执行前、执行后、执行异常时执行一些操作。通过 @Aspect 装饰器可以定义切面对象,通过 MethodBeforeAdviceAfterReturningAdviceAfterThrowsAdvice 接口可以定义通知对象。

AOP 能力涉及一下接口:

  • MethodBeforeAdvice:在方法执行前执行。
  • AfterReturningAdvice:在方法执行后执行。
  • AfterThrowsAdvice:在方法执行异常时执行。

可以使用 @Aspect 装饰器定义切面对象,通过 pointcut 属性指定切点。例如:

import { MethodBeforeAdvice, Autowired, Aspect, AfterReturningAdvice, Value, ConfigUtil, Injectable } from '@celljs/core';

const pointcut = 'test';

@Component({ id: MethodBeforeAdvice, pointcut })
export class TestMethodBeforeAdvice implements MethodBeforeAdvice {

    async before(method: string | number | symbol, args: any[], target: any): Promise<void> {
        console.log('before');
    }

}

@Component({ id: AfterReturningAdvice, pointcut })
export class TestAfterReturningAdvice implements AfterReturningAdvice {

    async afterReturning(returnValue: any, method: string | number | symbol, args: any[], target: any): Promise<void> {
        console.log('after');
    }

}

@Component({ id: AfterThrowsAdvice, pointcut })
export class TestAfterThrowsAdvice implements AfterThrowsAdvice {
    
    async afterThrows(error: any, method: string | number | symbol, args: any[], target: any): Promise<void> {
        console.log('throws');
    }

}

上面的示例表示定义了一个切面对象,通过 pointcut 属性指定切点为 test。当一个类对象的方法执行前、执行后、执行异常时,会执行相应的操作。其中,切点为 test,表示拦截所有使用 @Component 装饰器注册的类对象,且 sysTag 为 test

搭建应用

Cell 核心组件提供了一个轻量级的应用框架,用于搭建应用程序。通过 Application 接口可以定义应用程序,通过 ApplicationLifecycle 接口可以定义应用程序的生命周期。通过 ApplicationLifecycle 接口可以定义应用程序的生命周期。通过 ApplicationStateService 接口可以定义应用程序的状态管理。通过 ApplicationFactory 可以创建应用实例。

一般使用 ApplicationFactory 创建应用实例,加载 IoC 容器模块,组装成一个完整的 IoC 容器。例如:

import { ApplicationFactory } from '@celljs/core';
import { SampleService } from './sample-service';
import { SampleModule } from './sample-module';

(async () => {
    const appProps = {
        app: { name: 'MyApp' }
    };
    const app = await ApplicationFactory.create(appProps, SampleModule);
    app.start();
})();

在应用程序中,可以通过 ApplicationLifecycle 接口定义应用程序的生命周期。例如:

import { ApplicationLifecycle, Application } from '@celljs/core';

export class SampleLifecycle implements ApplicationLifecycle {
    onStart(app: Application): Promise<void> {
        return Promise.resolve();
    }

    onStop(app: Application): void {
        console.log('Application stopped.');
    }
}

另外,在 browser 和 node 目录下,提供了 Application 的实现,可以直接使用。

目录结构说明

.
├── browser                        # 浏览器端应用
│   ├── application                # 前端应用程序
│   ├── browser.ts                 # 与浏览器相关的工具方法
│   ├── index.ts                   # 导出模块
│   ├── shell                      # 前端应用程序启动入口
│   └── static-module.ts           # 静态加载类型的 IoC 容器模块
├── common                         # 公共模块
│   ├── annotation                 # 装饰器定义目录
│   ├── aop                        # AOP 相关
│   ├── application                # 后端应用程序
│   ├── config                     # 配置属性
│   ├── constants.ts               # 常量定义
│   ├── container                  # IoC 容器
│   ├── el                         # EL 表达式
│   ├── error                      # 错误处理
│   ├── index.ts                   # 导出模块
│   ├── logger                     # 日志
│   ├── provider                   # 依赖提供者
│   ├── static-module.ts           # 静态加载类型的 IoC 容器模块
│   ├── test                       # 测试
│   └── utils                      # 工具方法
├── node                           # Node.js 端应用
│   ├── application                # 后端应用程序
│   ├── constants.ts               # 常量定义
│   ├── context                    # 上下文
│   ├── index.ts                   # 导出模块
│   ├── static-module.ts           # 静态加载类型的 IoC 容器模块
│   └── utils                      # 工具方法
└── package.spec.ts                # 单元测试

错误类型

Cell 核心组件提供了一些常用的错误类型,用于处理异常情况。

  • CustomError:自定义错误类型。
  • IllegalArgumentError:非法参数错误。
  • IllegalStateError:非法状态错误。
  • InvalidMimeTypeError:无效的 MIME 类型错误。

例如:

import { IllegalArgumentError } from '@celljs/core';

if (condition) {
    throw new IllegalArgumentError('Invalid argument.');
}

由于 JS 的运行时提供的错误类型无法通过 instanceof 运算符来判断,所以 Cell 核心组件提供了 CustomError 基类,用于自定义错误类型。所以建议自定义错误类型都继承自 CustomError 基类。

日志

Cell 核心组件提供了日志功能,支持日志级别、日志格式、日志输出等功能。通过 Logger 接口可以定义日志对象,通过 TraceIdProvider 接口可以定义跟踪 ID 提供者。

日志级别有以下几种:

  • verbose:详细信息。
  • debug:调试信息。
  • info:信息。
  • warn:警告。
  • error:错误。

例如:

import { Logger } from '@celljs/core';

@Component()
export class SampleService {

    @Autowired(Logger)
    logger: Logger;

    run() {
        this.logger.info('Info message.');
        this.logger.error('Error message.');
    }
}

其中,默认提供的日志输出为 console,为了支持不同类对象,注入的日志对象都是互相独立的,可以通过 setContext 方法设置上下文信息。例如:

import { Logger } from '@celljs/core';

@Component()
export class SampleService {

    @Autowired(Logger)
    logger: Logger;

    run() {
        this.logger.setContext('SampleService');
        this.logger.info('Info message.');
        this.logger.error('Error message.');
    }
}

开发者也可以根据自己的需求,自定义 Logger 接口的实现,替换掉默认的实现。例如:

import { Component } from '@celljs/core';

@Component({ id: Logger, rebind: true })
export class MyLogger implements Logger {
    // TODO
}

上下文能力

Cell 核心组件提供了上下文能力,支持应用程序上下文、请求上下文、会话上下文等。通过 Context 类可以设置和获取上下文信息。

上下文信息有以下几种:

  • App:应用程序上下文。
  • Request:请求上下文。
  • Session:会话上下文。
import { Context, AttributeScope } from '@celljs/core';

Context.setAttr('key', 'value', AttributeScope.Request);
const value = Context.getAttr('key', AttributeScope.Request);

上下文运行可以保证上下文信息在函数执行期间有效。例如:

import { Context } from '@celljs/core';

Context.run(async () => {
    Context.setAttr('key', 'value1', AttributeScope.Request);
    const value = Context.getAttr('key', AttributeScope.Request);
    console.log(value); // value1
});

Context.run(() => {
    Context.setAttr('key', 'value2', AttributeScope.Request);
    const value = Context.getAttr('key', AttributeScope.Request);
    console.log(value); // value2
});

常用工具类和方法

Cell 核心组件提供了一些常用的工具类和方法,用于处理一些常见的问题。

  • AnnotationUtil:注解工具类。
  • Assert:断言工具类。
  • Async:异步工具类。
  • Cancellation:取消工具类。
  • ClassUtil:类工具类。
  • Disposable:可释放工具类。
  • Emitter:事件工具类。
  • Event:事件工具类。
  • GlobalUtil:全局工具类。
  • MetadataUtil:元数据工具类。
  • Prioritizeable:优先级工具类。
  • PromiseUtil:Promise 工具类。
  • ProxyUtil:代理工具类。
  • Types:类型工具类。
  • UrlUtil:URL 工具类。
  • generateUUUID:生成 UUID。
  • OS:操作系统工具类。
  • MimeTypeUtil:MIME 类型工具类。
  • MimeType:MIME 类型。

例如:

import { Assert } from '@celljs/core';

Assert.isTrue(condition, 'Invalid argument.');

EL 表达式

Cell 核心组件支持 EL 表达式,EL 表达式是一种简单、易读的模板语言,可以用于在模板中动态生成内容。通过 ExpressionCompiler 接口可以编译 EL 表达式,通过 ExpressionHandler 接口可以处理 EL 表达式。

有以下核心接口:

  • ExpressionCompiler:编译 EL 表达式。
  • ExpressionHandler:处理 EL 表达式。
  • ExpressionContext:EL 表达式上下文。
  • ContextInitializer:上下文初始化器。
  • ExpressionContextProvider:EL 表达式上下文提供者。
  • JexlEngineProvider:Jexl 引擎提供者。

例如:

import { ExpressionHandler } from '@celljs/core';

@Component()
export class SampleService {

    @Autowired(ExpressionHandler)
    expressionHandler: ExpressionHandler;

    run() {
        const result = this.expressionHandler.handle('Hello, ${name}!', { name: 'World' });
        console.log(result); // Hello, World!
    }
}

可以为 EL 表达式添加新的上下文变量、函数、变换器等。例如:

import { ContextInitializer } from '@celljs/core';

@Component(ContextInitializer)
export class CoreContextInitializer implements ContextInitializer {

    @Autowired(JexlEngineProvider)
    protected readonly jexlEngineProvider: JexlEngineProvider<any>;

    initialize(ctx: ExpressionContext): void {
        if (typeof process !== 'undefined') {
            ctx.env = { ...process.env, _ignoreEl: true };
        }
        const jexlEngine = this.jexlEngineProvider.provide();
        jexlEngine.addTransform('replace',
                (val: string, searchValue: string | RegExp, replaceValue: string) => val && val.replace(new RegExp(searchValue, 'g'), replaceValue));
        jexlEngine.addTransform('regexp',  (pattern: string, flags?: string) => new RegExp(pattern, flags));
        const expressionHandler = ContainerUtil.get<ExpressionHandler>(ExpressionHandler);
        jexlEngine.addTransform('eval',  (text: string) => expressionHandler.handle(text));
    }

    priority = 500;

}

应用的属性配置默认支持 EL 表达式模版字符串,例如:

import { ApplicationFactory } from '@celljs/core';

(async () => {
    const appProps = {
        app: { name: 'MyApp' },
        env: '${env}',
        version: '${version}',
        appVersion: '${app.name}-${version}',
        appVersion2: '${app.name}-${version}-${env}',
    };
    const app = await ApplicationFactory.create(appProps, SampleModule);
    app.start();
})();

许可证

本项目采用 MIT 许可证。