diff --git a/components/controller.ts b/components/controller.ts index 2224f12b1..654cb6ce0 100644 --- a/components/controller.ts +++ b/components/controller.ts @@ -380,6 +380,9 @@ export class Controller { public locationsWithETA = new Set() public parentsWithETA = new Set() + // A place for plugins to share data. + public blackboard: unknown = {} + /** * The constructor. */ diff --git a/components/hooksImpl.ts b/components/hooksImpl.ts index e8fd7cece..4b965ae2d 100644 --- a/components/hooksImpl.ts +++ b/components/hooksImpl.ts @@ -56,7 +56,15 @@ export class HookMap { /** * The options for a hook. Will either be just the name (as a string), or an object containing the additional options. */ -export type TapOptions = string | { name: string; context: boolean } +export type TapOptions = + | string + | { + name: string + // Whether the context object (which can be modified) should be used. Defaults to false. + enableContext?: boolean + // Priority of the tap, higher goes first. Defaults to 0. + priority?: number + } // eslint-disable-next-line @typescript-eslint/no-explicit-any type AsArray = T extends any[] ? T : [T] @@ -68,12 +76,14 @@ interface Tap { name: string func: (...args: AsArray) => R enableContext: boolean + priority: number } /** * The structure of an intercept. * * @see name + * @see priority * @see call * @see tap */ @@ -83,6 +93,11 @@ export interface Intercept { */ name: string + /** + * The priority of the intercept, higher goes first. + */ + priority: number + /** * A function called just after the hook is called, and before all taps run. * @@ -120,6 +135,12 @@ export abstract class BaseImpl { */ public intercept(intercept: Intercept): void { this._intercepts.push(intercept) + + this._intercepts.sort((a, b) => { + if (a.priority !== b.priority) return b.priority - a.priority + + return a.name.localeCompare(b.name) + }) } /** @@ -138,7 +159,11 @@ export abstract class BaseImpl { ? nameOrOptions : nameOrOptions.name const enableContext = - typeof nameOrOptions === "string" ? false : nameOrOptions.context + typeof nameOrOptions === "string" + ? false + : nameOrOptions.enableContext ?? false + const priority = + typeof nameOrOptions === "string" ? 0 : nameOrOptions.priority ?? 0 for (const intercept of this._intercepts) { if (intercept.tap) { @@ -150,6 +175,13 @@ export abstract class BaseImpl { name, func: consumer, enableContext, + priority, + }) + + this._taps.sort((a, b) => { + if (a.priority !== b.priority) return b.priority - a.priority + + return a.name.localeCompare(b.name) }) }