Commit 19fceeb4 authored by nanahira's avatar nanahira

add MergePlugin

parent 5556e9cf
import { Context } from 'koishi'; import { Context, Dict, Selection } from 'koishi';
import { PluginMeta } from '../register'; import { PluginMeta } from '../register';
export * from 'koishi-decorators/dist/src/def/interfaces'; export * from 'koishi-decorators/dist/src/def/interfaces';
...@@ -33,8 +33,6 @@ export type ParamsFromClass<T> = T extends { new (...args: infer U): any } ...@@ -33,8 +33,6 @@ export type ParamsFromClass<T> = T extends { new (...args: infer U): any }
? U ? U
: never; : never;
export type MultiPluginConfig<Inner, Outer> = Instances<Inner> & Outer;
export type PluginClass<C = any, P = any> = new (ctx: Context, config: C) => P; export type PluginClass<C = any, P = any> = new (ctx: Context, config: C) => P;
export type ClassPluginConfig<P extends PluginClass> = P extends PluginClass< export type ClassPluginConfig<P extends PluginClass> = P extends PluginClass<
...@@ -42,3 +40,11 @@ export type ClassPluginConfig<P extends PluginClass> = P extends PluginClass< ...@@ -42,3 +40,11 @@ export type ClassPluginConfig<P extends PluginClass> = P extends PluginClass<
> >
? C ? C
: never; : never;
export type MapPluginToConfig<M extends Dict<PluginClass>> = {
[K in keyof M]: ClassPluginConfig<M[K]>;
};
export type MapPluginToConfigWithSelection<M extends Dict<PluginClass>> = {
[K in keyof M]: ClassPluginConfig<M[K]> & Selection;
};
export * from './map-plugin'; export * from './map-plugin';
export * from './multi-plugin'; export * from './multi-plugin';
export * from './merge-plugin';
import { Dict, Selection } from 'koishi'; import { Dict } from 'koishi';
import { ClassPluginConfig, PluginClass } from '../def'; import { MapPluginToConfigWithSelection, PluginClass } from '../def';
import { BasePlugin } from '../base-plugin';
import { LifecycleEvents } from '../register';
import { reflector } from '../meta/meta-fetch';
import { ClassType, SchemaProperty } from 'schemastery-gen'; import { ClassType, SchemaProperty } from 'schemastery-gen';
import { CreatePluginFactory } from '../plugin-factory'; import { CreatePluginFactory } from '../plugin-factory';
import { ClonePlugin } from '../utility/clone-plugin'; import { MappingPluginBase } from './mapping-base';
import { UseEvent } from 'koishi-decorators'; import { getPluginSchema } from '../utility/get-schema';
type MapPluginToConfig<M extends Dict<PluginClass>> = {
[K in keyof M]: ClassPluginConfig<M[K]> & Selection;
};
export class MapPluginBase<M extends Dict<PluginClass>>
extends BasePlugin<MapPluginToConfig<M>, Partial<MapPluginToConfig<M>>>
implements LifecycleEvents
{
_getDict(): M {
throw new Error('not implemented');
}
_instanceMap = new Map<string, PluginClass>();
getInstance<K extends keyof M>(key: K): M[K] {
return this._instanceMap?.get(key as string) as M[K];
}
onApply() {
const dict = this._getDict();
for (const [key, plugin] of Object.entries(dict)) {
if (this.config[key] == null) continue;
const ctx =
typeof this.config[key] === 'object'
? this.ctx.select(this.config[key])
: this.ctx;
const clonedPlugin = ClonePlugin(
plugin,
`${this.constructor.name}_${plugin.name}_dict_${key}`,
(o) => this._instanceMap.set(key, o),
);
ctx.plugin(clonedPlugin, this.config[key]);
}
}
@UseEvent('dispose')
_onThingsDispose() {
delete this._instanceMap;
}
}
function MappedConfig<M extends Dict<PluginClass>>( function MappedConfig<M extends Dict<PluginClass>>(
dict: M, dict: M,
): ClassType<MapPluginToConfig<M>> { ): ClassType<MapPluginToConfigWithSelection<M>> {
const PropertySchema = class SpecificPropertySchema {} as ClassType< const PropertySchema = class SpecificPropertySchema {} as ClassType<
MapPluginToConfig<M> MapPluginToConfigWithSelection<M>
>; >;
for (const [key, plugin] of Object.entries(dict)) { for (const [key, plugin] of Object.entries(dict)) {
const propertySchemaClass =
plugin['Config'] ||
plugin['schema'] ||
reflector.get('KoishiPredefineSchema', plugin);
SchemaProperty({ SchemaProperty({
type: propertySchemaClass, type: getPluginSchema(plugin),
})(PropertySchema.prototype, key); })(PropertySchema.prototype, key);
} }
return PropertySchema; return PropertySchema;
...@@ -69,10 +23,17 @@ export function MapPlugin<M extends Dict<PluginClass>, OuterConfig>( ...@@ -69,10 +23,17 @@ export function MapPlugin<M extends Dict<PluginClass>, OuterConfig>(
dict: M, dict: M,
outerConfig?: ClassType<OuterConfig>, outerConfig?: ClassType<OuterConfig>,
) { ) {
const basePlugin = class SpecificMapPlugin extends MapPluginBase<M> { const basePlugin = class SpecificMapPlugin extends MappingPluginBase<
M,
MapPluginToConfigWithSelection<M>,
Partial<MapPluginToConfigWithSelection<M>>
> {
_getDict() { _getDict() {
return dict; return dict;
} }
_getPluginConfig(key: keyof M): any {
return this.config[key];
}
}; };
const schema = MappedConfig(dict); const schema = MappedConfig(dict);
const factory = CreatePluginFactory(basePlugin, schema); const factory = CreatePluginFactory(basePlugin, schema);
......
import { Dict } from 'koishi';
import { PluginClass } from '../../def';
import { BasePlugin, PartialDeep } from '../../base-plugin';
import { LifecycleEvents } from '../../register';
import { ClonePlugin } from '../../utility/clone-plugin';
import { UseEvent } from 'koishi-decorators';
export class MappingPluginBase<
M extends Dict<PluginClass>,
C,
PC = PartialDeep<C>,
>
extends BasePlugin<C, PC>
implements LifecycleEvents
{
_getDict(): M {
throw new Error('not implemented');
}
_instanceMap = new Map<string, PluginClass>();
getInstance<K extends keyof M>(key: K): M[K] {
return this._instanceMap?.get(key as string) as M[K];
}
_getPluginConfig(key: keyof M): any {
return {};
}
onApply() {
const dict = this._getDict();
for (const [key, plugin] of Object.entries(dict)) {
const config = this._getPluginConfig(key);
if (config == null) continue;
const ctx =
typeof config === 'object' ? this.ctx.select(config) : this.ctx;
const clonedPlugin = ClonePlugin(
plugin,
`${this.constructor.name}_${plugin.name}_dict_${key}`,
(o) => this._instanceMap.set(key, o),
);
ctx.plugin(clonedPlugin, config);
}
}
@UseEvent('dispose')
_onThingsDispose() {
delete this._instanceMap;
}
}
import { MapPluginToConfig, PluginClass } from '../def';
import { Dict } from 'koishi';
import { AnyClass, ClassType, Mixin } from 'schemastery-gen';
import { MappingPluginBase } from './mapping-base';
import { CreatePluginFactory } from '../plugin-factory';
import _ from 'lodash';
import { getPluginSchema } from '../utility/get-schema';
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (
x: infer R,
) => any
? R
: never;
type MergePluginConfig<M extends Dict<PluginClass>> = UnionToIntersection<
MapPluginToConfig<M>[keyof M]
>;
export function MergePlugin<M extends Dict<PluginClass>, OuterConfig>(
dict: M,
outerConfig?: ClassType<OuterConfig>,
) {
const basePlugin = class SpecificMapPlugin extends MappingPluginBase<
M,
MergePluginConfig<M>,
MergePluginConfig<M>
> {
_getDict() {
return dict;
}
_getPluginConfig(key: keyof M): any {
return this.config;
}
};
const schemas = _.compact(
Object.values(dict).map((plugin) => getPluginSchema(plugin)),
);
const factory = CreatePluginFactory(basePlugin, Mixin(...schemas));
return factory(outerConfig);
}
import { PluginClass } from '../def';
import { BasePlugin } from '../base-plugin';
import { ClassType } from 'schemastery-gen';
import { reflector } from '../meta/meta-fetch';
export function getPluginSchema<P extends PluginClass>(
plugin: P,
): ClassType<
P extends BasePlugin<any, infer PC>
? PC
: P extends PluginClass<infer C>
? C
: never
> {
return (
plugin['Config'] ||
plugin['schema'] ||
reflector.get('KoishiPredefineSchema', plugin)
);
}
...@@ -4,6 +4,7 @@ import { DefinePlugin } from '../src/register'; ...@@ -4,6 +4,7 @@ import { DefinePlugin } from '../src/register';
import { UseCommand } from 'koishi-decorators'; import { UseCommand } from 'koishi-decorators';
import { MapPlugin } from '../src/plugin-operators'; import { MapPlugin } from '../src/plugin-operators';
import { App } from 'koishi'; import { App } from 'koishi';
import { MergePlugin } from '../src/plugin-operators/merge-plugin';
class DressConfig { class DressConfig {
@SchemaProperty() @SchemaProperty()
...@@ -47,6 +48,17 @@ class WearingPlugin extends MapPlugin( ...@@ -47,6 +48,17 @@ class WearingPlugin extends MapPlugin(
} }
} }
@DefinePlugin()
class MergedWearingPlugin extends MergePlugin(
{ dress: DressPlugin, skirt: SkirtPlugin },
WearingConfig,
) {
@UseCommand('wearingStrip')
wearingStrip() {
return this.config.strip;
}
}
describe('register map plugin instance', () => { describe('register map plugin instance', () => {
it('should work on each level', async () => { it('should work on each level', async () => {
const app = new App(); const app = new App();
...@@ -71,4 +83,16 @@ describe('register map plugin instance', () => { ...@@ -71,4 +83,16 @@ describe('register map plugin instance', () => {
expect(await app.command('wearingStrip').execute({})).toBe('pink'); expect(await app.command('wearingStrip').execute({})).toBe('pink');
expect(await app.command('skirtSize').execute({})).toBe('S'); expect(await app.command('skirtSize').execute({})).toBe('S');
}); });
it('should work on merge plugin', async () => {
const app = new App();
app.plugin(MergedWearingPlugin, {
color: 'red',
size: 'XL',
strip: 'pink',
});
await app.start();
expect(await app.command('dressColor').execute({})).toBe('red');
expect(await app.command('skirtSize').execute({})).toBe('XL');
expect(await app.command('wearingStrip').execute({})).toBe('pink');
});
}); });
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