Commit f5b68fa1 authored by nanahira's avatar nanahira

use CallbackLayer and add UsingService

parent cf2dd8ce
......@@ -14,7 +14,7 @@
"@types/ws": "^8.5.3",
"koa": "^2.13.4",
"koa-bodyparser": "^4.3.0",
"koishi-decorators": "^2.1.0",
"koishi-decorators": "^2.1.6",
"lodash": "^4.17.21",
"typed-reflector": "^1.0.10",
"ws": "^8.7.0"
......@@ -46,7 +46,8 @@
"peerDependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
"koishi": "^4.7.4"
"koishi": "^4.7.4",
"rxjs": "^7.5.5"
}
},
"node_modules/@babel/code-frame": {
......@@ -5727,19 +5728,20 @@
}
},
"node_modules/koishi-decorators": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/koishi-decorators/-/koishi-decorators-2.1.0.tgz",
"integrity": "sha512-b0opV0YfWXH7/sJ7ecBjPWhz9PsL4esQ1J8EUfymTcZE5jhiTjKhvp+TEdlEC+bDTM49LwMSnohGMU8nZxaD+g==",
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/koishi-decorators/-/koishi-decorators-2.1.6.tgz",
"integrity": "sha512-G1NN31fCIOafKkDME4YBzgHZ6J3b/CjsKr2AszZw9rmH6j7qHbs3PxXrkEK7zFSvTJh1RLHR2gDMhxqC7A3zTQ==",
"dependencies": {
"@types/koa": "^2.13.4",
"@types/koa__router": "^8.0.11",
"lodash": "^4.17.21",
"mustache": "^4.2.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.5.5",
"typed-reflector": "^1.0.10"
},
"peerDependencies": {
"koishi": "^4.7.1"
"koishi": "^4.7.4"
}
},
"node_modules/koishi/node_modules/path-to-regexp": {
......@@ -6997,10 +6999,9 @@
}
},
"node_modules/rxjs": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.4.tgz",
"integrity": "sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ==",
"peer": true,
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
"integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
"dependencies": {
"tslib": "^2.1.0"
}
......@@ -7008,8 +7009,7 @@
"node_modules/rxjs/node_modules/tslib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
"peer": true
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A=="
},
"node_modules/safe-buffer": {
"version": "5.1.2",
......@@ -12627,15 +12627,16 @@
}
},
"koishi-decorators": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/koishi-decorators/-/koishi-decorators-2.1.0.tgz",
"integrity": "sha512-b0opV0YfWXH7/sJ7ecBjPWhz9PsL4esQ1J8EUfymTcZE5jhiTjKhvp+TEdlEC+bDTM49LwMSnohGMU8nZxaD+g==",
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/koishi-decorators/-/koishi-decorators-2.1.6.tgz",
"integrity": "sha512-G1NN31fCIOafKkDME4YBzgHZ6J3b/CjsKr2AszZw9rmH6j7qHbs3PxXrkEK7zFSvTJh1RLHR2gDMhxqC7A3zTQ==",
"requires": {
"@types/koa": "^2.13.4",
"@types/koa__router": "^8.0.11",
"lodash": "^4.17.21",
"mustache": "^4.2.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.5.5",
"typed-reflector": "^1.0.10"
}
},
......@@ -13585,10 +13586,9 @@
}
},
"rxjs": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.4.tgz",
"integrity": "sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ==",
"peer": true,
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
"integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
"requires": {
"tslib": "^2.1.0"
},
......@@ -13596,8 +13596,7 @@
"tslib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
"peer": true
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A=="
}
}
},
......
......@@ -33,7 +33,8 @@
"peerDependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
"koishi": "^4.7.4"
"koishi": "^4.7.4",
"rxjs": "^7.5.5"
},
"devDependencies": {
"@nestjs/platform-express": "^8.4.6",
......@@ -65,7 +66,7 @@
"@types/ws": "^8.5.3",
"koa": "^2.13.4",
"koa-bodyparser": "^4.3.0",
"koishi-decorators": "^2.1.0",
"koishi-decorators": "^2.1.6",
"lodash": "^4.17.21",
"typed-reflector": "^1.0.10",
"ws": "^8.7.0"
......
......@@ -19,6 +19,8 @@ import { KoishiMetadataFetcherService } from '../koishi-metadata-fetcher/koishi-
import { KoishiInterceptorManagerService } from '../koishi-interceptor-manager/koishi-interceptor-manager.service';
import { CommandRegisterConfig, Registrar } from 'koishi-decorators';
import { KoishiExceptionHandlerService } from '../koishi-exception-handler/koishi-exception-handler.service';
import { firstValueFrom, lastValueFrom } from 'rxjs';
import { takeFirstValue } from '../utility/take-first-value';
@Injectable()
export class KoishiMetascanService {
......@@ -51,36 +53,45 @@ export class KoishiMetascanService {
methodKey: string,
) {
const registrar = new Registrar(instance, undefined, this.templateParams);
const baseContext = registrar.getScopeContext(ctx, methodKey, false);
const result = registrar.register(baseContext, methodKey, false);
if (!result) {
return;
}
if (result.type === 'command') {
const command = result.result as Command;
const interceptorDefs: KoishiCommandInterceptorRegistration[] = _.uniq(
this.metaFetcher.getPropertyMetadataArray(
KoishiCommandInterceptorDef,
instance,
methodKey,
),
);
this.addInterceptors(command, interceptorDefs);
if (!(result.data as CommandRegisterConfig).config?.empty) {
command.action(async (argv) => {
try {
return await argv.next();
} catch (e) {
return this.exceptionHandler.handleActionException(e);
const scopeContext = registrar.getScopeContext(ctx, methodKey, false);
return takeFirstValue(
registrar.runLayers(
scopeContext,
async (baseContext) => {
const result = registrar.register(baseContext, methodKey, false);
if (!result) {
return;
}
}, true);
}
} else if (result.type === 'plugin') {
const mayBePromise = result.result as Promise<any>;
if (mayBePromise instanceof Promise) {
await mayBePromise;
}
}
if (result.type === 'command') {
const command = result.result as Command;
const interceptorDefs: KoishiCommandInterceptorRegistration[] =
_.uniq(
this.metaFetcher.getPropertyMetadataArray(
KoishiCommandInterceptorDef,
instance,
methodKey,
),
);
this.addInterceptors(command, interceptorDefs);
if (!(result.data as CommandRegisterConfig).config?.empty) {
command.action(async (argv) => {
try {
return await argv.next();
} catch (e) {
return this.exceptionHandler.handleActionException(e);
}
}, true);
}
} else if (result.type === 'plugin') {
const mayBePromise = result.result as Promise<any>;
if (mayBePromise instanceof Promise) {
await mayBePromise;
}
}
},
methodKey,
),
);
}
private registerOnService(
......@@ -177,13 +188,21 @@ export class KoishiMetascanService {
undefined,
this.templateParams,
);
registrar.performTopActions(providerCtx);
return Promise.all(
registrar
.getAllFieldsToRegister()
.map((methodKey: string) =>
this.handleInstanceRegistration(providerCtx, instance, methodKey),
),
return takeFirstValue(
registrar.runLayers(providerCtx, (providerInnerCtx) => {
registrar.performTopActions(providerInnerCtx);
return Promise.all(
registrar
.getAllFieldsToRegister()
.map((methodKey: string) =>
this.handleInstanceRegistration(
providerInnerCtx,
instance,
methodKey,
),
),
);
}),
);
}),
);
......
// Injections
import { KoishiCommandInterceptorRegistration } from './koishi.interfaces';
import {
KoishiCommandInterceptorRegistration,
ServiceName,
} from './koishi.interfaces';
import { Context } from 'koishi';
export const KOISHI_MODULE_OPTIONS = 'KOISHI_MODULE_OPTIONS';
......@@ -15,7 +18,7 @@ export const KoishiServiceProvideSym = 'KoishiServiceProvideSym';
// metadata map
export interface MetadataArrayMap {
KoishiServiceProvideSym: keyof Context;
KoishiServiceProvideSym: ServiceName;
KoishiCommandInterceptorDef: KoishiCommandInterceptorRegistration;
}
......
......@@ -13,12 +13,14 @@ import {
MetadataArrayValueMap,
MetadataGenericMap,
MetadataKey,
ServiceName,
} from './koishi.interfaces';
import { Context } from 'koishi';
import {
ContextScopeTypes,
getContextProvideToken,
} from './koishi-context.factory';
import { CallbackLayer } from 'koishi-decorators';
// Injections
export const InjectContext = () => Inject(KOISHI_CONTEXT);
......@@ -98,7 +100,7 @@ export { PluginDef } from 'koishi-decorators';
// Service
export function WireContextService(name?: keyof Context): PropertyDecorator {
export function WireContextService(name?: ServiceName): PropertyDecorator {
return (obj, key) => {
const objClass = obj.constructor;
const properties: string[] =
......@@ -114,11 +116,17 @@ export function WireContextService(name?: keyof Context): PropertyDecorator {
};
}
export function ProvideContextService(name: keyof Context): ClassDecorator {
Context.service(name);
export function ProvideContextService(
name: ServiceName,
options: Context.ServiceOptions,
): ClassDecorator {
Context.service(name, options);
return AppendMetadata(KoishiServiceProvideSym, name);
}
export const UsingService = (...services: ServiceName[]) =>
CallbackLayer((ctx, cb) => ctx.using(services, cb));
// Command interceptor
export const CommandInterceptors = (
......
import { ModuleMetadata, Provider, Type } from '@nestjs/common';
import { App, Channel, Command, User } from 'koishi';
import { App, Channel, Command, Context, User } from 'koishi';
import { MetadataArrayMap, MetadataMap } from './koishi.constants';
import { ContextSelector, PluginDefinition } from 'koishi-decorators';
......@@ -84,3 +84,5 @@ export type KoishiCommandInterceptorRegistration<
| Type<KoishiCommandInterceptor<U, G, A, O>>
| string
| symbol;
export type ServiceName = keyof Context | string;
import { Observable } from 'rxjs';
export function takeFirstValue<T>(obs: Observable<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
let resolved = false;
obs.subscribe({
next: (value) => {
if (!resolved) {
resolve(value);
resolved = true;
}
},
error: (error) => {
if (!resolved) {
reject(error);
resolved = true;
}
},
complete: () => {
if (!resolved) {
resolve(undefined);
resolved = true;
}
},
});
});
}
......@@ -6,6 +6,7 @@ import { KoishiService } from '../src/koishi.service';
import { testingModule } from './utility/testing-module';
import { KoishiWsAdapter } from '../src/koishi.ws-adapter';
import http from 'http';
import { Context } from 'koishi';
describe('Koishi module in Fastify adapter', () => {
let app: NestFastifyApplication;
......@@ -19,8 +20,10 @@ describe('Koishi module in Fastify adapter', () => {
}),
);
app.useWebSocketAdapter(new KoishiWsAdapter(app));
await app.init();
Context.service('ping');
koishiApp = app.get(KoishiService);
koishiApp['ping'] = { ping: 'pong' };
await app.init();
});
it('should define koishi', () => {
......
......@@ -2,9 +2,10 @@ import { KoishiService } from '../src/koishi.service';
import { KoishiWsAdapter } from '../src/koishi.ws-adapter';
import http from 'http';
import request from 'supertest';
import { Session } from 'koishi';
import { Context, Session } from 'koishi';
import { testingModule } from './utility/testing-module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { EventName } from 'koishi-decorators';
describe('Koishi in Nest.js', () => {
let app: NestExpressApplication;
......@@ -15,8 +16,10 @@ describe('Koishi in Nest.js', () => {
app = moduleFixture.createNestApplication<NestExpressApplication>();
app.useWebSocketAdapter(new KoishiWsAdapter(app));
app.set('trust proxy', ['loopback']);
await app.init();
Context.service('ping');
koishiApp = app.get(KoishiService);
koishiApp['ping'] = { ping: 'pong' };
await app.init();
});
it('should define koishi', () => {
......@@ -128,4 +131,13 @@ describe('Koishi in Nest.js', () => {
expect(command).toBeDefined();
expect(command.execute({ options: {} })).resolves.toBe('miiii');
});
it('should handle partial dep', async () => {
koishiApp['ping'] = { ping: 'pong' };
expect(await koishiApp.waterfall(<EventName>'ping')).toBe('pong');
koishiApp['ping'] = undefined;
expect(await koishiApp.waterfall(<EventName>'ping')).toBeUndefined();
koishiApp['ping'] = { ping: 'pong' };
expect(await koishiApp.waterfall(<EventName>'ping')).toBe('pong');
});
});
......@@ -8,8 +8,12 @@ import {
PutOption,
PutValue,
UseCommand,
UseEvent,
} from 'koishi-decorators';
import { CommandInterceptors } from '../../src/utility/koishi.decorators';
import {
CommandInterceptors,
UsingService,
} from '../../src/utility/koishi.decorators';
import { Test } from '@nestjs/testing';
import { KoishiModule } from '../../src/koishi.module';
......@@ -73,6 +77,14 @@ class KoishiTestService {
async onAbstract(@PutValue('{{abstract.content}}') content: string) {
return content;
}
@UsingService('ping')
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
@UseEvent('ping')
async onPing() {
return 'pong';
}
}
export function testingModule() {
......
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