Commit f5b68fa1 authored by nanahira's avatar nanahira

use CallbackLayer and add UsingService

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