Commit 3203665d authored by nanahira's avatar nanahira

add @ProvideMixin() and support mixin in @Inject()

parent 6ca0c8f0
......@@ -31,7 +31,7 @@
"typescript": "^4.7.4"
},
"peerDependencies": {
"cordis": "^2.6.0",
"cordis": "^2.7.2",
"schemastery": "^3.5.1"
}
},
......@@ -1845,12 +1845,12 @@
}
},
"node_modules/cordis": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/cordis/-/cordis-2.6.0.tgz",
"integrity": "sha512-4VUY2x6ufctBr1zYAML3c+b1eXwgY94nkqLP7/icb3QVGXMBJuH4Nztakf6ADVonN5MvgJ06RRawRvmWdx3LxA==",
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/cordis/-/cordis-2.7.2.tgz",
"integrity": "sha512-bM+4P1bhIawrXRLvBMoxfWwSHebm0B3n0sM+T/FCED/iMUnEs6N9Y7lwJ2gDo2fm0wvu4JzU8cuMKrxqroq16Q==",
"peer": true,
"dependencies": {
"cosmokit": "^1.3.3"
"cosmokit": "^1.4.0"
}
},
"node_modules/cosmokit": {
......@@ -6184,12 +6184,12 @@
}
},
"cordis": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/cordis/-/cordis-2.6.0.tgz",
"integrity": "sha512-4VUY2x6ufctBr1zYAML3c+b1eXwgY94nkqLP7/icb3QVGXMBJuH4Nztakf6ADVonN5MvgJ06RRawRvmWdx3LxA==",
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/cordis/-/cordis-2.7.2.tgz",
"integrity": "sha512-bM+4P1bhIawrXRLvBMoxfWwSHebm0B3n0sM+T/FCED/iMUnEs6N9Y7lwJ2gDo2fm0wvu4JzU8cuMKrxqroq16Q==",
"peer": true,
"requires": {
"cosmokit": "^1.3.3"
"cosmokit": "^1.4.0"
}
},
"cosmokit": {
......
......@@ -54,7 +54,7 @@
"typescript": "^4.7.4"
},
"peerDependencies": {
"cordis": "^2.6.0",
"cordis": "^2.7.2",
"schemastery": "^3.5.1"
},
"dependencies": {
......
......@@ -28,16 +28,19 @@ export function Inject(...args: [(string | boolean)?, boolean?]) {
return pluginDecorators.Inject(...args);
}
export function Internal(): MethodDecorator & PropertyDecorator {
export const ProvideMixin = (): MethodDecorator & PropertyDecorator => {
return (obj, key, des?) => {
const cls = obj.constructor as Context.MixinOptions;
const field = des ? 'methods' : 'properties';
if (!cls[field]) {
cls[field] = [];
}
cls[field].push(key);
defaultRegistrar.metadata.set('CordisMixin', field, 'CordisMixinKeys')(
obj,
key,
des,
);
};
}
};
// for backward compatibility
export const Internal = ProvideMixin;
export function Accept(
options?: AcceptOptions,
......
......@@ -14,6 +14,7 @@ import Schema from 'schemastery';
import { PluginRegistrar } from './plugin-def';
import { RegisterMeta } from './utility/register-meta';
import { uniq } from './utility/utility';
import MixinOptions = Context.MixinOptions;
declare module 'cordis' {
interface Context {
......@@ -23,6 +24,8 @@ declare module 'cordis' {
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Registrar {
import MixinOptions = Context.MixinOptions;
export interface MetadataArrayMap {
CordisRegisterKeys: string;
CordisContextTransformer: RegisterMeta<ContextTransformer<Context>>;
......@@ -34,6 +37,7 @@ export namespace Registrar {
CordisPluginInjectKeys: string;
CordisPluginSystemKeys: string;
CordisConfigAcceptors: (ctx: Context) => any;
CordisMixinKeys: string;
}
export interface MetadataMap {
CordisRegister: MethodMeta<Context>;
......@@ -44,6 +48,7 @@ export namespace Registrar {
CordisPluginFork: PluginRegistrar.PluginClass<Context>;
CordisPluginReusable: boolean;
CordisPluginReactive: boolean;
CordisMixin: keyof MixinOptions;
}
export type DecorateFunctionParam<
......@@ -288,12 +293,15 @@ export class Registrar<Ctx extends Context> {
enumerable: true,
configurable: true,
get: () => {
return this.__ctx[name];
const val = this.__ctx[name];
if (typeof val === 'function') {
return (...args: any[]) => this.__ctx[name](...args);
} else {
return val;
}
},
set: (val: any) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.__ctx[name] = val;
// do nothing
},
});
}
......@@ -537,16 +545,34 @@ export class Registrar<Ctx extends Context> {
name: string,
options?: Registrar.ProvideOptions,
): ClassDecorator => {
if (options?.internal) {
return (cls) => {
Context.service(name, cls);
return (cls) => {
const mixinSet: { [K in keyof MixinOptions]: Set<string> } = {
methods: new Set(),
properties: new Set(),
};
}
Context.service(name);
return this.metadata.append('CordisPluginProvide', {
...options,
serviceName: name,
});
const mixinKeys = this.reflector.getArray('CordisMixinKeys', cls);
for (const key of mixinKeys) {
const mixinType = this.reflector.get('CordisMixin', cls, key);
mixinSet[mixinType].add(key);
}
const mixin: MixinOptions = {};
for (const mixinType of ['methods', 'properties'] as const) {
const set = mixinSet[mixinType];
if (set.size) {
mixin[mixinType] = [...set];
}
}
if (options?.internal) {
Object.assign(cls, mixin);
Context.service(name, cls);
return;
}
Context.service(name, mixin);
this.metadata.append('CordisPluginProvide', {
...options,
serviceName: name,
})(cls);
};
},
Inject: (...args: [(string | boolean)?, boolean?]): PropertyDecorator => {
let name: string;
......
import { Inject, Internal, Provide } from '../src/decorators';
import { Internal, Provide } from '../src/decorators';
import { DefinePlugin, StarterPlugin } from './utility/decorators';
import { LifecycleEvents } from '../src/plugin-def';
import { Context } from 'cordis';
declare module 'cordis' {
......@@ -23,7 +22,7 @@ class MyInternalService extends StarterPlugin() {
}
}
describe('Apply and Connect in koishi-thirdeye', () => {
describe('Internal service', () => {
let app: Context;
beforeEach(() => {
app = new Context();
......
import { Inject, Internal, Provide } from '../src/decorators';
import { DefinePlugin, StarterPlugin } from './utility/decorators';
import { Context } from 'cordis';
declare module 'cordis' {
// eslint-disable-next-line @typescript-eslint/no-namespace
interface Context {
foo: string;
bar(): number;
bar2(): number;
bazz: MyProvider;
myBarConsumer: MyConsumer;
}
}
@Provide('bazz', { immediate: true })
@DefinePlugin()
class MyProvider extends StarterPlugin() {
barValue = 5;
@Internal()
foo: string;
@Internal()
bar() {
return this.barValue;
}
}
@Provide('myBarConsumer', { immediate: true })
@DefinePlugin()
class MyConsumer extends StarterPlugin() {
@Inject()
bar: Context['bar'];
}
describe('Mixin', () => {
let app: Context;
beforeEach(() => {
app = new Context();
app.plugin(MyProvider);
app.plugin(MyConsumer);
});
it('should load provide mixin', () => {
app.foo = 'msg';
expect(app.foo).toBe('msg');
expect(app.bar()).toBe(5);
expect(app.bazz.foo).toBe('msg');
expect(app.bazz.bar()).toBe(5);
expect(app.myBarConsumer.bar()).toBe(5);
});
});
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