feat(lib/plugins): do API decorator filtering

This commit is contained in:
PalmDevs 2025-07-21 14:02:00 +07:00
parent 72035286a9
commit 84bb9992f5
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
2 changed files with 75 additions and 25 deletions

View file

@ -1,36 +1,77 @@
import { getPluginDependencies, pMetadata } from '.'
import type {
InitPluginApi,
Plugin,
PluginApi,
PreInitPluginApi,
} from '../types'
import type { InternalPluginMeta } from '.'
import type { AnyPlugin, InternalPluginMeta } from '.'
export type PluginApiDecoratorStore<
A extends PreInitPluginApi | InitPluginApi | PluginApi,
> = [decorator: Parameters<A['decorate']>[0], handleError: (e: unknown) => void]
> = WeakMap<AnyPlugin, Parameters<A['decorate']>[0][]>
export const pDecorators: {
preInit: PluginApiDecoratorStore<PreInitPluginApi>[]
init: PluginApiDecoratorStore<InitPluginApi>[]
start: PluginApiDecoratorStore<PluginApi>[]
} = {
preInit: [],
init: [],
start: [],
// Set of plugins that will always decorate the API of every other plugin.
export const pImplicitDeps = new Set<AnyPlugin>()
export const pDecoratorsPreInit: PluginApiDecoratorStore<PreInitPluginApi> =
new WeakMap()
export const pDecoratorsInit: PluginApiDecoratorStore<InitPluginApi> =
new WeakMap()
export const pDecoratorsStart: PluginApiDecoratorStore<PluginApi> =
new WeakMap()
export function addPluginApiDecorator(
store: PluginApiDecoratorStore<any>,
plugin: AnyPlugin,
decorator: Parameters<NonNullable<AnyPlugin['api']>['decorate']>[0],
) {
let list = store.get(plugin)
if (!list) {
list = []
store.set(plugin, list)
}
list.push(decorator)
}
export function decoratePluginApi(
store: PluginApiDecoratorStore<
PreInitPluginApi | InitPluginApi | PluginApi
>[],
plugin: Plugin,
>,
plugin: Plugin<any, any>,
meta: InternalPluginMeta,
) {
for (const [decorator, handleError] of store)
try {
decorator(plugin as Plugin<any, any>, meta.options)
} catch (e) {
handleError(e)
// Don't decorate implicit dependencies
if (pImplicitDeps.has(plugin)) return
// Decorate the plugin API with implicit dependencies
// Implicit dependencies are internal plugin APIs, so we can run this without try-catch
// If anything fails, everything else would fail anyway
for (const dep of pImplicitDeps) {
const decorators = store.get(dep)
if (decorators)
for (const decorator of decorators) decorator(plugin, meta.options)
}
const deps = getPluginDependencies(plugin)
const { handleError: handleDependentError } = meta
for (const dep of deps) {
const decorators = store.get(dep)
if (decorators) {
const { handleError } = pMetadata.get(dep)!
try {
for (const decorator of decorators)
decorator(plugin, meta.options)
} catch (e) {
handleError(e)
handleDependentError(e)
}
}
}
}

View file

@ -7,7 +7,14 @@ import {
PluginFlags,
PluginStatus as Status,
} from '../constants'
import { decoratePluginApi, pDecorators } from './decorators'
import {
addPluginApiDecorator,
decoratePluginApi,
pDecoratorsInit,
pDecoratorsPreInit,
pDecoratorsStart,
pImplicitDeps,
} from './decorators'
import { pLeafOrSingleNodes, pPending } from './dependency-graph'
import type {
InitPluginApi,
@ -88,8 +95,10 @@ export function registerPlugin<O extends PluginApiExtensionsOptions>(
pMetadata.set(plugin, meta)
pList.set(manifest.id, plugin)
if (iflags & InternalPluginFlags.ImplicitDependency)
if (iflags & InternalPluginFlags.ImplicitDependency) {
pLeafOrSingleNodes.add(plugin)
pImplicitDeps.add(plugin)
}
// Only add to pending if the plugin is enabled
else if (flags & PluginFlags.Enabled) pPending.add(plugin)
@ -159,11 +168,11 @@ function preparePluginPreInit(plugin: AnyPlugin) {
plugin,
unscoped: pUnscopedApi,
decorate: decorator => {
pDecorators.preInit.push([decorator, meta.handleError])
addPluginApiDecorator(pDecoratorsPreInit, plugin, decorator)
},
} satisfies PreInitPluginApi
decoratePluginApi(pDecorators.preInit, plugin, meta)
decoratePluginApi(pDecoratorsPreInit, plugin, meta)
meta.apiLevel = PluginApiLevel.PreInit
}
@ -172,10 +181,10 @@ function preparePluginInit(plugin: AnyPlugin) {
const meta = pMetadata.get(plugin)!
const api = plugin.api as InitPluginApi
api.decorate = decorator => {
pDecorators.init.push([decorator, meta.handleError])
addPluginApiDecorator(pDecoratorsInit, plugin, decorator)
}
decoratePluginApi(pDecorators.init, plugin, meta)
decoratePluginApi(pDecoratorsInit, plugin, meta)
meta.apiLevel = PluginApiLevel.Init
}
@ -184,10 +193,10 @@ function preparePluginStart(plugin: AnyPlugin) {
const meta = pMetadata.get(plugin)!
const api = plugin.api as PluginApi
api.decorate = decorator => {
pDecorators.start.push([decorator, meta.handleError])
addPluginApiDecorator(pDecoratorsStart, plugin, decorator)
}
decoratePluginApi(pDecorators.start, plugin, meta)
decoratePluginApi(pDecoratorsStart, plugin, meta)
meta.apiLevel = PluginApiLevel.Start
}