Commit 4f8f9c81 authored by nanahira's avatar nanahira

adapt Koishi v4.8

parent d7df8e31
This diff is collapsed.
......@@ -33,7 +33,7 @@
"peerDependencies": {
"@nestjs/common": "^9.0.3 || ^8.0.0",
"@nestjs/core": "^9.0.3 || ^8.0.0",
"koishi": "4.7.6",
"koishi": "^4.8.1",
"rxjs": "^7.5.5"
},
"devDependencies": {
......@@ -66,9 +66,9 @@
"@types/ws": "^8.5.3",
"koa": "^2.13.4",
"koa-bodyparser": "^4.3.0",
"koishi-decorators": "^2.1.7",
"koishi-thirdeye": "^11.0.2",
"lodash": "^4.17.21",
"typed-reflector": "^1.0.10",
"typed-reflector": "^1.0.11",
"ws": "^8.7.0"
},
"jest": {
......
import { App, Command } from 'koishi';
import { Command, Context } from 'koishi';
import {
Inject,
Injectable,
......@@ -16,7 +16,6 @@ import { KoishiMetascanService } from './providers/koishi-metascan.service';
import { KOISHI_MODULE_OPTIONS, KoishiIpSym } from './utility/koishi.constants';
import { KoishiLoggerService } from './providers/koishi-logger.service';
import { KoishiHttpDiscoveryService } from './koishi-http-discovery/koishi-http-discovery.service';
import { applySelector } from 'koishi-decorators';
import WebSocket from 'ws';
import { KoishiNestRouter } from './utility/koa-router';
import './utility/koishi.workarounds';
......@@ -24,7 +23,7 @@ import './utility/koishi.declares';
@Injectable()
export class KoishiService
extends App
extends Context
implements OnModuleInit, OnModuleDestroy
{
constructor(
......@@ -57,15 +56,15 @@ export class KoishiService
const httpServer: Server = httpAdapter?.getHttpServer();
if (httpServer && httpServer instanceof Server) {
this.logger('app').info('App using Nest HTTP Server.');
this._httpServer = httpServer;
this.router._http = httpServer;
} else {
this.logger('app').info('No http adapters found from Nest application.');
this._httpServer = createServer(this._nestKoaTmpInstance.callback());
this._wsServer = new WebSocket.Server({
server: this._httpServer,
this.router._http = createServer(this._nestKoaTmpInstance.callback());
this.router._ws = new WebSocket.Server({
server: this.router._http,
});
this._wsServer.on('connection', (socket, request) => {
this.router._ws.on('connection', (socket, request) => {
for (const manager of this.router.wsStack) {
if (manager.accept(socket, request)) return;
}
......@@ -79,8 +78,7 @@ export class KoishiService
this.metascan.preRegisterContext(this.any());
if (this.koishiModuleOptions.usePlugins) {
for (const pluginDef of this.koishiModuleOptions.usePlugins) {
const ctx = applySelector(this, pluginDef);
ctx.plugin(pluginDef.plugin, pluginDef.options);
this.plugin(pluginDef.plugin, pluginDef.options);
}
}
await this.metascan.registerContext(this.any());
......
......@@ -4,9 +4,10 @@ import {
KoishiModuleOptions,
KoishiModuleSelection,
} from '../utility/koishi.interfaces';
import { applySelector, Registrar } from 'koishi-decorators';
import { Context } from 'koishi';
import { Module } from '@nestjs/core/injector/module';
import { selectContext } from 'koishi-thirdeye';
import { koishiRegistrar } from 'koishi-thirdeye/dist/src/registrar';
@Injectable()
export class KoishiContextService {
......@@ -24,7 +25,7 @@ export class KoishiContextService {
getModuleCtx(ctx: Context, module: Module) {
const moduleSelection = this.moduleSelections.get(module.metatype);
if (moduleSelection) {
return applySelector(ctx, moduleSelection);
return selectContext(ctx, moduleSelection);
} else {
return ctx;
}
......@@ -32,11 +33,9 @@ export class KoishiContextService {
getProviderCtx(ctx: Context, ...instances: any[]) {
for (const instance of instances) {
ctx = new Registrar(
instance,
undefined,
this.options.templateParams || {},
).getScopeContext(ctx);
ctx = koishiRegistrar
.aspect(instance, this.options.templateParams || {})
.getScopeContext(ctx);
}
return ctx;
}
......
......@@ -17,9 +17,11 @@ import { KoishiContextService } from './koishi-context.service';
import { Module } from '@nestjs/core/injector/module';
import { KoishiMetadataFetcherService } from '../koishi-metadata-fetcher/koishi-metadata-fetcher.service';
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 { takeFirstValue } from '../utility/take-first-value';
import { registerAtLeastEach } from '../utility/take-first-value';
import { koishiRegistrar } from 'koishi-thirdeye/dist/src/registrar';
import { CommandConfigExtended } from 'koishi-thirdeye/dist/src/def';
import { map } from 'rxjs';
@Injectable()
export class KoishiMetascanService {
......@@ -46,53 +48,6 @@ export class KoishiMetascanService {
return this.intercepterManager.addInterceptors(command, interceptorDefs);
}
private async handleInstanceRegistration(
ctx: Context,
instance: Record<string, any>,
methodKey: string,
) {
const registrar = new Registrar(instance, undefined, this.templateParams);
const scopeContext = registrar.getScopeContext(ctx, methodKey, false);
return takeFirstValue(
registrar.runLayers(
scopeContext,
async (baseContext) => {
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);
}
}, true);
}
} else if (result.type === 'plugin') {
const mayBePromise = result.result as Promise<any>;
if (mayBePromise instanceof Promise) {
await mayBePromise;
}
}
},
methodKey,
),
);
}
private registerOnService(
ctx: Context,
instance: any,
......@@ -178,30 +133,51 @@ export class KoishiMetascanService {
);
}
private mutateCommandRegistration(
instance: any,
key: string,
command: Command,
) {
const interceptorDefs: KoishiCommandInterceptorRegistration[] = _.uniq(
this.metaFetcher.getPropertyMetadataArray(
KoishiCommandInterceptorDef,
instance,
key,
),
);
this.addInterceptors(command, interceptorDefs);
const config = command.config as CommandConfigExtended;
if (!config?.empty) {
command.action(async (argv) => {
try {
return await argv.next();
} catch (e) {
return this.exceptionHandler.handleActionException(e);
}
}, true);
}
}
registerContext(ctx: Context) {
return Promise.all(
this.runForEachProvider(ctx, (providerCtx, instance) => {
this.scanInstanceForProvidingContextService(providerCtx, instance);
const registrar = new Registrar(
instance,
undefined,
this.templateParams,
);
return takeFirstValue(
registrar.runLayers(providerCtx, (providerInnerCtx) => {
registrar.performTopActions(providerInnerCtx);
return Promise.all(
registrar
.getAllFieldsToRegister()
.map((methodKey: string) =>
this.handleInstanceRegistration(
providerInnerCtx,
instance,
methodKey,
),
),
);
}),
const registrar = koishiRegistrar.aspect(instance, this.templateParams);
const allFields = registrar.getAllFieldsToRegister();
return registerAtLeastEach(
registrar.register(providerCtx).pipe(
map((result) => {
if (result.type === 'command') {
this.mutateCommandRegistration(
instance,
result.key,
result.result,
);
}
return result;
}),
),
allFields,
);
}),
);
......
......@@ -20,7 +20,7 @@ export class KoishiWebsocketGateway
afterInit(server: any): any {
// console.log('Init ws server', server, server === this.wsServer);
this.wsServer.path = '__koishi_fallback';
this.koishi._wsServer = this.wsServer;
this.koishi.router._ws = this.wsServer;
}
handleConnection(socket: WebSocket, request: IncomingMessage) {
......
......@@ -20,7 +20,6 @@ import {
ContextScopeTypes,
getContextProvideToken,
} from './koishi-context.factory';
import { CallbackLayer } from 'koishi-decorators';
// Injections
export const InjectContext = () => Inject(KOISHI_CONTEXT);
......@@ -95,8 +94,8 @@ export const ConcatMetadata = <K extends keyof MetadataArrayValueMap>(
// Export all koishi-decorator decorators
export * from 'koishi-decorators/dist/src/decorators';
export { PluginDef } from 'koishi-decorators';
export * from 'koishi-thirdeye/dist/src/decorators/common';
export { PluginDef } from 'koishi-thirdeye';
// Service
......@@ -116,17 +115,11 @@ export function WireContextService(name?: ServiceName): PropertyDecorator {
};
}
export function ProvideContextService(
name: ServiceName,
options: Context.ServiceOptions,
): ClassDecorator {
Context.service(name, options);
export function ProvideContextService(name: ServiceName): ClassDecorator {
Context.service(name);
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, Context, User } from 'koishi';
import { MetadataArrayMap, MetadataMap } from './koishi.constants';
import { ContextSelector, PluginDefinition } from 'koishi-decorators';
import { PluginRegistrar, Selection } from 'koishi-thirdeye';
export * from 'koishi-decorators/dist/src/def/interfaces';
export * from 'koishi-thirdeye/dist/src/def';
export interface KoishiModuleSelection extends ContextSelector {
export interface KoishiModuleSelection extends Selection {
module: Type<any>;
}
......@@ -17,7 +17,7 @@ export interface KoishiModuleTopOptions {
export interface KoishiModuleOptions
extends App.Config,
KoishiModuleTopOptions {
usePlugins?: PluginDefinition<any>[];
usePlugins?: PluginRegistrar.PluginDefinition<any>[];
loggerPrefix?: string;
loggerColor?: number;
moduleSelection?: KoishiModuleSelection[];
......
......@@ -20,6 +20,8 @@ export class IntercepterManagerService {
return this[Context.current] || this.ctx;
}
static methods = ['withInterceptors'];
withInterceptors(
interceptors: KoishiCommandInterceptorRegistration[],
): Context {
......@@ -30,7 +32,4 @@ export class IntercepterManagerService {
}
}
Context.service('$interceptorManager', {
constructor: IntercepterManagerService,
methods: ['withInterceptors'],
});
Context.service('$interceptorManager', IntercepterManagerService);
import { Observable } from 'rxjs';
export function takeFirstValue<T>(obs: Observable<T>): Promise<T> {
export function registerAtLeastEach<T extends { key: string }>(
obs: Observable<T>,
keys: string[],
): Promise<T> {
const remainingKeys = new Set(keys);
return new Promise<T>((resolve, reject) => {
let resolved = false;
let lastValue: T = undefined;
obs.subscribe({
next: (value) => {
if (!resolved) {
lastValue = value;
remainingKeys.delete(value.key);
if (!resolved && remainingKeys.size === 0) {
resolve(value);
resolved = true;
}
......@@ -18,7 +25,7 @@ export function takeFirstValue<T>(obs: Observable<T>): Promise<T> {
},
complete: () => {
if (!resolved) {
resolve(undefined);
resolve(lastValue);
resolved = true;
}
},
......
......@@ -31,13 +31,13 @@ describe('Koishi module in Fastify adapter', () => {
});
it('should register http and ws server', () => {
expect(koishiApp._httpServer).toBeDefined();
expect(koishiApp._wsServer).toBeDefined();
expect(koishiApp.router._http).toBeDefined();
expect(koishiApp.router._ws).toBeDefined();
});
it('should be nest http server', () => {
expect(koishiApp._httpServer).toBeInstanceOf(http.Server);
expect(app.getHttpServer()).toEqual(koishiApp._httpServer);
expect(koishiApp.router._http).toBeInstanceOf(http.Server);
expect(app.getHttpServer()).toEqual(koishiApp.router._http);
});
it('should response to koishi routes', () => {
......
......@@ -2,10 +2,11 @@ import { KoishiService } from '../src/koishi.service';
import { KoishiWsAdapter } from '../src/koishi.ws-adapter';
import http from 'http';
import request from 'supertest';
import { Context, Session } from 'koishi';
import { Context, Events, Session } from 'koishi';
import { testingModule } from './utility/testing-module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { EventName } from 'koishi-decorators';
type EventName = keyof Events;
describe('Koishi in Nest.js', () => {
let app: NestExpressApplication;
......@@ -27,13 +28,13 @@ describe('Koishi in Nest.js', () => {
});
it('should register http and ws server', () => {
expect(koishiApp._httpServer).toBeDefined();
expect(koishiApp._wsServer).toBeDefined();
expect(koishiApp.router._http).toBeDefined();
expect(koishiApp.router._ws).toBeDefined();
});
it('should be nest http server', () => {
expect(koishiApp._httpServer).toBeInstanceOf(http.Server);
expect(app.getHttpServer()).toEqual(koishiApp._httpServer);
expect(koishiApp.router._http).toBeInstanceOf(http.Server);
expect(app.getHttpServer()).toEqual(koishiApp.router._http);
});
it('should response to koishi routes', () => {
......
......@@ -19,6 +19,7 @@ describe('Koishi in Nest.js context', () => {
});
it('should register http service', () => {
expect(koishiApp._httpServer).toBeDefined();
expect(koishiApp.router._http).toBeDefined();
expect(koishiApp.router._ws).toBeDefined();
});
});
......@@ -9,7 +9,7 @@ import {
PutValue,
UseCommand,
UseEvent,
} from 'koishi-decorators';
} from 'koishi-thirdeye';
import {
CommandInterceptors,
UsingService,
......
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