{"record":{"id":"a439c9e96da5f708","repo":"can1357/oh-my-pi","slug":"pluginclass-must-be-a-mnemopiplugin-subclass","errorCode":null,"errorMessage":"pluginClass must be a MnemopiPlugin subclass","messagePattern":"pluginClass must be a MnemopiPlugin subclass","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/mnemopi/src/core/plugins.ts","lineNumber":273,"sourceCode":"\toverride onRecall(_memory: MemoryDict): void {}\n\toverride onConsolidate(_summary: MemoryDict): void {}\n\toverride onInvalidate(_memoryId: string): void {}\n}\n\nexport type PluginConstructor<T extends MnemopiPlugin = MnemopiPlugin> = new (config?: PluginConfig) => T;\n\nexport class PluginManager {\n\tprivate readonly registry = new Map<string, PluginConstructor>();\n\tprivate readonly instances = new Map<string, MnemopiPlugin>();\n\tconstructor(private readonly pluginDir = DEFAULT_PLUGIN_DIR) {\n\t\tthis.registerPlugin(\"logging\", LoggingPlugin);\n\t\tthis.registerPlugin(\"metrics\", MetricsPlugin);\n\t\tthis.registerPlugin(\"filter\", FilterPlugin);\n\t\tthis.registerPlugin(\"compression\", CompressionPlugin);\n\t}\n\tregisterPlugin(name: string, pluginClass: PluginConstructor): void {\n\t\tif (typeof pluginClass !== \"function\" || !(pluginClass.prototype instanceof MnemopiPlugin)) {\n\t\t\tthrow new TypeError(\"pluginClass must be a MnemopiPlugin subclass\");\n\t\t}\n\t\tif (this.registry.has(name)) throw new ValueError(`Plugin '${name}' is already registered`);\n\t\tthis.registry.set(name, pluginClass);\n\t}\n\tloadPlugin(name: string, config: PluginConfig = {}): MnemopiPlugin {\n\t\tconst pluginClass = this.registry.get(name);\n\t\tif (pluginClass === undefined) throw new ValueError(`Plugin '${name}' is not registered`);\n\t\tif (this.instances.has(name)) throw new Error(`Plugin '${name}' is already loaded`);\n\t\tconst instance = new pluginClass(config);\n\t\tinstance.initialize();\n\t\tthis.instances.set(name, instance);\n\t\treturn instance;\n\t}\n\tunloadPlugin(name: string): void {\n\t\tconst instance = this.instances.get(name);\n\t\tif (instance === undefined) throw new ValueError(`Plugin '${name}' is not loaded`);\n\t\tthis.instances.delete(name);\n\t\tinstance.shutdown();","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/mnemopi/src/core/plugins.ts#L255-L291","documentation":"registerPlugin validates that the second argument is a constructor function whose prototype chain derives from MnemopiPlugin. The TypeError fires when you pass anything else — a plain class, a function, or a non-function value — because the manager relies on MnemopiPlugin's interface for initialize/shutdown lifecycle calls.","triggerScenarios":"Calling manager.registerPlugin(name, X) where X is a plain object, an arrow function, a class not extending MnemopiPlugin, or a mis-typed import (e.g. passing the module namespace instead of the class).","commonSituations":"Importing the plugin class incorrectly (default vs named import) so the value is undefined; writing a custom plugin that forgets `extends MnemopiPlugin`; refactoring that replaced the class with a factory function.","solutions":["Make the plugin class extend MnemopiPlugin: `class MyPlugin extends MnemopiPlugin { ... }`","Verify the import resolves to the class itself (console.log(pluginClass) before registering; fix default/named import)","Pass a class constructor, not an instance or a factory function that returns one"],"exampleFix":"// before\nmanager.registerPlugin(\"custom\", createCustomPlugin);\n// after\nclass CustomPlugin extends MnemopiPlugin { /* ... */ }\nmanager.registerPlugin(\"custom\", CustomPlugin);","handlingStrategy":"type-guard","validationCode":"import { MnemopiPlugin } from \"...\";\nif (typeof pluginClass === \"function\" && pluginClass.prototype instanceof MnemopiPlugin) {\n  manager.registerPlugin(name, pluginClass);\n}","typeGuard":"function isPluginConstructor(value: unknown): value is new (config: PluginConfig) => MnemopiPlugin {\n  return typeof value === \"function\" && value.prototype instanceof MnemopiPlugin;\n}","tryCatchPattern":"try {\n  manager.registerPlugin(name, pluginClass);\n} catch (err) {\n  if (err instanceof TypeError) {\n    throw new Error(`registerPlugin(${name}): not a MnemopiPlugin subclass`, { cause: err });\n  }\n  throw err;\n}","preventionTips":["Always declare custom plugins as `class X extends MnemopiPlugin`","Type the registration argument as PluginConstructor so TS catches non-class values at compile time","Verify imports resolve to the class (not undefined) before registering"],"tags":["plugin","typeerror","validation"],"backgroundTag":"invalid-plugin-class","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}