Commit 94092e42 authored by nanahira's avatar nanahira

add WireContextService

parent 8d0e3366
......@@ -119,7 +119,7 @@ export class AppService implements OnModuleInit {
### 注入上下文
```ts
import { Injectable } from '@nestjs/common';
import { Injectable, OnModuleInit } from '@nestjs/common';
import { InjectContext } from 'koishi-nestjs';
import { Context } from 'koishi';
......@@ -136,7 +136,7 @@ export class AppService implements OnModuleInit {
### 注入某一特定上下文
```ts
import { Injectable } from '@nestjs/common';
import { Injectable, OnModuleInit } from '@nestjs/common';
import { InjectContextGuild } from 'koishi-nestjs';
import { Context } from 'koishi';
......@@ -201,6 +201,45 @@ export class AppModule {}
* `values` 作用域值。例如 `getContextProvideToken('platform', ['onebot'])` 等价于 `ctx.platform('onebot')` .
### 注入上下文 Service
您可以使用装饰器与 Koishi 的 Service 系统进行交互。
```ts
import { Injectable, OnApplicationBootstrap } from "@nestjs/common";
import { WireContextService, UseEvent } from 'koishi-nestjs';
import { Cache } from 'koishi';
@Injectable()
export class AppService implements OnApplicationBootstrap {
constructor(@InjectContextGuild('1111111111') private ctx: Context) {
}
// 注入 Service
@WireContextService('cache')
private cache2: Cache;
// 成员变量名与 Service 名称一致时 name 可省略。
@WireContextService()
private cache: Cache;
async onApplicationBootstrap() {
// 可以在 onApplicationBootstrap 访问上下文 Service
const user = this.cache.get('user', '111111111');
}
@UseEvent('service/cache')
async onCacheAvailable() {
// 建议监听 Service 事件
const user = this.cache.get('user', '111111112');
}
}
```
#### 定义
* `@WireContextService(name?: string)` 在提供者类某一属性注入特定上下文 Service 。 `name` 默认为类方法名。
## 使用装饰器注册 Koishi 指令
您也可以在完全不注入任何 Koishi 上下文的情况下注册 Koishi 指令,只需要在提供者类中使用装饰器即可。下面是一个例子。
......
{
"name": "koishi-nestjs",
"version": "1.1.6",
"version": "1.2.0",
"description": "Koishi.js as Nest.js Module",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
......
......@@ -4,6 +4,8 @@ import {
KoishiCommandDefinition,
KoishiDoRegister,
KoishiOnContextScope,
KoishiServiceWireKeys,
KoishiServiceWireProperty,
} from './utility/koishi.constants';
import {
CommandDefinitionFun,
......@@ -150,3 +152,21 @@ export const CommandOption = (
desc: string,
config: Argv.OptionConfig = {},
) => CommandDef((cmd) => cmd.option(name, desc, config));
// Service
export function WireContextService(name?: string): PropertyDecorator {
return (obj, key) => {
const objClass = obj.constructor;
const properties: string[] =
Reflect.getMetadata(KoishiServiceWireKeys, objClass) || [];
properties.push(key.toString());
Reflect.defineMetadata(KoishiServiceWireKeys, properties, objClass);
Reflect.defineMetadata(
KoishiServiceWireProperty,
name || key,
objClass,
key,
);
};
}
......@@ -50,6 +50,7 @@ export class KoishiService
async onModuleInit() {
await this.setHttpServer();
await this.metascan.preRegisterContext(this.any());
if (this.koishiModuleOptions.usePlugins) {
for (const pluginDesc of this.koishiModuleOptions.usePlugins) {
const ctx = applySelector(this, pluginDesc);
......
......@@ -13,11 +13,14 @@ import {
KoishiCommandDefinition,
KoishiDoRegister,
KoishiOnContextScope,
KoishiServiceWireKeys,
KoishiServiceWireProperty,
} from '../utility/koishi.constants';
import {
CommandDefinitionFun,
ContextFunction,
DoRegisterConfig,
EventName,
EventNameAndPrepend,
KoishiModulePlugin,
OnContextFunction,
......@@ -25,6 +28,7 @@ import {
import { applySelector } from '../utility/koishi.utility';
import _ from 'lodash';
import { KoishiContextService } from './koishi-context.service';
import { Module } from '@nestjs/core/injector/module';
@Injectable()
export class KoishiMetascanService {
......@@ -45,7 +49,7 @@ export class KoishiMetascanService {
}
}
private async handleInstance(
private async handleInstanceRegistration(
ctx: Context,
instance: Record<string, any>,
methodKey: string,
......@@ -88,9 +92,17 @@ export class KoishiMetascanService {
const {
data: eventData,
} = regData as DoRegisterConfig<EventNameAndPrepend>;
const eventName = eventData.name;
baseContext.on(eventData.name, (...args: any[]) =>
methodFun.call(instance, ...args),
);
// special events
if (typeof eventName === 'string' && eventName.startsWith('service/')) {
const serviceName = eventName.slice(8);
if (baseContext[serviceName] != null) {
methodFun.call(instance);
}
}
break;
case 'plugin':
const pluginDesc: KoishiModulePlugin<any> = await methodFun.call(
......@@ -125,28 +137,81 @@ export class KoishiMetascanService {
}
}
private registerOnService(
ctx: Context,
instance: any,
property: string,
name: string,
) {
const preObject = ctx[name];
if (preObject) {
instance[property] = preObject;
}
ctx.on(
<EventName>`service/${name}`,
() => {
instance[property] = ctx[name];
},
true,
);
}
private scanInstanceForService(ctx: Context, instance: any) {
const instanceClass = instance.constructor;
const properties: string[] = Reflect.getMetadata(
KoishiServiceWireKeys,
instanceClass,
);
if (!properties) {
return;
}
for (const property of properties) {
const serviceName = Reflect.getMetadata(
KoishiServiceWireProperty,
instanceClass,
property,
);
this.registerOnService(ctx, instance, property, serviceName);
}
}
private getAllActiveProvidersFromModule(module: Module) {
return [
...Array.from(module.routes.values()),
...Array.from(module.providers.values()),
]
.filter((wrapper) => wrapper.isDependencyTreeStatic())
.filter((wrapper) => wrapper.instance);
}
async preRegisterContext(ctx: Context) {
for (const module of this.moduleContainer.values()) {
const moduleCtx = this.ctxService.getModuleCtx(ctx, module);
const allProviders = this.getAllActiveProvidersFromModule(module);
for (const provider of allProviders) {
return this.scanInstanceForService(moduleCtx, provider.instance);
}
}
}
async registerContext(ctx: Context) {
const modules = Array.from(this.moduleContainer.values());
await Promise.all(
_.flatten(
modules.map((module) => {
const moduleCtx = this.ctxService.getModuleCtx(ctx, module);
return [
...Array.from(module.routes.values()),
...Array.from(module.providers.values()),
]
.filter((wrapper) => wrapper.isDependencyTreeStatic())
.filter((wrapper) => wrapper.instance)
.map((wrapper: InstanceWrapper) => {
const { instance } = wrapper;
const prototype = Object.getPrototypeOf(instance);
return this.metadataScanner.scanFromPrototype(
instance,
prototype,
(methodKey: string) =>
this.handleInstance(moduleCtx, instance, methodKey),
);
});
const allProviders = this.getAllActiveProvidersFromModule(module);
return allProviders.map((wrapper: InstanceWrapper) => {
const { instance } = wrapper;
this.scanInstanceForService(moduleCtx, instance);
const prototype = Object.getPrototypeOf(instance);
return this.metadataScanner.scanFromPrototype(
instance,
prototype,
(methodKey: string) =>
this.handleInstanceRegistration(moduleCtx, instance, methodKey),
);
});
}),
),
);
......
......@@ -6,3 +6,6 @@ export const KOISHI_CONTEXT = 'KOISHI_CONTEXT';
export const KoishiOnContextScope = 'KoishiOnContextScope';
export const KoishiDoRegister = 'KoishiDoRegister';
export const KoishiCommandDefinition = 'KoishiCommandDefinition';
export const KoishiServiceWireProperty = Symbol('KoishiServiceWireProperty');
export const KoishiServiceWireKeys = Symbol('KoishiServiceWireKeys');
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment