From e06b3f35e7bca7d29cb9a33eda3b07384fcc88e2 Mon Sep 17 00:00:00 2001 From: Shigma Date: Fri, 7 Jun 2024 15:25:50 +0800 Subject: [PATCH] feat(cordis): support Serivce.tracker for tracibility --- packages/core/src/context.ts | 11 +++---- packages/core/src/events.ts | 5 ++- packages/core/src/registry.ts | 6 +++- packages/core/src/service.ts | 16 +++++---- packages/core/src/utils.ts | 47 ++++++++++++++------------- packages/core/tests/associate.spec.ts | 8 +++-- packages/core/tests/utils.ts | 7 +++- packages/loader/src/isolate.ts | 2 +- 8 files changed, 61 insertions(+), 41 deletions(-) diff --git a/packages/core/src/context.ts b/packages/core/src/context.ts index eb7b73d..2a03a26 100644 --- a/packages/core/src/context.ts +++ b/packages/core/src/context.ts @@ -1,7 +1,7 @@ import { defineProperty, Dict, isNullable } from 'cosmokit' import { Lifecycle } from './events.ts' import { Registry } from './registry.ts' -import { createTraceable, getTraceable, isObject, isUnproxyable, resolveConfig, symbols } from './utils.ts' +import { getTraceable, isObject, isUnproxyable, resolveConfig, symbols } from './utils.ts' export namespace Context { export type Parameterized = C & { config: T } @@ -202,8 +202,7 @@ export class Context { const internal = this[symbols.internal][name] if (internal?.type !== 'service') return const value = this.root[this[symbols.isolate][name]] - if (!isObject(value) || isUnproxyable(value)) return value - return createTraceable(this, value, name) + return getTraceable(this, value) } set(name: K, value: undefined | this[K]): () => void @@ -238,9 +237,9 @@ export class Context { ctx.emit(self, 'internal/before-service', name, value) ctx.root[key] = value - // if (value instanceof Object) { - // defineProperty(value, symbols.origin, ctx) - // } + if (isObject(value)) { + defineProperty(value, symbols.source, ctx) + } ctx.emit(self, 'internal/service', name, oldValue) return dispose } diff --git a/packages/core/src/events.ts b/packages/core/src/events.ts index d06f4b0..0782f0f 100644 --- a/packages/core/src/events.ts +++ b/packages/core/src/events.ts @@ -46,7 +46,10 @@ export class Lifecycle { _hooks: Record = {} constructor(private ctx: Context) { - defineProperty(this, symbols.trace, 'lifecycle') + defineProperty(this, symbols.tracker, { + associate: 'lifecycle', + property: 'ctx', + }) defineProperty(this.on('internal/listener', function (this: Context, name, listener, options: EventOptions) { const method = options.prepend ? 'unshift' : 'push' diff --git a/packages/core/src/registry.ts b/packages/core/src/registry.ts index 82a21d6..fe0f5b0 100644 --- a/packages/core/src/registry.ts +++ b/packages/core/src/registry.ts @@ -67,7 +67,11 @@ export class Registry { protected context: Context constructor(public ctx: C, config: any) { - defineProperty(this, symbols.trace, 'registry') + defineProperty(this, symbols.tracker, { + associate: 'registry', + property: 'ctx', + }) + this.context = ctx const runtime = new MainScope(ctx, null!, config) ctx.scope = runtime diff --git a/packages/core/src/service.ts b/packages/core/src/service.ts index 21e49e6..5b82b37 100644 --- a/packages/core/src/service.ts +++ b/packages/core/src/service.ts @@ -1,15 +1,15 @@ import { Awaitable, defineProperty } from 'cosmokit' import { Context } from './context.ts' -import { createCallable, joinPrototype, symbols } from './utils.ts' +import { createCallable, joinPrototype, symbols, Tracker } from './utils.ts' import { Spread } from './registry.ts' export abstract class Service { static readonly setup: unique symbol = symbols.setup as any static readonly invoke: unique symbol = symbols.invoke as any static readonly extend: unique symbol = symbols.extend as any + static readonly tracker: unique symbol = symbols.tracker as any static readonly provide: unique symbol = symbols.provide as any static readonly immediate: unique symbol = symbols.immediate as any - static readonly trace: unique symbol = symbols.trace as any protected start(): Awaitable {} protected stop(): Awaitable {} @@ -40,8 +40,12 @@ export abstract class Service { immediate ??= this.constructor[symbols.immediate] let self = this + const tracker: Tracker = { + associate: name, + property: 'ctx', + } if (self[symbols.invoke]) { - self = createCallable(name, joinPrototype(Object.getPrototypeOf(this), Function.prototype)) + self = createCallable(name, joinPrototype(Object.getPrototypeOf(this), Function.prototype), tracker) } if (_ctx) { self.ctx = _ctx @@ -50,7 +54,7 @@ export abstract class Service { } self.name = name self.config = config - defineProperty(self, symbols.trace, name) + defineProperty(self, symbols.tracker, tracker) self.ctx.provide(name) self.ctx.runtime.name = name @@ -81,12 +85,10 @@ export abstract class Service { protected [symbols.extend](props?: any) { let self: any if (this[Service.invoke]) { - self = createCallable(this.name, this) + self = createCallable(this.name, this, this[symbols.tracker]) } else { self = Object.create(this) } - defineProperty(self, symbols.trace, this.name) - // defineProperty(self, symbols.origin, this.ctx) return Object.assign(self, props) } diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index bbcb7fd..fba4cfc 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -1,6 +1,11 @@ import { defineProperty } from 'cosmokit' import type { Context, Service } from './index.ts' +export interface Tracker { + associate?: string + property?: string +} + export const symbols = { // context symbols source: Symbol.for('cordis.source') as typeof Context.source, @@ -13,10 +18,10 @@ export const symbols = { intercept: Symbol.for('cordis.intercept') as typeof Context.intercept, // service symbols - trace: Symbol.for('cordis.traceable') as typeof Service.trace, setup: Symbol.for('cordis.setup') as typeof Service.setup, invoke: Symbol.for('cordis.invoke') as typeof Service.invoke, extend: Symbol.for('cordis.extend') as typeof Service.extend, + tracker: Symbol.for('cordis.tracker') as typeof Service.tracker, provide: Symbol.for('cordis.provide') as typeof Service.provide, immediate: Symbol.for('cordis.immediate') as typeof Service.immediate, } @@ -59,44 +64,42 @@ export function isObject(value: any): value is {} { return value && (typeof value === 'object' || typeof value === 'function') } +function isTraceable(value: any): value is {} { + return isObject(value) && !isUnproxyable(value) && symbols.tracker in value +} + export function getTraceable(ctx: any, value: any) { - if (isObject(value) && symbols.trace in value) { - return createTraceable(ctx, value, value[symbols.trace] as any) + if (isTraceable(value)) { + return createTraceable(ctx, value, value[symbols.tracker]) } else { return value } } -export function createTraceable(ctx: any, value: any, name: string) { +function createTraceable(ctx: any, value: any, tracer: Tracker) { const proxy = new Proxy(value, { get: (target, prop, receiver) => { - if (prop === 'ctx') { - const origin = Reflect.getOwnPropertyDescriptor(target, 'ctx')?.value - return ctx.extend({ [symbols.source]: origin }) - } - if (typeof prop === 'symbol') { return Reflect.get(target, prop, receiver) } - - if (!ctx[symbols.internal][`${name}.${prop}`]) { + if (prop === tracer.property) { + const origin = Reflect.getOwnPropertyDescriptor(target, tracer.property)?.value + return ctx.extend({ [symbols.source]: origin }) + } + if (!tracer.associate || !ctx[symbols.internal][`${tracer.associate}.${prop}`]) { return getTraceable(ctx, Reflect.get(target, prop, receiver)) } - - return ctx[`${name}.${prop}`] + return ctx[`${tracer.associate}.${prop}`] }, set: (target, prop, value, receiver) => { - if (prop === 'ctx') return false - + if (prop === tracer.property) return false if (typeof prop === 'symbol') { return Reflect.set(target, prop, value, receiver) } - - if (!ctx[symbols.internal][`${name}.${prop}`]) { + if (!tracer.associate || !ctx[symbols.internal][`${tracer.associate}.${prop}`]) { return Reflect.set(target, prop, value, receiver) } - - ctx[`${name}.${prop}`] = value + ctx[`${tracer.associate}.${prop}`] = value return true }, apply: (target, thisArg, args) => { @@ -106,14 +109,14 @@ export function createTraceable(ctx: any, value: any, name: string) { return proxy } -export function applyTraceable(proxy: any, value: any, thisArg: any, args: any[]) { +function applyTraceable(proxy: any, value: any, thisArg: any, args: any[]) { if (!value[symbols.invoke]) return Reflect.apply(value, thisArg, args) return value[symbols.invoke].apply(proxy, args) } -export function createCallable(name: string, proto: {}) { +export function createCallable(name: string, proto: {}, tracker: Tracker) { const self = function (...args: any[]) { - const proxy = createTraceable(self['ctx'], self, name) + const proxy = createTraceable(self['ctx'], self, tracker) return applyTraceable(proxy, self, this, args) } defineProperty(self, 'name', name) diff --git a/packages/core/tests/associate.spec.ts b/packages/core/tests/associate.spec.ts index 47e75b3..3b31ef0 100644 --- a/packages/core/tests/associate.spec.ts +++ b/packages/core/tests/associate.spec.ts @@ -56,8 +56,12 @@ describe('Association', () => { it('associated type', async () => { class Session { - [Service.trace] = 'session' - constructor(ctx: Context) {} + [Service.tracker] = { + property: 'ctx', + associate: 'session', + } + + constructor(private ctx: Context) {} } class Foo extends Service { diff --git a/packages/core/tests/utils.ts b/packages/core/tests/utils.ts index 9ef2a88..98c1b2c 100644 --- a/packages/core/tests/utils.ts +++ b/packages/core/tests/utils.ts @@ -1,5 +1,5 @@ import { use } from 'chai' -import { Context } from '../src' +import { Context, Service } from '../src' import promised from 'chai-as-promised' import { Dict } from 'cosmokit' @@ -44,6 +44,11 @@ declare module '../src/events' { } export class Counter { + [Service.tracker] = { + associate: 'counter', + property: 'ctx', + } + value = 0 constructor(public ctx: Context) {} diff --git a/packages/loader/src/isolate.ts b/packages/loader/src/isolate.ts index dbeeaa4..02e14ac 100644 --- a/packages/loader/src/isolate.ts +++ b/packages/loader/src/isolate.ts @@ -112,7 +112,7 @@ export function apply(ctx: Context) { for (const symbol of [oldMap[key], this.newMap[key]]) { const value = symbol && entry.ctx[symbol] if (!(value instanceof Object)) continue - const source = Reflect.getOwnPropertyDescriptor(value, Context.origin)?.value + const source = Reflect.getOwnPropertyDescriptor(value, Context.source)?.value if (!source) { entry.ctx.emit('internal/warning', new Error(`expected service ${key} to be implemented`)) continue