diff --git a/packages/core/src/registry.ts b/packages/core/src/registry.ts index 9c03ad6..3fc91ea 100644 --- a/packages/core/src/registry.ts +++ b/packages/core/src/registry.ts @@ -1,4 +1,4 @@ -import { defineProperty, Dict } from 'cosmokit' +import { Awaitable, defineProperty, Dict } from 'cosmokit' import { Context } from './context.ts' import { ForkScope, MainScope } from './scope.ts' import { resolveConfig, symbols } from './utils.ts' @@ -51,7 +51,7 @@ export namespace Plugin { } export interface Function extends Base { - (ctx: C, config: T): void + (ctx: C, config: T): Awaitable } export interface Constructor extends Base { @@ -59,7 +59,7 @@ export namespace Plugin { } export interface Object extends Base { - apply: (ctx: C, config: T) => void + apply: (ctx: C, config: T) => Awaitable } } @@ -91,7 +91,7 @@ class Registry { }) this.context = ctx - const runtime = new MainScope(ctx, null!, config) + const runtime = new MainScope(ctx, null!, config, null, new Error().stack!.split('\n').slice(1)) ctx.scope = runtime runtime.ctx = ctx this.set(null!, runtime) @@ -176,18 +176,20 @@ class Registry { config = null } + const stack = new Error().stack!.split('\n').slice(2) + // check duplication let runtime = this.get(plugin) if (runtime) { if (!runtime.isForkable) { this.context.emit(this.ctx, 'internal/warning', new Error(`duplicate plugin detected: ${plugin.name}`)) } - return runtime.fork(this.ctx, config, error) + return runtime.fork(this.ctx, config, error, stack) } - runtime = new MainScope(this.ctx, plugin, config, error) + runtime = new MainScope(this.ctx, plugin, config, error, stack) this.set(plugin, runtime) - return runtime.fork(this.ctx, config, error) + return runtime.fork(this.ctx, config, error, stack) } } diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index f0b5ad0..a64d5af 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -74,7 +74,7 @@ export abstract class EffectScope { abstract dispose(): boolean abstract update(config: C['config'], forced?: boolean): void - constructor(public parent: C, public config: C['config']) { + constructor(public parent: C, public config: C['config'], public stack: string[]) { this.uid = parent.registry ? parent.registry.counter : 0 this.ctx = this.context = parent.extend({ scope: this }) this.proxy = new Proxy({}, { @@ -256,8 +256,8 @@ export abstract class EffectScope { export class ForkScope extends EffectScope { dispose: () => boolean - constructor(parent: Context, public runtime: MainScope, config: C['config'], error?: any) { - super(parent as C, config) + constructor(parent: Context, public runtime: MainScope, config: C['config'], error: any, stack: string[]) { + super(parent as C, config, stack) this.dispose = defineProperty(parent.scope.collect(`fork <${parent.runtime.name}>`, () => { this.uid = null @@ -300,8 +300,6 @@ export class ForkScope extends EffectScope { } export class MainScope extends EffectScope { - public value: any - runtime = this schema: any name?: string @@ -311,8 +309,8 @@ export class MainScope extends EffectScope { isReusable?: boolean = false isReactive?: boolean = false - constructor(ctx: C, public plugin: Plugin, config: any, error?: any) { - super(ctx, config) + constructor(ctx: C, public plugin: Plugin, config: any, error: any, stack: string[]) { + super(ctx, config, stack) if (!plugin) { this.name = 'root' this.isActive = true @@ -326,8 +324,8 @@ export class MainScope extends EffectScope { return this.forkables.length > 0 } - fork(parent: Context, config: any, error?: any) { - return new ForkScope(parent, this, config, error) + fork(parent: Context, config: any, error: any, stack: string[]) { + return new ForkScope(parent, this, config, error, stack) } dispose() { @@ -351,22 +349,41 @@ export class MainScope extends EffectScope { } } - private apply = (context: C, config: any) => { - if (typeof this.plugin !== 'function') { - return this.plugin.apply(context, config) - } else if (isConstructor(this.plugin)) { - // eslint-disable-next-line new-cap - const instance = new this.plugin(context, config) - const name = instance[Context.expose] - if (name) { - context.set(name, instance) + private apply = async (ctx: C, config: any) => { + const initialLine = new Error().stack!.split('\n')[2] + try { + if (typeof this.plugin !== 'function') { + return await this.plugin.apply(ctx, config) + } else if (isConstructor(this.plugin)) { + // eslint-disable-next-line new-cap + const instance = new this.plugin(ctx, config) + const name = instance[Context.expose] + if (name) { + ctx.set(name, instance) + } + if (instance['fork']) { + this.forkables.push(instance['fork'].bind(instance)) + } + return instance + } else { + return await this.plugin(ctx, config) } - if (instance['fork']) { - this.forkables.push(instance['fork'].bind(instance)) + } catch (reason) { + // 'full' | 'short' | 'none' (default: 'short') + const { traceInternal } = ctx.root.config + if (reason instanceof Error && traceInternal !== 'full') { + const lines = reason.stack!.split('\n') + const index = lines.indexOf(initialLine) + if (index > 0) { + lines.splice(index - 1, Infinity) + if (traceInternal !== 'none') { + lines.push(' at MainScope.apply (cordis:internal)') + } + lines.push(...this.stack) + reason.stack = lines.join('\n') + } } - return instance - } else { - return this.plugin(context, config) + throw reason } } @@ -380,7 +397,7 @@ export class MainScope extends EffectScope { start() { if (super.start()) return true if (!this.isReusable && this.plugin) { - this.ensure(async () => this.value = this.apply(this.ctx, this._config)) + this.ensure(async () => this.apply(this.ctx, this._config)) } for (const fork of this.children) { fork.start()