diff --git a/packages/core/src/context.ts b/packages/core/src/context.ts index ca5f24e..c305d18 100644 --- a/packages/core/src/context.ts +++ b/packages/core/src/context.ts @@ -56,7 +56,7 @@ export interface Context { export class Context { static readonly invoke = Symbol.for('cordis.invoke') - static readonly config = Symbol.for('cordis.config') + static readonly extend = Symbol.for('cordis.extend') static readonly events = Symbol.for('cordis.events') static readonly static = Symbol.for('cordis.static') static readonly filter = Symbol.for('cordis.filter') @@ -66,20 +66,20 @@ export class Context { static readonly internal = Symbol.for('cordis.internal') static readonly intercept = Symbol.for('cordis.intercept') - static createProxy(ctx: any, value: any) { + static createTraceable(ctx: any, value: any) { const proxy = new Proxy(value, { get: (target, name, receiver) => { if (name === Context.current || name === 'caller') return ctx return Reflect.get(target, name, receiver) }, apply: (target, thisArg, args) => { - return Context.applyProxy(proxy, target, thisArg, args) + return Context.applyTraceable(proxy, target, thisArg, args) }, }) return proxy } - static applyProxy(proxy: any, value: any, thisArg: any, args: any[]) { + static applyTraceable(proxy: any, value: any, thisArg: any, args: any[]) { if (!value[Context.invoke]) return Reflect.apply(value, thisArg, args) return value[Context.invoke].apply(proxy, args) } @@ -274,7 +274,7 @@ export class Context { defineProperty(value, Context.current, this) return value } - return Context.createProxy(this, value) + return Context.createTraceable(this, value) } provide(name: string, value?: any, builtin?: boolean) { diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 7d3d48d..779912a 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -332,7 +332,7 @@ export class MainScope extends EffectScope { super(registry[Context.current] as C, config) registry.set(plugin, this) if (!plugin) { - this.name = 'app' + this.name = 'root' this.isActive = true } else { this.setup() diff --git a/packages/core/src/service.ts b/packages/core/src/service.ts index b6cab98..170e634 100644 --- a/packages/core/src/service.ts +++ b/packages/core/src/service.ts @@ -5,20 +5,28 @@ export namespace Service { export interface Options { name?: string immediate?: boolean - standalone?: boolean } } -function makeFunctional(proto: {}) { +function makeCallableProto(proto: {}) { if (proto === Object.prototype) return Function.prototype - const result = Object.create(makeFunctional(Object.getPrototypeOf(proto))) + const result = Object.create(makeCallableProto(Object.getPrototypeOf(proto))) for (const key of Reflect.ownKeys(proto)) { Object.defineProperty(result, key, Object.getOwnPropertyDescriptor(proto, key)!) } return result } -export abstract class Service { +function makeCallable(ctx: Context, name: string, proto: {}) { + const self = function (...args: any[]) { + const proxy = Context.createTraceable(ctx, self) + return Context.applyTraceable(proxy, self, this, args) + } + defineProperty(self, 'name', name) + return Object.setPrototypeOf(self, proto) +} + +export abstract class Service { static immediate = false static Context = Context @@ -29,24 +37,20 @@ export abstract class Service { protected ctx!: C protected [Context.current]!: C - constructor(ctx: C | undefined, public readonly name: string, options?: boolean | Service.Options) { + constructor(ctx: C | undefined, public readonly name: string, options?: boolean) { let self = this if (self[Context.invoke]) { - // functional service - self = function (...args: any[]) { - const proxy = Context.createProxy(ctx, self) - return Context.applyProxy(proxy, self, this, args) - } as any - defineProperty(self, 'name', name) - Object.setPrototypeOf(self, makeFunctional(Object.getPrototypeOf(this))) + // FIXME ctx! + self = makeCallable(ctx!, name, makeCallableProto(Object.getPrototypeOf(this))) } self.ctx = ctx ?? new (self.constructor as any).Context() - self.ctx.provide(name) defineProperty(self, Context.current, ctx) const resolved = typeof options === 'boolean' ? { immediate: options } : options ?? {} - if (!resolved.standalone && resolved.immediate) { + if (resolved.immediate) { + self.ctx.provide(name) + self.ctx.runtime.name = name if (ctx) self[Context.expose] = name else self.ctx[name] = self } @@ -55,7 +59,7 @@ export abstract class Service { // await until next tick because derived class has not been initialized yet await Promise.resolve() await self.start() - if (!resolved.standalone && !resolved.immediate) self.ctx[name] = self + if (!resolved.immediate) self.ctx[name] = self }) self.ctx.on('dispose', () => self.stop()) @@ -66,6 +70,18 @@ export abstract class Service { return ctx[Context.shadow][this.name] === this.ctx[Context.shadow][this.name] } + [Context.extend](props?: any) { + const caller = this[Context.current] + let self: typeof this + if (this[Context.invoke]) { + self = makeCallable(caller, this.name, this) + } else { + self = Object.create(this) + } + defineProperty(self, Context.current, caller) + return Context.associate(Object.assign(self, props), this.name) + } + static [Symbol.hasInstance](instance: any) { let constructor = instance.constructor while (constructor) { diff --git a/packages/core/tests/service.spec.ts b/packages/core/tests/service.spec.ts index e41dad2..82a18a8 100644 --- a/packages/core/tests/service.spec.ts +++ b/packages/core/tests/service.spec.ts @@ -282,8 +282,8 @@ describe('Service', () => { } class Foo extends Service { - constructor(ctx: Context, public config?: Config, standalone?: boolean) { - super(ctx, 'foo', { immediate: true, standalone }) + constructor(ctx: Context, public config?: Config) { + super(ctx, 'foo', true) } [Context.invoke](init?: Config) { @@ -304,7 +304,9 @@ describe('Service', () => { } extend(config?: Config) { - return new Foo(this[Context.current], { ...this.config, ...config }, true) + return this[Context.extend]({ + config: { ...this.config, ...config }, + }) } }