refactor(loader): resolve config after injected services

This commit is contained in:
Turtle
2026-08-07 17:27:38 +08:00
parent b692f38506
commit 7e3a82eacc
38 changed files with 404 additions and 306 deletions

View File

@@ -331,6 +331,12 @@ export interface Events {
'internal/plugin'(fiber: Fiber): void
/** A fiber changed lifecycle state; receives the fiber and its previous state. */
'internal/status'(fiber: Fiber, oldValue: FiberState): void
/**
* Resolve raw plugin config after the fiber's injections become active.
* @param config - the raw config for this activation.
* @mode waterfall
*/
'internal/config'(this: Fiber, config: any, next: () => any): any
/** Interception hook for a service binding (no core producer). */
'internal/service'(this: Context, name: string, value: any): void
/** Waterfall: a fiber config update is being applied; skip `next()` to veto. */

View File

@@ -188,6 +188,8 @@ export class Fiber {
public readonly ctx: Context
/** The validated plugin config (updated by `update()`). */
public config: any
/** The raw plugin config, re-resolved before each activation. */
public _config: any
/** Current lifecycle state; transitions emit `internal/status`. */
public state = FiberState.PENDING
/** Dispose this fiber: unload the plugin, then settle once cleanup finished. */
@@ -224,6 +226,7 @@ export class Fiber {
public runtime: Plugin.Runtime | null,
getOuterStack: () => string[],
) {
this._config = config
const collect = (dispose: Disposable) => {
this._disposables.push(dispose)
}
@@ -259,16 +262,8 @@ export class Fiber {
collect,
}
let shouldRefresh = false
this.dispose = parent.fiber.effect(() => {
const remove = runtime.fibers.push(this)
try {
this.config = resolveConfig(runtime, config)
shouldRefresh = true
} catch (error) {
this.ctx.logger.error(error)
this._error = error
}
return async () => {
this.uid = null
emitPluginDisposed(this.context, this)
@@ -320,7 +315,7 @@ export class Fiber {
for (const name of Object.keys(this.inject)) {
this._checkImpl(name)
}
if (shouldRefresh) this._refresh()
this._refresh()
}
} else {
this.uid = 0
@@ -643,6 +638,11 @@ export class Fiber {
})
}
private _resolveConfig(config: any) {
config = this.context.waterfall(this, 'internal/config', config, () => config)
return this.runtime ? resolveConfig(this.runtime, config) : config
}
private async _reload() {
this.store = { ...this._store }
const oldEpoch = this._runner.epoch
@@ -652,7 +652,9 @@ export class Fiber {
// the load. Do not run plugin code for a stale epoch; the state update
// below will drain any effects collected while the fiber was PENDING.
if (this._runner.epoch === oldEpoch) {
this.config = this._resolveConfig(this._config)
await this._execute(this._runner)
this._error = undefined
}
} catch (reason) {
// impl guarantees that the error is non-null (?)
@@ -733,7 +735,16 @@ export class Fiber {
*/
update(config: any, noSave = false) {
this.assertActive()
config = resolveConfig(this.runtime!, config)
this._config = config
if (this.state !== FiberState.ACTIVE) {
// Config resolution may access injected services, so defer it until the
// fiber can activate.
this._error = undefined
this._setEpoch(INACTIVE)
this._refresh()
return
}
config = this._resolveConfig(config)
return this.context.waterfall(this, 'internal/update', config, noSave, () => {
this.config = config
this._error = undefined