Without before/after hook pairs, plugins are limited to one-sided interception. A caching plugin cannot check a cache before a request and store the result after it completes — it can only do one or the other. A metrics plugin cannot measure operation duration. A validation plugin cannot both validate input before a write and sanitize output after. This forces plugin authors to duplicate host internals or wire two separate plugins together, increasing coupling and the surface area for bugs. ISO 25010 maintainability.modularity requires that cross-cutting concerns be implementable without modifying core code.
Medium because the absence of before/after hook pairs prevents plugins from implementing caching, metrics, and two-phase transformations, limiting the plugin system to superficially useful interception only.
Add a paired post-hook for every mutation hook that only has a pre-hook. For patterns that need to share context between pre and post phases, consider a middleware wrapper with a next() call.
// Paired hooks:
await invokeHook('before:save', { entity, context });
const result = await save(entity);
await invokeHook('after:save', { entity, result, context });
// Or wrapper pattern:
async function saveWithHooks(entity: Entity) {
return runMiddleware(entity, savePipeline, async (entity) => {
return await save(entity);
});
}
ID: plugin-extension-architecture.hook-system.pre-post-hooks
Severity: medium
What to look for: Check if the hook system supports before/after (pre/post) patterns for intercepting operations. This means plugins can run code both before AND after a host operation, enabling use cases like request logging (before + after), caching (check cache before, store result after), validation (validate before, transform after), and performance measurement (start timer before, record duration after). Look for:
before:save / after:save, onPreHandler / onPostHandler, pre_save / post_savenext() function to call the original operationSyncWaterfallHook or AsyncSeriesWaterfallHook patternsnext() functionPass criteria: Count all hook points that support pre/post variants. The hook system provides before/after patterns for at least the core operations. Plugins can intercept operations both before they execute and after they complete. This is implemented via paired hooks (before/after), wrapper hooks (around/next), or middleware chains. At least 50% of mutation hooks must offer both pre and post variants.
Fail criteria: Hooks only fire at one point — either before or after operations, but not both. Plugins cannot measure duration, implement caching, or perform before-and-after transformations.
Skip (N/A) when: The plugin system's hooks don't correspond to interceptable operations (e.g., plugins provide data or configuration, not behavior interception).
Detail on fail: "The hook system only fires 'onRequest' hooks before handling. There is no corresponding 'afterRequest' or 'onResponse' hook. Plugins cannot measure request duration, log response status, or implement response caching because they have no hook point after the operation completes."
Remediation: Before/after hooks are what make a plugin system powerful enough for real-world use cases. Without them, plugins are limited to one-directional interception.
// Paired hooks:
await invokeHook('before:save', { entity, context });
const result = await save(entity);
await invokeHook('after:save', { entity, result, context });
// Or middleware/wrapper pattern:
async function saveWithHooks(entity: Entity) {
return runMiddleware(entity, savePipeline, async (entity) => {
return await save(entity);
});
}