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';
export * from 'koishi-decorators/dist/src/def/interfaces';
......@@ -33,8 +33,6 @@ export type ParamsFromClass<T> = T extends { new (...args: infer U): any }
? U
: never;
export type MultiPluginConfig<Inner, Outer> = Instances<Inner> & Outer;
export type PluginClass<C = any, P = any> = new (ctx: Context, config: C) => P;
export type ClassPluginConfig<P extends PluginClass> = P extends PluginClass<
......@@ -42,3 +40,11 @@ export type ClassPluginConfig<P extends PluginClass> = P extends PluginClass<
>
? C
: 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 './multi-plugin';
export * from './merge-plugin';
import { Dict, Selection } from 'koishi';
import { ClassPluginConfig, PluginClass } from '../def';
import { BasePlugin } from '../base-plugin';
import { LifecycleEvents } from '../register';
import { reflector } from '../meta/meta-fetch';
import { Dict } from 'koishi';
import { MapPluginToConfigWithSelection, PluginClass } from '../def';
import { ClassType, SchemaProperty } from 'schemastery-gen';
import { CreatePluginFactory } from '../plugin-factory';
import { ClonePlugin } from '../utility/clone-plugin';
import { UseEvent } from 'koishi-decorators';
import { MappingPluginBase } from './mapping-base';
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>>(
dict: M,
): ClassType<MapPluginToConfig<M>> {
): ClassType<MapPluginToConfigWithSelection<M>> {
const PropertySchema = class SpecificPropertySchema {} as ClassType<
MapPluginToConfig<M>
MapPluginToConfigWithSelection<M>
>;
for (const [key, plugin] of Object.entries(dict)) {
const propertySchemaClass =
plugin['Config'] ||
plugin['schema'] ||
reflector.get('KoishiPredefineSchema', plugin);
SchemaProperty({
type: propertySchemaClass,
type: getPluginSchema(plugin),
})(PropertySchema.prototype, key);
}
return PropertySchema;
......@@ -69,10 +23,17 @@ export function MapPlugin<M extends Dict<PluginClass>, OuterConfig>(
dict: M,
outerConfig?: ClassType<OuterConfig>,
) {
const basePlugin = class SpecificMapPlugin extends MapPluginBase<M> {
const basePlugin = class SpecificMapPlugin extends MappingPluginBase<
M,
MapPluginToConfigWithSelection<M>,
Partial<MapPluginToConfigWithSelection<M>>
> {
_getDict() {
return dict;
}
_getPluginConfig(key: keyof M): any {
return this.config[key];
}
};
const schema = MappedConfig(dict);
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';
import { UseCommand } from 'koishi-decorators';
import { MapPlugin } from '../src/plugin-operators';
import { App } from 'koishi';
import { MergePlugin } from '../src/plugin-operators/merge-plugin';
class DressConfig {
@SchemaProperty()
......@@ -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', () => {
it('should work on each level', async () => {
const app = new App();
......@@ -71,4 +83,16 @@ describe('register map plugin instance', () => {
expect(await app.command('wearingStrip').execute({})).toBe('pink');
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