Commit 713994cb authored by nanahira's avatar nanahira

add map plugin

parent 0f22a31e
......@@ -4,6 +4,7 @@ export * from './src/decorators';
export * from './src/cosmotype-exports';
export * from './src/base-plugin';
export * from './src/multi-plugin';
export * from './src/map-plugin';
export * from './src/plugin-factory';
export * from './src/def/interfaces';
export * from 'schemastery-gen';
import { Context } from 'koishi';
import { PluginClass } from '../register';
import { PluginMeta } from '../register';
export * from 'koishi-decorators/dist/src/def/interfaces';
// Command stuff
export type SystemInjectFun = <T = any>(obj: PluginClass<T>) => any;
export type SystemInjectFun = <T = any>(obj: PluginMeta<T>) => any;
export interface ProvideOptions {
immediate?: boolean;
......@@ -35,6 +35,10 @@ export type ParamsFromClass<T> = T extends { new (...args: infer U): any }
export type MultiPluginConfig<Inner, Outer> = Instances<Inner> & Outer;
export type ClassPluginConfig<
P extends new (ctx: Context, config: any) => any,
> = P extends new (ctx: Context, config: infer C) => any ? C : never;
export type PluginClass<C = any, P = any> = new (ctx: Context, config: C) => P;
export type ClassPluginConfig<P extends PluginClass> = P extends PluginClass<
infer C
>
? C
: never;
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 { ClassType, SchemaProperty } from 'schemastery-gen';
import { CreatePluginFactory } from './plugin-factory';
import { ClonePlugin } from './utility/clone-plugin';
import { UseEvent } from 'koishi-decorators';
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>, 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) {
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>> {
const PropertySchema: ClassType<
MapPluginToConfig<M>
> = class SpecificPropertySchema {};
for (const [key, plugin] of Object.entries(dict)) {
const propertySchemaClass =
plugin['Config'] ||
plugin['schema'] ||
reflector.get('KoishiPredefineSchema', plugin);
SchemaProperty({ type: propertySchemaClass, default: undefined })(
PropertySchema.prototype,
key,
);
}
return PropertySchema;
}
export function MapPlugin<M extends Dict<PluginClass>, OuterConfig>(
dict: M,
outerConfig?: ClassType<OuterConfig>,
) {
const basePlugin = class SpecificMapPlugin extends MapPluginBase<M> {
_getDict() {
return dict;
}
};
const schema = MappedConfig(dict);
const factory = CreatePluginFactory(basePlugin, schema);
return factory(outerConfig);
}
import { ClonePlugin } from './utility/clone-plugin';
import { Context } from 'koishi';
import { BasePlugin } from './base-plugin';
import { ClassPluginConfig, Instances, TypeFromClass } from './def';
import {
ClassPluginConfig,
Instances,
PluginClass,
TypeFromClass,
} from './def';
import { ClassType } from 'schemastery-gen';
import { ToInstancesConfig } from './utility/to-instance-config';
import Schema from 'schemastery';
import { UsingService } from './decorators';
import { UseEvent } from 'koishi-decorators';
import { CreatePluginFactory } from './plugin-factory';
import { LifecycleEvents } from './register';
export class MultiInstancePluginFramework<
InnerPlugin extends new (ctx: Context, config: any) => any,
> extends BasePlugin<
Instances<ClassPluginConfig<InnerPlugin>>,
Instances<ClassPluginConfig<InnerPlugin>>
> {
export class MultiInstancePluginFramework<InnerPlugin extends PluginClass>
extends BasePlugin<
Instances<ClassPluginConfig<InnerPlugin>>,
Instances<ClassPluginConfig<InnerPlugin>>
>
implements LifecycleEvents
{
instances: TypeFromClass<InnerPlugin>[] = [];
_getInnerPlugin(): InnerPlugin {
......@@ -26,7 +33,7 @@ export class MultiInstancePluginFramework<
for (let i = 0; i < this.config.instances.length; i++) {
const clonedInnerPlugin = ClonePlugin(
innerPlugin,
`${innerPlugin.name}_instance_${i}`,
`${this.constructor.name}_${innerPlugin.name}_instance_${i}`,
(instance) => this.instances.push(instance),
);
this.ctx.plugin(clonedInnerPlugin, this.config.instances[i]);
......@@ -44,7 +51,7 @@ export class MultiInstancePluginFramework<
}
export function MultiInstancePlugin<
InnerPlugin extends new (ctx: Context, config: any) => any,
InnerPlugin extends PluginClass,
OuterConfig,
>(innerPlugin: InnerPlugin, outerConfig?: ClassType<OuterConfig>) {
const basePlugin = class SpecificMultiInstancePlugin extends MultiInstancePluginFramework<InnerPlugin> {
......
......@@ -2,9 +2,10 @@ import { Context } from 'koishi';
import { AnyClass, ClassType, Mixin } from 'schemastery-gen';
import { PluginSchema } from './decorators';
import { PartialDeep } from './base-plugin';
import { PluginClass } from './def';
export function CreatePluginFactory<C, IC, P extends { config: IC }>(
basePlugin: new (ctx: Context, config: C) => P,
basePlugin: PluginClass<C, P>,
baseConfig: ClassType<IC>,
): <S>(specificConfig?: ClassType<S>) => new (
ctx: Context,
......@@ -13,7 +14,7 @@ export function CreatePluginFactory<C, IC, P extends { config: IC }>(
config: IC & S;
};
export function CreatePluginFactory(
basePlugin: new (ctx: Context, config: any) => any,
basePlugin: PluginClass,
baseConfig: AnyClass,
) {
return (specificConfig: AnyClass) => {
......
......@@ -21,7 +21,7 @@ export interface KoishiPluginRegistrationOptions<T = any> {
using?: (keyof Context.Services)[];
}
export interface PluginClass<T = any> {
export interface PluginMeta<T = any> {
__ctx: Context;
__config: T;
__registrar: Registrar;
......@@ -66,7 +66,7 @@ export function DefinePlugin<T = any>(
if (originalClass[ThirdEyeSym]) {
return originalClass;
}
const newClass = class extends originalClass implements PluginClass {
const newClass = class extends originalClass implements PluginMeta {
static get Config() {
const schemaType =
reflector.get('KoishiPredefineSchema', newClass) ||
......
import { SchemaProperty } from 'schemastery-gen';
import { StarterPlugin } from '../src/base-plugin';
import { DefinePlugin } from '../src/register';
import { UseCommand } from 'koishi-decorators';
import { MapPlugin } from '../src/map-plugin';
import { App } from 'koishi';
class DressConfig {
@SchemaProperty()
color: string;
}
@DefinePlugin()
class DressPlugin extends StarterPlugin(DressConfig) {
@UseCommand('dressColor')
dressColor() {
return this.config.color;
}
}
class SkirtConfig {
@SchemaProperty()
size: string;
}
@DefinePlugin()
class SkirtPlugin extends StarterPlugin(SkirtConfig) {
@UseCommand('skirtSize')
skirtSize() {
return this.config.size;
}
}
class WearingConfig {
@SchemaProperty()
strip: string;
}
@DefinePlugin()
class WearingPlugin extends MapPlugin(
{ 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();
app.plugin(WearingPlugin, {
dress: { color: 'red' },
skirt: { 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