@libria/plugin-loader
Advanced tools
+1
-1
@@ -1,2 +0,2 @@ | ||
| var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`fs-extra`);c=s(c);let l=require(`fast-glob`);l=s(l);let u=require(`path`);u=s(u);let d=require(`url`),f=require(`node:module`);function p(e,t,n){return{pluginType:e,name:t,api:n}}async function m(e,t){let n=[];if(e.includes(`*`)||e.includes(`{`)||e.includes(`?`)){let r=await(0,l.default)(e.replace(/\\/g,`/`),{onlyDirectories:!0,absolute:!0});for(let e of r){let r=u.default.join(e,`plugin.json`);if(!await c.default.pathExists(r))continue;let i=await c.default.readJson(r);t&&i.pluginType!==t||n.push({...i,__dir:e})}}else{if(!await c.default.pathExists(e))return[];let r=await c.default.readdir(e);for(let i of r){let r=u.default.join(e,i);if(!(await c.default.stat(r)).isDirectory())continue;let a=u.default.join(r,`plugin.json`);if(!await c.default.pathExists(a))continue;let o=await c.default.readJson(a);t&&o.pluginType!==t||n.push({...o,__dir:r})}}return n}var h=class extends Error{constructor(e,t){super(`Failed to load plugin "${e}".\n${String(t)}`),this.name=`PluginLoadError`,this.pluginName=e,this.cause=t}},g=class extends Error{constructor(e){super(`Plugin "${e}" has invalid export`),this.name=`PluginInvalidExportError`,this.pluginName=e}},_=class extends Error{constructor(e,t,n){super(`Plugin type mismatch for "${e}": "${n}" !== "${t}"`),this.name=`PluginTypeMismatchError`,this.pluginName=e,this.expected=t,this.actual=n}};const v=(0,f.createRequire)(require(`url`).pathToFileURL(__filename).href);async function y(e){let t,n;if(e.module)try{t=await import((0,d.pathToFileURL)(u.default.resolve(e.__dir,e.module)).href)}catch(e){n=e}if(!t&&e.main)try{t=v(u.default.resolve(e.__dir,e.main))}catch(e){n=e}if(!t)throw new h(e.name,n);let r=t.default??t;if(!r||typeof r!=`object`)throw new g(e.name);if(r.pluginType!==e.pluginType)throw new _(e.name,e.pluginType,r.pluginType);return r}async function b(e,t){let n=await m(e,t),r=[];for(let e of n){let t=await y(e);r.push(t)}return r}exports.PluginInvalidExportError=g,exports.PluginLoadError=h,exports.PluginTypeMismatchError=_,exports.definePlugin=p,exports.findPlugins=m,exports.loadAllPlugins=b,exports.loadPlugin=y; | ||
| var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`path`);c=s(c);let l=require(`fast-glob`);l=s(l);let u=require(`fs-extra`);u=s(u);let d=require(`semver`);d=s(d);let f=require(`node:module`),p=require(`url`),m=require(`chokidar`);var h=class extends Error{constructor(e,t){super(`Failed to load plugin "${e}".\n${String(t)}`),this.name=`PluginLoadError`,this.pluginName=e,this.cause=t}},g=class extends Error{constructor(e){super(`Plugin "${e}" has invalid export`),this.name=`PluginInvalidExportError`,this.pluginName=e}},_=class extends Error{constructor(e,t,n){super(`Plugin type mismatch for "${e}": "${n}" !== "${t}"`),this.name=`PluginTypeMismatchError`,this.pluginName=e,this.expected=t,this.actual=n}},v=class extends Error{constructor(e){super(`Duplicate plugin "${e}"`),this.id=e}},y=class extends Error{constructor(e){super(`Plugin "${e}" not found`),this.name=`PluginNotFoundError`,this.id=e}},b=class extends Error{constructor(e,t){super(`Manifest not found for plugin "${e}" in "${t}"`),this.name=`ManifestNotFoundError`,this.pluginId=e,this.dir=t}},x=class extends Error{constructor(e){super(`Circular dependency detected: ${e.join(` -> `)}`),this.name=`CircularDependencyError`,this.cycle=e}},S=class extends Error{constructor(e,t){let n=t?`Dependency "${e}" not found (required by "${t}")`:`Dependency "${e}" not found`;super(n),this.name=`DependencyNotFoundError`,this.dependencyId=e,this.requestedBy=t}},C=class extends Error{constructor(e,t,n,r){let i=r?`Version mismatch: ${e}@${t} does not satisfy ${n} (required by "${r}")`:`Version mismatch: ${e}@${t} does not satisfy ${n}`;super(i),this.name=`VersionMismatchError`,this.packageId=e,this.actualVersion=t,this.requiredVersion=n,this.requestedBy=r}},w=class{constructor(){this.plugins=new Map}register(e,t,n){if(this.plugins.has(e))throw new v(e);this.plugins.set(e,{plugin:t,metadata:n})}unregister(e){let t=this.plugins.get(e);if(t)return this.plugins.delete(e),t.plugin}getPlugin(e){let t=this.plugins.get(e);if(!t)throw new y(e);return t.plugin.api}hasPlugin(e){return this.plugins.has(e)}getPluginInstance(e){return this.plugins.get(e)?.plugin}getPluginMetadata(e){return this.plugins.get(e)?.metadata}getPluginIds(){return[...this.plugins.keys()]}getPluginsByType(e){let t=[];for(let{metadata:n}of this.plugins.values())n.pluginType===e&&t.push(n);return t}getAllMetadata(){return[...this.plugins.values()].map(e=>e.metadata)}};function T(e){return e}async function E(e,t){let n=[];if(e.includes(`*`)||e.includes(`{`)||e.includes(`?`)){let r=await(0,l.default)(e.replace(/\\/g,`/`),{onlyDirectories:!0,absolute:!0});for(let e of r){let r=c.default.join(e,`plugin.json`);if(!await u.default.pathExists(r))continue;let i=await u.default.readJson(r);t&&i.pluginType!==t||n.push({...i,__dir:e})}}else{if(!await u.default.pathExists(e))return[];let r=c.default.join(e,`plugin.json`);if(await u.default.pathExists(r)){let i=await u.default.readJson(r);return(!t||i.pluginType===t)&&n.push({...i,__dir:c.default.resolve(e)}),n}let i=await u.default.readdir(e);for(let r of i){let i=c.default.join(e,r);if(!(await u.default.stat(i)).isDirectory())continue;let a=c.default.join(i,`plugin.json`);if(!await u.default.pathExists(a))continue;let o=await u.default.readJson(a);t&&o.pluginType!==t||n.push({...o,__dir:i})}}return n}function D(e){let t=new Map(e.map(e=>[e.id,e])),n=[],r=new Set,i=new Set;function a(e,o,s=[]){if(i.has(e)){if(o){let n=t.get(e);if(n&&!d.default.satisfies(n.version,o))throw new C(e,n.version,o,s[s.length-1])}return}if(r.has(e))throw new x([...s,e].slice(s.indexOf(e)));let c=t.get(e);if(!c)throw new S(e,s[s.length-1]);if(o&&!d.default.satisfies(c.version,o))throw new C(e,c.version,o,s[s.length-1]);if(r.add(e),c.dependencies)for(let t of c.dependencies)a(t.id,t.version,[...s,e]);r.delete(e),i.add(e),n.push(c)}for(let t of e)a(t.id);return n}const O=(0,f.createRequire)(require(`url`).pathToFileURL(__filename).href);function k(e){return typeof e==`object`&&!!e&&`id`in e&&`pluginType`in e&&`create`in e&&typeof e.create==`function`}async function A(e){let t,n;if(e.module)try{t=await import(`${(0,p.pathToFileURL)(c.default.resolve(e.__dir,e.module)).href}?t=${Date.now()}`)}catch(e){n=e}if(!t&&e.main)try{let n=c.default.resolve(e.__dir,e.main);delete O.cache[O.resolve(n)],t=O(n)}catch(e){n=e}if(!t)throw new h(e.id,n);let r=t.default??t;if(!k(r))throw new g(e.id);return r}function j(e){if(e.main){let t=c.default.resolve(e.__dir,e.main);try{delete O.cache[O.resolve(t)]}catch{}}}function M(e){return{id:e.id,name:e.name,pluginType:e.pluginType,version:e.version,description:e.description,dependencies:e.dependencies,dir:e.__dir}}var N=class{constructor(){this.manifests=new Map,this.watcher=null,this.watchPatterns=[],this.context=new w}async loadPlugins(e){let t=[];for(let n of e){let e=await E(n);t.push(...e)}let n=D(t);for(let e of n)await this.loadSinglePlugin(e)}async loadSinglePlugin(e){let t=await A(e),n=await Promise.resolve(t.create(this.context)),r=M(e);this.context.register(e.id,n,r),this.manifests.set(e.id,e),n.onLoad&&await Promise.resolve(n.onLoad())}async unloadSinglePlugin(e){let t=this.context.getPluginInstance(e);t?.onUnload&&await Promise.resolve(t.onUnload()),this.context.unregister(e),this.manifests.delete(e)}async reloadPlugin(e){let t=this.manifests.get(e);if(!t)throw new y(e);j(t),await this.unloadSinglePlugin(e);let[n]=await E(t.__dir);if(!n)throw new b(e,t.__dir);await this.loadSinglePlugin(n)}async unloadPlugin(e){if(!this.hasPlugin(e))throw new y(e);await this.unloadSinglePlugin(e)}async watch(e,t){this.watcher&&await this.stopWatching(),this.watchPatterns=e;let n=[];for(let[,e]of this.manifests)n.push(e.__dir);n.length!==0&&(this.watcher=(0,m.watch)(n,{ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:100,pollInterval:50}}),this.watcher.on(`change`,async e=>{for(let[n,r]of this.manifests)if(e.startsWith(r.__dir)){try{await this.reloadPlugin(n),t?.(n,`reload`)}catch(e){t?.(n,`error`,e instanceof Error?e:Error(String(e)))}break}}))}async stopWatching(){this.watcher&&=(await this.watcher.close(),null)}getPlugin(e){return this.context.getPlugin(e)}hasPlugin(e){return this.context.hasPlugin(e)}getPluginIds(){return this.context.getPluginIds()}getPluginMetadata(e){return this.context.getPluginMetadata(e)}getPluginsByType(e){return this.context.getPluginsByType(e)}getAllMetadata(){return this.context.getAllMetadata()}getContext(){return this.context}async shutdown(){await this.stopWatching();let e=[...this.manifests.keys()].reverse();for(let t of e)await this.unloadSinglePlugin(t)}};exports.CircularDependencyError=x,exports.DefaultPluginContext=w,exports.DependencyNotFoundError=S,exports.DuplicatePluginError=v,exports.ManifestNotFoundError=b,exports.PluginInvalidExportError=g,exports.PluginLoadError=h,exports.PluginManager=N,exports.PluginNotFoundError=y,exports.PluginTypeMismatchError=_,exports.VersionMismatchError=C,exports.clearPluginCache=j,exports.definePlugin=T,exports.findPlugins=E,exports.loadPlugin=A,exports.topologicalSort=D; | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs","names":["fs","require"],"sources":["../src/define-plugin.ts","../src/find-plugins.ts","../src/types.ts","../src/load-plugin.ts","../src/load-all-plugins.ts"],"sourcesContent":["import {LibriaPlugin} from \"./types\";\r\n\r\nexport function definePlugin<T>(\r\n pluginType: string,\r\n name: string,\r\n api: T\r\n): LibriaPlugin<T> {\r\n return {\r\n pluginType: pluginType,\r\n name,\r\n api\r\n };\r\n}","import {PluginManifest} from \"./types\";\r\nimport fs from \"fs-extra\";\r\nimport fg from \"fast-glob\";\r\nimport path from \"path\";\r\n\r\nexport async function findPlugins(\r\n pattern: string,\r\n pluginType?: string\r\n): Promise<PluginManifest[]> {\r\n const manifests: PluginManifest[] = [];\r\n\r\n // Check if pattern is a glob pattern or a simple path\r\n const isGlob = pattern.includes('*') || pattern.includes('{') || pattern.includes('?');\r\n\r\n if (isGlob) {\r\n // Normalize path separators for fast-glob (it expects forward slashes)\r\n const normalizedPattern = pattern.replace(/\\\\/g, '/');\r\n\r\n // Use fast-glob to find all matching directories\r\n const pluginDirs = await fg(normalizedPattern, {\r\n onlyDirectories: true,\r\n absolute: true,\r\n });\r\n\r\n for (const dir of pluginDirs) {\r\n const manifestPath = path.join(dir, 'plugin.json');\r\n if (!(await fs.pathExists(manifestPath))) continue;\r\n\r\n const raw = await fs.readJson(manifestPath);\r\n\r\n if (pluginType && raw.pluginType !== pluginType) continue;\r\n\r\n manifests.push({\r\n ...raw,\r\n __dir: dir\r\n });\r\n }\r\n } else {\r\n // Original behavior for simple directory paths\r\n if (!(await fs.pathExists(pattern))) return [];\r\n\r\n const entries = await fs.readdir(pattern);\r\n\r\n for (const entry of entries) {\r\n const fullPath = path.join(pattern, entry);\r\n const stat = await fs.stat(fullPath);\r\n\r\n if (!stat.isDirectory()) continue;\r\n\r\n const manifestPath = path.join(fullPath, 'plugin.json');\r\n if (!(await fs.pathExists(manifestPath))) continue;\r\n\r\n const raw = await fs.readJson(manifestPath);\r\n\r\n if (pluginType && raw.pluginType !== pluginType) continue;\r\n\r\n manifests.push({\r\n ...raw,\r\n __dir: fullPath\r\n });\r\n }\r\n }\r\n\r\n return manifests;\r\n}","export interface LibriaPlugin<T = unknown> {\r\n readonly pluginType: string;\r\n readonly name: string;\r\n readonly api: T;\r\n}\r\n\r\nexport interface PluginManifest {\r\n readonly name: string;\r\n readonly pluginType: string;\r\n\r\n readonly main?: string; // cjs\r\n readonly module?: string; // esm\r\n readonly types?: string;\r\n\r\n // resolved absolute path to the plugin folder\r\n readonly __dir: string;\r\n}\r\n\r\n// Plugin Errors\r\n\r\nexport class PluginLoadError extends Error {\r\n readonly pluginName: string;\r\n readonly cause: unknown;\r\n\r\n constructor(pluginName: string, cause: unknown) {\r\n super(`Failed to load plugin \"${pluginName}\".\\n${String(cause)}`);\r\n this.name = 'PluginLoadError';\r\n this.pluginName = pluginName;\r\n this.cause = cause;\r\n }\r\n}\r\n\r\nexport class PluginInvalidExportError extends Error {\r\n readonly pluginName: string;\r\n\r\n constructor(pluginName: string) {\r\n super(`Plugin \"${pluginName}\" has invalid export`);\r\n this.name = 'PluginInvalidExportError';\r\n this.pluginName = pluginName;\r\n }\r\n}\r\n\r\nexport class PluginTypeMismatchError extends Error {\r\n readonly pluginName: string;\r\n readonly expected: string;\r\n readonly actual: string;\r\n\r\n constructor(pluginName: string, expected: string, actual: string) {\r\n super(\r\n `Plugin type mismatch for \"${pluginName}\": ` +\r\n `\"${actual}\" !== \"${expected}\"`\r\n );\r\n this.name = 'PluginTypeMismatchError';\r\n this.pluginName = pluginName;\r\n this.expected = expected;\r\n this.actual = actual;\r\n }\r\n}","import {\r\n LibriaPlugin,\r\n PluginManifest,\r\n PluginLoadError,\r\n PluginInvalidExportError,\r\n PluginTypeMismatchError\r\n} from \"./types\";\r\nimport path from \"path\";\r\nimport {pathToFileURL} from \"url\";\r\nimport {createRequire} from \"node:module\";\r\n\r\nconst require = createRequire(import.meta.url);\r\n\r\nexport async function loadPlugin<T = unknown>(\r\n manifest: PluginManifest\r\n): Promise<LibriaPlugin<T>> {\r\n let mod: any;\r\n let lastError: unknown;\r\n\r\n // 1 Try ESM first\r\n if (manifest.module) {\r\n try {\r\n const esmPath = path.resolve(manifest.__dir, manifest.module);\r\n mod = await import(pathToFileURL(esmPath).href);\r\n } catch (err) {\r\n lastError = err;\r\n }\r\n }\r\n\r\n // 2 Fallback to CJS\r\n if (!mod && manifest.main) {\r\n try {\r\n const cjsPath = path.resolve(manifest.__dir, manifest.main);\r\n mod = require(cjsPath);\r\n } catch (err) {\r\n lastError = err;\r\n }\r\n }\r\n\r\n if (!mod) {\r\n throw new PluginLoadError(manifest.name, lastError);\r\n }\r\n\r\n // 3️⃣ Normalize export\r\n const plugin = (mod.default ?? mod) as LibriaPlugin<T>;\r\n\r\n if (!plugin || typeof plugin !== 'object') {\r\n throw new PluginInvalidExportError(manifest.name);\r\n }\r\n\r\n if (plugin.pluginType !== manifest.pluginType) {\r\n throw new PluginTypeMismatchError(\r\n manifest.name,\r\n manifest.pluginType,\r\n plugin.pluginType\r\n );\r\n }\r\n\r\n return plugin;\r\n}","import {LibriaPlugin} from \"./types\";\r\nimport {findPlugins} from \"./find-plugins\";\r\nimport {loadPlugin} from \"./load-plugin\";\r\n\r\nexport async function loadAllPlugins<T = unknown>(\r\n pattern: string,\r\n pluginType?: string\r\n): Promise<LibriaPlugin<T>[]> {\r\n const manifests = await findPlugins(pattern, pluginType);\r\n const plugins: LibriaPlugin<T>[] = [];\r\n\r\n for (const manifest of manifests) {\r\n const plugin = await loadPlugin<T>(manifest);\r\n plugins.push(plugin);\r\n }\r\n\r\n return plugins;\r\n}\r\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.cjs","names":["fs","require"],"sources":["../src/types.ts","../src/default-plugin-context.ts","../src/define-plugin.ts","../src/find-plugins.ts","../src/helpers/topological-sort.ts","../src/load-plugin.ts","../src/plugin-manager.ts"],"sourcesContent":["// Lifecycle hooks that plugins can implement\nexport interface PluginLifecycle {\n /** Called after the plugin is registered and ready to use */\n onLoad?(): void | Promise<void>;\n /** Called before the plugin is unloaded (for hot-reload or shutdown) */\n onUnload?(): void | Promise<void>;\n}\n\nexport interface LibriaPlugin<T = unknown> extends PluginLifecycle {\n api: T;\n}\n\nexport interface PluginFactory<T = unknown> {\n readonly id: string;\n readonly pluginType: string;\n readonly name?: string;\n\n /** Create the plugin instance. Can be sync or async. */\n create<C extends PluginContext>(ctx: C): LibriaPlugin<T> | Promise<LibriaPlugin<T>>;\n}\n\nexport interface PluginContext {\n getPlugin<T = unknown>(id: string): T;\n hasPlugin(id: string): boolean;\n}\n\n/** Metadata stored for each loaded plugin */\nexport interface PluginMetadata {\n readonly id: string;\n readonly name?: string;\n readonly pluginType: string;\n readonly version: string;\n readonly description?: string;\n readonly dependencies?: { id: string; version: string }[];\n /** Absolute path to the plugin directory */\n readonly dir: string;\n}\n\nexport interface PluginManifest {\n readonly id: string;\n readonly name?: string;\n readonly pluginType: string;\n readonly version: string; // Semver\n readonly description?: string;\n\n readonly main?: string; // cjs\n readonly module?: string; // esm\n readonly types?: string;\n\n readonly dependencies?: { id: string; version: string }[];\n // resolved absolute path to the plugin folder\n readonly __dir: string;\n}\n\n// Plugin Errors\n\nexport class PluginLoadError extends Error {\n public readonly pluginName: string;\n public readonly cause: unknown;\n\n constructor(pluginName: string, cause: unknown) {\n super(`Failed to load plugin \"${pluginName}\".\\n${String(cause)}`);\n this.name = 'PluginLoadError';\n this.pluginName = pluginName;\n this.cause = cause;\n }\n}\n\nexport class PluginInvalidExportError extends Error {\n public readonly pluginName: string;\n\n constructor(pluginName: string) {\n super(`Plugin \"${pluginName}\" has invalid export`);\n this.name = 'PluginInvalidExportError';\n this.pluginName = pluginName;\n }\n}\n\nexport class PluginTypeMismatchError extends Error {\n public readonly pluginName: string;\n public readonly expected: string;\n public readonly actual: string;\n\n constructor(pluginName: string, expected: string, actual: string) {\n super(`Plugin type mismatch for \"${pluginName}\": ` + `\"${actual}\" !== \"${expected}\"`);\n this.name = 'PluginTypeMismatchError';\n this.pluginName = pluginName;\n this.expected = expected;\n this.actual = actual;\n }\n}\n\nexport class DuplicatePluginError extends Error {\n public readonly id: string;\n\n constructor(id: string) {\n super(`Duplicate plugin \"${id}\"`);\n\n this.id = id;\n }\n}\n\nexport class PluginNotFoundError extends Error {\n public readonly id: string;\n\n constructor(id: string) {\n super(`Plugin \"${id}\" not found`);\n this.name = 'PluginNotFoundError';\n this.id = id;\n }\n}\n\nexport class ManifestNotFoundError extends Error {\n public readonly pluginId: string;\n public readonly dir: string;\n\n constructor(pluginId: string, dir: string) {\n super(`Manifest not found for plugin \"${pluginId}\" in \"${dir}\"`);\n this.name = 'ManifestNotFoundError';\n this.pluginId = pluginId;\n this.dir = dir;\n }\n}\n\n// Dependency Resolution Errors\n\nexport class CircularDependencyError extends Error {\n public readonly cycle: string[];\n\n constructor(cycle: string[]) {\n super(`Circular dependency detected: ${cycle.join(' -> ')}`);\n this.name = 'CircularDependencyError';\n this.cycle = cycle;\n }\n}\n\nexport class DependencyNotFoundError extends Error {\n public readonly dependencyId: string;\n public readonly requestedBy?: string;\n\n constructor(dependencyId: string, requestedBy?: string) {\n const message = requestedBy\n ? `Dependency \"${dependencyId}\" not found (required by \"${requestedBy}\")`\n : `Dependency \"${dependencyId}\" not found`;\n super(message);\n this.name = 'DependencyNotFoundError';\n this.dependencyId = dependencyId;\n this.requestedBy = requestedBy;\n }\n}\n\nexport class VersionMismatchError extends Error {\n public readonly packageId: string;\n public readonly actualVersion: string;\n public readonly requiredVersion: string;\n public readonly requestedBy?: string;\n\n constructor(\n packageId: string,\n actualVersion: string,\n requiredVersion: string,\n requestedBy?: string\n ) {\n const message = requestedBy\n ? `Version mismatch: ${packageId}@${actualVersion} does not satisfy ${requiredVersion} (required by \"${requestedBy}\")`\n : `Version mismatch: ${packageId}@${actualVersion} does not satisfy ${requiredVersion}`;\n super(message);\n this.name = 'VersionMismatchError';\n this.packageId = packageId;\n this.actualVersion = actualVersion;\n this.requiredVersion = requiredVersion;\n this.requestedBy = requestedBy;\n }\n}\n","import {\n DuplicatePluginError,\n LibriaPlugin,\n PluginContext,\n PluginMetadata,\n PluginNotFoundError,\n} from './types';\n\ninterface RegisteredPlugin {\n plugin: LibriaPlugin;\n metadata: PluginMetadata;\n}\n\nexport class DefaultPluginContext implements PluginContext {\n private plugins: Map<string, RegisteredPlugin> = new Map();\n\n public register(id: string, plugin: LibriaPlugin, metadata: PluginMetadata): void {\n if (this.plugins.has(id)) {\n throw new DuplicatePluginError(id);\n }\n\n this.plugins.set(id, { plugin, metadata });\n }\n\n public unregister(id: string): LibriaPlugin | undefined {\n const registered = this.plugins.get(id);\n if (registered) {\n this.plugins.delete(id);\n return registered.plugin;\n }\n return undefined;\n }\n\n public getPlugin<T = unknown>(id: string): T {\n const registered = this.plugins.get(id);\n if (!registered) {\n throw new PluginNotFoundError(id);\n }\n\n return registered.plugin.api as T;\n }\n\n public hasPlugin(id: string): boolean {\n return this.plugins.has(id);\n }\n\n /** Get the raw LibriaPlugin instance (includes lifecycle hooks) */\n public getPluginInstance(id: string): LibriaPlugin | undefined {\n return this.plugins.get(id)?.plugin;\n }\n\n /** Get metadata for a specific plugin */\n public getPluginMetadata(id: string): PluginMetadata | undefined {\n return this.plugins.get(id)?.metadata;\n }\n\n /** Get all plugin IDs */\n public getPluginIds(): string[] {\n return [...this.plugins.keys()];\n }\n\n /** Get all plugins of a specific type */\n public getPluginsByType(pluginType: string): PluginMetadata[] {\n const result: PluginMetadata[] = [];\n for (const { metadata } of this.plugins.values()) {\n if (metadata.pluginType === pluginType) {\n result.push(metadata);\n }\n }\n return result;\n }\n\n /** Get all loaded plugin metadata */\n public getAllMetadata(): PluginMetadata[] {\n return [...this.plugins.values()].map(r => r.metadata);\n }\n}\n","import { PluginFactory } from './types';\n\nexport function definePlugin<T>(factory: PluginFactory<T>): PluginFactory<T> {\n return factory;\n}\n","import path from 'path';\n\nimport fg from 'fast-glob';\nimport fs from 'fs-extra';\n\nimport { PluginManifest } from './types';\n\nexport async function findPlugins(pattern: string, pluginType?: string): Promise<PluginManifest[]> {\n const manifests: PluginManifest[] = [];\n\n // Check if pattern is a glob pattern or a simple path\n const isGlob = pattern.includes('*') || pattern.includes('{') || pattern.includes('?');\n\n if (isGlob) {\n // Normalize path separators for fast-glob (it expects forward slashes)\n const normalizedPattern = pattern.replace(/\\\\/g, '/');\n\n // Use fast-glob to find all matching directories\n const pluginDirs = await fg(normalizedPattern, {\n onlyDirectories: true,\n absolute: true,\n });\n\n for (const dir of pluginDirs) {\n const manifestPath = path.join(dir, 'plugin.json');\n if (!(await fs.pathExists(manifestPath))) continue;\n\n const raw = await fs.readJson(manifestPath);\n\n if (pluginType && raw.pluginType !== pluginType) continue;\n\n manifests.push({\n ...raw,\n __dir: dir,\n });\n }\n } else {\n // Simple directory path - check if it exists\n if (!(await fs.pathExists(pattern))) return [];\n\n // First, check if the directory itself is a plugin (has plugin.json)\n const directManifestPath = path.join(pattern, 'plugin.json');\n if (await fs.pathExists(directManifestPath)) {\n const raw = await fs.readJson(directManifestPath);\n if (!pluginType || raw.pluginType === pluginType) {\n manifests.push({\n ...raw,\n __dir: path.resolve(pattern),\n });\n }\n return manifests;\n }\n\n // Otherwise, scan subdirectories for plugins\n const entries = await fs.readdir(pattern);\n\n for (const entry of entries) {\n const fullPath = path.join(pattern, entry);\n const stat = await fs.stat(fullPath);\n\n if (!stat.isDirectory()) continue;\n\n const manifestPath = path.join(fullPath, 'plugin.json');\n if (!(await fs.pathExists(manifestPath))) continue;\n\n const raw = await fs.readJson(manifestPath);\n\n if (pluginType && raw.pluginType !== pluginType) continue;\n\n manifests.push({\n ...raw,\n __dir: fullPath,\n });\n }\n }\n\n return manifests;\n}\n","import semver from 'semver';\n\nimport {\n CircularDependencyError,\n DependencyNotFoundError,\n PluginManifest,\n VersionMismatchError,\n} from '../types';\n\nexport function topologicalSort(packages: PluginManifest[]): PluginManifest[] {\n const packageMap = new Map(packages.map(pkg => [pkg.id, pkg]));\n const sorted: PluginManifest[] = [];\n const visiting = new Set<string>();\n const visited = new Set<string>();\n\n function visit(id: string, requiredVersion?: string, path: string[] = []): void {\n if (visited.has(id)) {\n // Still need to validate version even if already visited\n if (requiredVersion) {\n const pkg = packageMap.get(id);\n if (pkg && !semver.satisfies(pkg.version, requiredVersion)) {\n throw new VersionMismatchError(\n id,\n pkg.version,\n requiredVersion,\n path[path.length - 1]\n );\n }\n }\n return;\n }\n\n if (visiting.has(id)) {\n // Circular dependency detected\n const cycle = [...path, id].slice(path.indexOf(id));\n throw new CircularDependencyError(cycle);\n }\n\n const pkg = packageMap.get(id);\n if (!pkg) {\n throw new DependencyNotFoundError(id, path[path.length - 1]);\n }\n\n // Validate version requirement\n if (requiredVersion && !semver.satisfies(pkg.version, requiredVersion)) {\n throw new VersionMismatchError(id, pkg.version, requiredVersion, path[path.length - 1]);\n }\n\n visiting.add(id);\n\n // Visit all dependencies first\n if (pkg.dependencies) {\n for (const dep of pkg.dependencies) {\n visit(dep.id, dep.version, [...path, id]);\n }\n }\n\n visiting.delete(id);\n visited.add(id);\n sorted.push(pkg);\n }\n\n // Visit all packages\n for (const pkg of packages) {\n visit(pkg.id);\n }\n\n return sorted;\n}\n","import { createRequire } from 'node:module';\nimport path from 'path';\nimport { pathToFileURL } from 'url';\n\nimport { PluginFactory, PluginInvalidExportError, PluginLoadError, PluginManifest } from './types';\n\nconst require = createRequire(import.meta.url);\n\nfunction isPluginFactory(obj: unknown): obj is PluginFactory {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'id' in obj &&\n 'pluginType' in obj &&\n 'create' in obj &&\n typeof (obj as PluginFactory).create === 'function'\n );\n}\n\nexport async function loadPlugin<T = unknown>(manifest: PluginManifest): Promise<PluginFactory<T>> {\n let mod: unknown;\n let lastError: unknown;\n\n // 1. Try ESM first\n if (manifest.module) {\n try {\n const esmPath = path.resolve(manifest.__dir, manifest.module);\n const fileUrl = pathToFileURL(esmPath).href;\n // Add timestamp to bust ESM cache for hot-reload\n mod = await import(`${fileUrl}?t=${Date.now()}`);\n } catch (err) {\n lastError = err;\n }\n }\n\n // 2. Fallback to CJS\n if (!mod && manifest.main) {\n try {\n const cjsPath = path.resolve(manifest.__dir, manifest.main);\n // Clear CJS cache before requiring\n delete require.cache[require.resolve(cjsPath)];\n mod = require(cjsPath);\n } catch (err) {\n lastError = err;\n }\n }\n\n if (!mod) {\n throw new PluginLoadError(manifest.id, lastError);\n }\n\n // 3. Normalize export\n const moduleWithDefault = mod as { default?: unknown };\n const factory = (moduleWithDefault.default ?? mod) as PluginFactory<T>;\n\n if (!isPluginFactory(factory)) {\n throw new PluginInvalidExportError(manifest.id);\n }\n\n return factory;\n}\n\n/**\n * Clear the module cache for a plugin (used for hot-reload)\n */\nexport function clearPluginCache(manifest: PluginManifest): void {\n // Clear CJS cache\n if (manifest.main) {\n const cjsPath = path.resolve(manifest.__dir, manifest.main);\n try {\n delete require.cache[require.resolve(cjsPath)];\n } catch {\n // Ignore if not in cache\n }\n }\n\n // ESM cache is handled by timestamp query param in loadPlugin\n}\n","import { FSWatcher, watch } from 'chokidar';\n\nimport { DefaultPluginContext } from './default-plugin-context';\nimport { findPlugins } from './find-plugins';\nimport { topologicalSort } from './helpers';\nimport { loadPlugin, clearPluginCache } from './load-plugin';\nimport {\n ManifestNotFoundError,\n PluginManifest,\n PluginMetadata,\n PluginNotFoundError,\n} from './types';\n\nfunction manifestToMetadata(manifest: PluginManifest): PluginMetadata {\n return {\n id: manifest.id,\n name: manifest.name,\n pluginType: manifest.pluginType,\n version: manifest.version,\n description: manifest.description,\n dependencies: manifest.dependencies,\n dir: manifest.__dir,\n };\n}\n\nexport class PluginManager {\n private readonly context: DefaultPluginContext;\n private manifests: Map<string, PluginManifest> = new Map();\n private watcher: FSWatcher | null = null;\n private watchPatterns: string[] = [];\n\n public constructor() {\n this.context = new DefaultPluginContext();\n }\n\n public async loadPlugins(patterns: string[]): Promise<void> {\n const allManifests: PluginManifest[] = [];\n\n for (const pattern of patterns) {\n const manifests = await findPlugins(pattern);\n allManifests.push(...manifests);\n }\n\n // Sort the manifests so that the dependencies are loaded before their dependents\n const sortedManifests = topologicalSort(allManifests);\n\n for (const manifest of sortedManifests) {\n await this.loadSinglePlugin(manifest);\n }\n }\n\n private async loadSinglePlugin(manifest: PluginManifest): Promise<void> {\n const factory = await loadPlugin(manifest);\n const plugin = await Promise.resolve(factory.create(this.context));\n const metadata = manifestToMetadata(manifest);\n\n this.context.register(manifest.id, plugin, metadata);\n this.manifests.set(manifest.id, manifest);\n\n // Call onLoad lifecycle hook\n if (plugin.onLoad) {\n await Promise.resolve(plugin.onLoad());\n }\n }\n\n private async unloadSinglePlugin(id: string): Promise<void> {\n const plugin = this.context.getPluginInstance(id);\n\n // Call onUnload lifecycle hook before removing\n if (plugin?.onUnload) {\n await Promise.resolve(plugin.onUnload());\n }\n\n this.context.unregister(id);\n this.manifests.delete(id);\n }\n\n /**\n * Reload a specific plugin by ID (useful for hot-reload)\n */\n public async reloadPlugin(id: string): Promise<void> {\n const manifest = this.manifests.get(id);\n if (!manifest) {\n throw new PluginNotFoundError(id);\n }\n\n // Clear the module cache for this plugin\n clearPluginCache(manifest);\n\n // Unload the old plugin\n await this.unloadSinglePlugin(id);\n\n // Re-read the manifest from the plugin directory\n const [newManifest] = await findPlugins(manifest.__dir);\n if (!newManifest) {\n throw new ManifestNotFoundError(id, manifest.__dir);\n }\n\n // Load the new version\n await this.loadSinglePlugin(newManifest);\n }\n\n /**\n * Unload a plugin by ID\n */\n public async unloadPlugin(id: string): Promise<void> {\n if (!this.hasPlugin(id)) {\n throw new PluginNotFoundError(id);\n }\n await this.unloadSinglePlugin(id);\n }\n\n /**\n * Start watching for plugin file changes (hot-reload)\n * @param patterns Glob patterns to watch (same as loadPlugins)\n * @param onChange Callback when a plugin is reloaded\n */\n public async watch(\n patterns: string[],\n onChange?: (id: string, event: 'reload' | 'error', error?: Error) => void\n ): Promise<void> {\n if (this.watcher) {\n await this.stopWatching();\n }\n\n this.watchPatterns = patterns;\n\n // Get all plugin directories to watch\n const dirsToWatch: string[] = [];\n for (const [, manifest] of this.manifests) {\n dirsToWatch.push(manifest.__dir);\n }\n\n if (dirsToWatch.length === 0) {\n return;\n }\n\n this.watcher = watch(dirsToWatch, {\n ignoreInitial: true,\n awaitWriteFinish: {\n stabilityThreshold: 100,\n pollInterval: 50,\n },\n });\n\n this.watcher.on('change', async (filePath: string) => {\n // Find which plugin this file belongs to\n for (const [id, manifest] of this.manifests) {\n if (filePath.startsWith(manifest.__dir)) {\n try {\n await this.reloadPlugin(id);\n onChange?.(id, 'reload');\n } catch (err) {\n onChange?.(\n id,\n 'error',\n err instanceof Error ? err : new Error(String(err))\n );\n }\n break;\n }\n }\n });\n }\n\n /**\n * Stop watching for file changes\n */\n public async stopWatching(): Promise<void> {\n if (this.watcher) {\n await this.watcher.close();\n this.watcher = null;\n }\n }\n\n /**\n * Get a plugin's API by its id\n */\n public getPlugin<T = unknown>(id: string): T {\n return this.context.getPlugin<T>(id);\n }\n\n /**\n * Check if a plugin is loaded\n */\n public hasPlugin(id: string): boolean {\n return this.context.hasPlugin(id);\n }\n\n /**\n * Get all loaded plugin IDs\n */\n public getPluginIds(): string[] {\n return this.context.getPluginIds();\n }\n\n /**\n * Get metadata for a specific plugin\n */\n public getPluginMetadata(id: string): PluginMetadata | undefined {\n return this.context.getPluginMetadata(id);\n }\n\n /**\n * Get all plugins of a specific type\n */\n public getPluginsByType(pluginType: string): PluginMetadata[] {\n return this.context.getPluginsByType(pluginType);\n }\n\n /**\n * Get all loaded plugin metadata\n */\n public getAllMetadata(): PluginMetadata[] {\n return this.context.getAllMetadata();\n }\n\n /**\n * Get the plugin context (for advanced use cases)\n */\n public getContext(): DefaultPluginContext {\n return this.context;\n }\n\n /**\n * Shutdown the plugin manager - unloads all plugins and stops watching\n */\n public async shutdown(): Promise<void> {\n await this.stopWatching();\n\n // Unload plugins in reverse order (dependents before dependencies)\n const ids = [...this.manifests.keys()].reverse();\n for (const id of ids) {\n await this.unloadSinglePlugin(id);\n }\n }\n}\n"],"mappings":"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"} |
+159
-9
| //#region src/types.d.ts | ||
| interface LibriaPlugin<T = unknown> { | ||
| interface PluginLifecycle { | ||
| /** Called after the plugin is registered and ready to use */ | ||
| onLoad?(): void | Promise<void>; | ||
| /** Called before the plugin is unloaded (for hot-reload or shutdown) */ | ||
| onUnload?(): void | Promise<void>; | ||
| } | ||
| interface LibriaPlugin<T = unknown> extends PluginLifecycle { | ||
| api: T; | ||
| } | ||
| interface PluginFactory<T = unknown> { | ||
| readonly id: string; | ||
| readonly pluginType: string; | ||
| readonly name: string; | ||
| readonly api: T; | ||
| readonly name?: string; | ||
| /** Create the plugin instance. Can be sync or async. */ | ||
| create<C extends PluginContext>(ctx: C): LibriaPlugin<T> | Promise<LibriaPlugin<T>>; | ||
| } | ||
| interface PluginContext { | ||
| getPlugin<T = unknown>(id: string): T; | ||
| hasPlugin(id: string): boolean; | ||
| } | ||
| /** Metadata stored for each loaded plugin */ | ||
| interface PluginMetadata { | ||
| readonly id: string; | ||
| readonly name?: string; | ||
| readonly pluginType: string; | ||
| readonly version: string; | ||
| readonly description?: string; | ||
| readonly dependencies?: { | ||
| id: string; | ||
| version: string; | ||
| }[]; | ||
| /** Absolute path to the plugin directory */ | ||
| readonly dir: string; | ||
| } | ||
| interface PluginManifest { | ||
| readonly name: string; | ||
| readonly id: string; | ||
| readonly name?: string; | ||
| readonly pluginType: string; | ||
| readonly version: string; | ||
| readonly description?: string; | ||
| readonly main?: string; | ||
| readonly module?: string; | ||
| readonly types?: string; | ||
| readonly dependencies?: { | ||
| id: string; | ||
| version: string; | ||
| }[]; | ||
| readonly __dir: string; | ||
@@ -30,5 +66,53 @@ } | ||
| } | ||
| declare class DuplicatePluginError extends Error { | ||
| readonly id: string; | ||
| constructor(id: string); | ||
| } | ||
| declare class PluginNotFoundError extends Error { | ||
| readonly id: string; | ||
| constructor(id: string); | ||
| } | ||
| declare class ManifestNotFoundError extends Error { | ||
| readonly pluginId: string; | ||
| readonly dir: string; | ||
| constructor(pluginId: string, dir: string); | ||
| } | ||
| declare class CircularDependencyError extends Error { | ||
| readonly cycle: string[]; | ||
| constructor(cycle: string[]); | ||
| } | ||
| declare class DependencyNotFoundError extends Error { | ||
| readonly dependencyId: string; | ||
| readonly requestedBy?: string; | ||
| constructor(dependencyId: string, requestedBy?: string); | ||
| } | ||
| declare class VersionMismatchError extends Error { | ||
| readonly packageId: string; | ||
| readonly actualVersion: string; | ||
| readonly requiredVersion: string; | ||
| readonly requestedBy?: string; | ||
| constructor(packageId: string, actualVersion: string, requiredVersion: string, requestedBy?: string); | ||
| } | ||
| //#endregion | ||
| //#region src/default-plugin-context.d.ts | ||
| declare class DefaultPluginContext implements PluginContext { | ||
| private plugins; | ||
| register(id: string, plugin: LibriaPlugin, metadata: PluginMetadata): void; | ||
| unregister(id: string): LibriaPlugin | undefined; | ||
| getPlugin<T = unknown>(id: string): T; | ||
| hasPlugin(id: string): boolean; | ||
| /** Get the raw LibriaPlugin instance (includes lifecycle hooks) */ | ||
| getPluginInstance(id: string): LibriaPlugin | undefined; | ||
| /** Get metadata for a specific plugin */ | ||
| getPluginMetadata(id: string): PluginMetadata | undefined; | ||
| /** Get all plugin IDs */ | ||
| getPluginIds(): string[]; | ||
| /** Get all plugins of a specific type */ | ||
| getPluginsByType(pluginType: string): PluginMetadata[]; | ||
| /** Get all loaded plugin metadata */ | ||
| getAllMetadata(): PluginMetadata[]; | ||
| } | ||
| //#endregion | ||
| //#region src/define-plugin.d.ts | ||
| declare function definePlugin<T>(pluginType: string, name: string, api: T): LibriaPlugin<T>; | ||
| declare function definePlugin<T>(factory: PluginFactory<T>): PluginFactory<T>; | ||
| //#endregion | ||
@@ -38,9 +122,75 @@ //#region src/find-plugins.d.ts | ||
| //#endregion | ||
| //#region src/helpers/topological-sort.d.ts | ||
| declare function topologicalSort(packages: PluginManifest[]): PluginManifest[]; | ||
| //#endregion | ||
| //#region src/load-plugin.d.ts | ||
| declare function loadPlugin<T = unknown>(manifest: PluginManifest): Promise<LibriaPlugin<T>>; | ||
| declare function loadPlugin<T = unknown>(manifest: PluginManifest): Promise<PluginFactory<T>>; | ||
| /** | ||
| * Clear the module cache for a plugin (used for hot-reload) | ||
| */ | ||
| declare function clearPluginCache(manifest: PluginManifest): void; | ||
| //#endregion | ||
| //#region src/load-all-plugins.d.ts | ||
| declare function loadAllPlugins<T = unknown>(pattern: string, pluginType?: string): Promise<LibriaPlugin<T>[]>; | ||
| //#region src/plugin-manager.d.ts | ||
| declare class PluginManager { | ||
| private readonly context; | ||
| private manifests; | ||
| private watcher; | ||
| private watchPatterns; | ||
| constructor(); | ||
| loadPlugins(patterns: string[]): Promise<void>; | ||
| private loadSinglePlugin; | ||
| private unloadSinglePlugin; | ||
| /** | ||
| * Reload a specific plugin by ID (useful for hot-reload) | ||
| */ | ||
| reloadPlugin(id: string): Promise<void>; | ||
| /** | ||
| * Unload a plugin by ID | ||
| */ | ||
| unloadPlugin(id: string): Promise<void>; | ||
| /** | ||
| * Start watching for plugin file changes (hot-reload) | ||
| * @param patterns Glob patterns to watch (same as loadPlugins) | ||
| * @param onChange Callback when a plugin is reloaded | ||
| */ | ||
| watch(patterns: string[], onChange?: (id: string, event: 'reload' | 'error', error?: Error) => void): Promise<void>; | ||
| /** | ||
| * Stop watching for file changes | ||
| */ | ||
| stopWatching(): Promise<void>; | ||
| /** | ||
| * Get a plugin's API by its id | ||
| */ | ||
| getPlugin<T = unknown>(id: string): T; | ||
| /** | ||
| * Check if a plugin is loaded | ||
| */ | ||
| hasPlugin(id: string): boolean; | ||
| /** | ||
| * Get all loaded plugin IDs | ||
| */ | ||
| getPluginIds(): string[]; | ||
| /** | ||
| * Get metadata for a specific plugin | ||
| */ | ||
| getPluginMetadata(id: string): PluginMetadata | undefined; | ||
| /** | ||
| * Get all plugins of a specific type | ||
| */ | ||
| getPluginsByType(pluginType: string): PluginMetadata[]; | ||
| /** | ||
| * Get all loaded plugin metadata | ||
| */ | ||
| getAllMetadata(): PluginMetadata[]; | ||
| /** | ||
| * Get the plugin context (for advanced use cases) | ||
| */ | ||
| getContext(): DefaultPluginContext; | ||
| /** | ||
| * Shutdown the plugin manager - unloads all plugins and stops watching | ||
| */ | ||
| shutdown(): Promise<void>; | ||
| } | ||
| //#endregion | ||
| export { LibriaPlugin, PluginInvalidExportError, PluginLoadError, PluginManifest, PluginTypeMismatchError, definePlugin, findPlugins, loadAllPlugins, loadPlugin }; | ||
| export { CircularDependencyError, DefaultPluginContext, DependencyNotFoundError, DuplicatePluginError, LibriaPlugin, ManifestNotFoundError, PluginContext, PluginFactory, PluginInvalidExportError, PluginLifecycle, PluginLoadError, PluginManager, PluginManifest, PluginMetadata, PluginNotFoundError, PluginTypeMismatchError, VersionMismatchError, clearPluginCache, definePlugin, findPlugins, loadPlugin, topologicalSort }; | ||
| //# sourceMappingURL=index.d.cts.map |
+159
-9
| //#region src/types.d.ts | ||
| interface LibriaPlugin<T = unknown> { | ||
| interface PluginLifecycle { | ||
| /** Called after the plugin is registered and ready to use */ | ||
| onLoad?(): void | Promise<void>; | ||
| /** Called before the plugin is unloaded (for hot-reload or shutdown) */ | ||
| onUnload?(): void | Promise<void>; | ||
| } | ||
| interface LibriaPlugin<T = unknown> extends PluginLifecycle { | ||
| api: T; | ||
| } | ||
| interface PluginFactory<T = unknown> { | ||
| readonly id: string; | ||
| readonly pluginType: string; | ||
| readonly name: string; | ||
| readonly api: T; | ||
| readonly name?: string; | ||
| /** Create the plugin instance. Can be sync or async. */ | ||
| create<C extends PluginContext>(ctx: C): LibriaPlugin<T> | Promise<LibriaPlugin<T>>; | ||
| } | ||
| interface PluginContext { | ||
| getPlugin<T = unknown>(id: string): T; | ||
| hasPlugin(id: string): boolean; | ||
| } | ||
| /** Metadata stored for each loaded plugin */ | ||
| interface PluginMetadata { | ||
| readonly id: string; | ||
| readonly name?: string; | ||
| readonly pluginType: string; | ||
| readonly version: string; | ||
| readonly description?: string; | ||
| readonly dependencies?: { | ||
| id: string; | ||
| version: string; | ||
| }[]; | ||
| /** Absolute path to the plugin directory */ | ||
| readonly dir: string; | ||
| } | ||
| interface PluginManifest { | ||
| readonly name: string; | ||
| readonly id: string; | ||
| readonly name?: string; | ||
| readonly pluginType: string; | ||
| readonly version: string; | ||
| readonly description?: string; | ||
| readonly main?: string; | ||
| readonly module?: string; | ||
| readonly types?: string; | ||
| readonly dependencies?: { | ||
| id: string; | ||
| version: string; | ||
| }[]; | ||
| readonly __dir: string; | ||
@@ -30,5 +66,53 @@ } | ||
| } | ||
| declare class DuplicatePluginError extends Error { | ||
| readonly id: string; | ||
| constructor(id: string); | ||
| } | ||
| declare class PluginNotFoundError extends Error { | ||
| readonly id: string; | ||
| constructor(id: string); | ||
| } | ||
| declare class ManifestNotFoundError extends Error { | ||
| readonly pluginId: string; | ||
| readonly dir: string; | ||
| constructor(pluginId: string, dir: string); | ||
| } | ||
| declare class CircularDependencyError extends Error { | ||
| readonly cycle: string[]; | ||
| constructor(cycle: string[]); | ||
| } | ||
| declare class DependencyNotFoundError extends Error { | ||
| readonly dependencyId: string; | ||
| readonly requestedBy?: string; | ||
| constructor(dependencyId: string, requestedBy?: string); | ||
| } | ||
| declare class VersionMismatchError extends Error { | ||
| readonly packageId: string; | ||
| readonly actualVersion: string; | ||
| readonly requiredVersion: string; | ||
| readonly requestedBy?: string; | ||
| constructor(packageId: string, actualVersion: string, requiredVersion: string, requestedBy?: string); | ||
| } | ||
| //#endregion | ||
| //#region src/default-plugin-context.d.ts | ||
| declare class DefaultPluginContext implements PluginContext { | ||
| private plugins; | ||
| register(id: string, plugin: LibriaPlugin, metadata: PluginMetadata): void; | ||
| unregister(id: string): LibriaPlugin | undefined; | ||
| getPlugin<T = unknown>(id: string): T; | ||
| hasPlugin(id: string): boolean; | ||
| /** Get the raw LibriaPlugin instance (includes lifecycle hooks) */ | ||
| getPluginInstance(id: string): LibriaPlugin | undefined; | ||
| /** Get metadata for a specific plugin */ | ||
| getPluginMetadata(id: string): PluginMetadata | undefined; | ||
| /** Get all plugin IDs */ | ||
| getPluginIds(): string[]; | ||
| /** Get all plugins of a specific type */ | ||
| getPluginsByType(pluginType: string): PluginMetadata[]; | ||
| /** Get all loaded plugin metadata */ | ||
| getAllMetadata(): PluginMetadata[]; | ||
| } | ||
| //#endregion | ||
| //#region src/define-plugin.d.ts | ||
| declare function definePlugin<T>(pluginType: string, name: string, api: T): LibriaPlugin<T>; | ||
| declare function definePlugin<T>(factory: PluginFactory<T>): PluginFactory<T>; | ||
| //#endregion | ||
@@ -38,9 +122,75 @@ //#region src/find-plugins.d.ts | ||
| //#endregion | ||
| //#region src/helpers/topological-sort.d.ts | ||
| declare function topologicalSort(packages: PluginManifest[]): PluginManifest[]; | ||
| //#endregion | ||
| //#region src/load-plugin.d.ts | ||
| declare function loadPlugin<T = unknown>(manifest: PluginManifest): Promise<LibriaPlugin<T>>; | ||
| declare function loadPlugin<T = unknown>(manifest: PluginManifest): Promise<PluginFactory<T>>; | ||
| /** | ||
| * Clear the module cache for a plugin (used for hot-reload) | ||
| */ | ||
| declare function clearPluginCache(manifest: PluginManifest): void; | ||
| //#endregion | ||
| //#region src/load-all-plugins.d.ts | ||
| declare function loadAllPlugins<T = unknown>(pattern: string, pluginType?: string): Promise<LibriaPlugin<T>[]>; | ||
| //#region src/plugin-manager.d.ts | ||
| declare class PluginManager { | ||
| private readonly context; | ||
| private manifests; | ||
| private watcher; | ||
| private watchPatterns; | ||
| constructor(); | ||
| loadPlugins(patterns: string[]): Promise<void>; | ||
| private loadSinglePlugin; | ||
| private unloadSinglePlugin; | ||
| /** | ||
| * Reload a specific plugin by ID (useful for hot-reload) | ||
| */ | ||
| reloadPlugin(id: string): Promise<void>; | ||
| /** | ||
| * Unload a plugin by ID | ||
| */ | ||
| unloadPlugin(id: string): Promise<void>; | ||
| /** | ||
| * Start watching for plugin file changes (hot-reload) | ||
| * @param patterns Glob patterns to watch (same as loadPlugins) | ||
| * @param onChange Callback when a plugin is reloaded | ||
| */ | ||
| watch(patterns: string[], onChange?: (id: string, event: 'reload' | 'error', error?: Error) => void): Promise<void>; | ||
| /** | ||
| * Stop watching for file changes | ||
| */ | ||
| stopWatching(): Promise<void>; | ||
| /** | ||
| * Get a plugin's API by its id | ||
| */ | ||
| getPlugin<T = unknown>(id: string): T; | ||
| /** | ||
| * Check if a plugin is loaded | ||
| */ | ||
| hasPlugin(id: string): boolean; | ||
| /** | ||
| * Get all loaded plugin IDs | ||
| */ | ||
| getPluginIds(): string[]; | ||
| /** | ||
| * Get metadata for a specific plugin | ||
| */ | ||
| getPluginMetadata(id: string): PluginMetadata | undefined; | ||
| /** | ||
| * Get all plugins of a specific type | ||
| */ | ||
| getPluginsByType(pluginType: string): PluginMetadata[]; | ||
| /** | ||
| * Get all loaded plugin metadata | ||
| */ | ||
| getAllMetadata(): PluginMetadata[]; | ||
| /** | ||
| * Get the plugin context (for advanced use cases) | ||
| */ | ||
| getContext(): DefaultPluginContext; | ||
| /** | ||
| * Shutdown the plugin manager - unloads all plugins and stops watching | ||
| */ | ||
| shutdown(): Promise<void>; | ||
| } | ||
| //#endregion | ||
| export { LibriaPlugin, PluginInvalidExportError, PluginLoadError, PluginManifest, PluginTypeMismatchError, definePlugin, findPlugins, loadAllPlugins, loadPlugin }; | ||
| export { CircularDependencyError, DefaultPluginContext, DependencyNotFoundError, DuplicatePluginError, LibriaPlugin, ManifestNotFoundError, PluginContext, PluginFactory, PluginInvalidExportError, PluginLifecycle, PluginLoadError, PluginManager, PluginManifest, PluginMetadata, PluginNotFoundError, PluginTypeMismatchError, VersionMismatchError, clearPluginCache, definePlugin, findPlugins, loadPlugin, topologicalSort }; | ||
| //# sourceMappingURL=index.d.mts.map |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import{createRequire as e}from"node:module";import t from"fs-extra";import n from"fast-glob";import r from"path";import{pathToFileURL as i}from"url";function a(e,t,n){return{pluginType:e,name:t,api:n}}async function o(e,i){let a=[];if(e.includes(`*`)||e.includes(`{`)||e.includes(`?`)){let o=await n(e.replace(/\\/g,`/`),{onlyDirectories:!0,absolute:!0});for(let e of o){let n=r.join(e,`plugin.json`);if(!await t.pathExists(n))continue;let o=await t.readJson(n);i&&o.pluginType!==i||a.push({...o,__dir:e})}}else{if(!await t.pathExists(e))return[];let n=await t.readdir(e);for(let o of n){let n=r.join(e,o);if(!(await t.stat(n)).isDirectory())continue;let s=r.join(n,`plugin.json`);if(!await t.pathExists(s))continue;let c=await t.readJson(s);i&&c.pluginType!==i||a.push({...c,__dir:n})}}return a}var s=class extends Error{constructor(e,t){super(`Failed to load plugin "${e}".\n${String(t)}`),this.name=`PluginLoadError`,this.pluginName=e,this.cause=t}},c=class extends Error{constructor(e){super(`Plugin "${e}" has invalid export`),this.name=`PluginInvalidExportError`,this.pluginName=e}},l=class extends Error{constructor(e,t,n){super(`Plugin type mismatch for "${e}": "${n}" !== "${t}"`),this.name=`PluginTypeMismatchError`,this.pluginName=e,this.expected=t,this.actual=n}};const u=e(import.meta.url);async function d(e){let t,n;if(e.module)try{t=await import(i(r.resolve(e.__dir,e.module)).href)}catch(e){n=e}if(!t&&e.main)try{t=u(r.resolve(e.__dir,e.main))}catch(e){n=e}if(!t)throw new s(e.name,n);let a=t.default??t;if(!a||typeof a!=`object`)throw new c(e.name);if(a.pluginType!==e.pluginType)throw new l(e.name,e.pluginType,a.pluginType);return a}async function f(e,t){let n=await o(e,t),r=[];for(let e of n){let t=await d(e);r.push(t)}return r}export{c as PluginInvalidExportError,s as PluginLoadError,l as PluginTypeMismatchError,a as definePlugin,o as findPlugins,f as loadAllPlugins,d as loadPlugin}; | ||
| import{createRequire as e}from"node:module";import t from"path";import n from"fast-glob";import r from"fs-extra";import i from"semver";import{pathToFileURL as a}from"url";import{watch as o}from"chokidar";var s=class extends Error{constructor(e,t){super(`Failed to load plugin "${e}".\n${String(t)}`),this.name=`PluginLoadError`,this.pluginName=e,this.cause=t}},c=class extends Error{constructor(e){super(`Plugin "${e}" has invalid export`),this.name=`PluginInvalidExportError`,this.pluginName=e}},l=class extends Error{constructor(e,t,n){super(`Plugin type mismatch for "${e}": "${n}" !== "${t}"`),this.name=`PluginTypeMismatchError`,this.pluginName=e,this.expected=t,this.actual=n}},u=class extends Error{constructor(e){super(`Duplicate plugin "${e}"`),this.id=e}},d=class extends Error{constructor(e){super(`Plugin "${e}" not found`),this.name=`PluginNotFoundError`,this.id=e}},f=class extends Error{constructor(e,t){super(`Manifest not found for plugin "${e}" in "${t}"`),this.name=`ManifestNotFoundError`,this.pluginId=e,this.dir=t}},p=class extends Error{constructor(e){super(`Circular dependency detected: ${e.join(` -> `)}`),this.name=`CircularDependencyError`,this.cycle=e}},m=class extends Error{constructor(e,t){let n=t?`Dependency "${e}" not found (required by "${t}")`:`Dependency "${e}" not found`;super(n),this.name=`DependencyNotFoundError`,this.dependencyId=e,this.requestedBy=t}},h=class extends Error{constructor(e,t,n,r){let i=r?`Version mismatch: ${e}@${t} does not satisfy ${n} (required by "${r}")`:`Version mismatch: ${e}@${t} does not satisfy ${n}`;super(i),this.name=`VersionMismatchError`,this.packageId=e,this.actualVersion=t,this.requiredVersion=n,this.requestedBy=r}},g=class{constructor(){this.plugins=new Map}register(e,t,n){if(this.plugins.has(e))throw new u(e);this.plugins.set(e,{plugin:t,metadata:n})}unregister(e){let t=this.plugins.get(e);if(t)return this.plugins.delete(e),t.plugin}getPlugin(e){let t=this.plugins.get(e);if(!t)throw new d(e);return t.plugin.api}hasPlugin(e){return this.plugins.has(e)}getPluginInstance(e){return this.plugins.get(e)?.plugin}getPluginMetadata(e){return this.plugins.get(e)?.metadata}getPluginIds(){return[...this.plugins.keys()]}getPluginsByType(e){let t=[];for(let{metadata:n}of this.plugins.values())n.pluginType===e&&t.push(n);return t}getAllMetadata(){return[...this.plugins.values()].map(e=>e.metadata)}};function _(e){return e}async function v(e,i){let a=[];if(e.includes(`*`)||e.includes(`{`)||e.includes(`?`)){let o=await n(e.replace(/\\/g,`/`),{onlyDirectories:!0,absolute:!0});for(let e of o){let n=t.join(e,`plugin.json`);if(!await r.pathExists(n))continue;let o=await r.readJson(n);i&&o.pluginType!==i||a.push({...o,__dir:e})}}else{if(!await r.pathExists(e))return[];let n=t.join(e,`plugin.json`);if(await r.pathExists(n)){let o=await r.readJson(n);return(!i||o.pluginType===i)&&a.push({...o,__dir:t.resolve(e)}),a}let o=await r.readdir(e);for(let n of o){let o=t.join(e,n);if(!(await r.stat(o)).isDirectory())continue;let s=t.join(o,`plugin.json`);if(!await r.pathExists(s))continue;let c=await r.readJson(s);i&&c.pluginType!==i||a.push({...c,__dir:o})}}return a}function y(e){let t=new Map(e.map(e=>[e.id,e])),n=[],r=new Set,a=new Set;function o(e,s,c=[]){if(a.has(e)){if(s){let n=t.get(e);if(n&&!i.satisfies(n.version,s))throw new h(e,n.version,s,c[c.length-1])}return}if(r.has(e))throw new p([...c,e].slice(c.indexOf(e)));let l=t.get(e);if(!l)throw new m(e,c[c.length-1]);if(s&&!i.satisfies(l.version,s))throw new h(e,l.version,s,c[c.length-1]);if(r.add(e),l.dependencies)for(let t of l.dependencies)o(t.id,t.version,[...c,e]);r.delete(e),a.add(e),n.push(l)}for(let t of e)o(t.id);return n}const b=e(import.meta.url);function x(e){return typeof e==`object`&&!!e&&`id`in e&&`pluginType`in e&&`create`in e&&typeof e.create==`function`}async function S(e){let n,r;if(e.module)try{n=await import(`${a(t.resolve(e.__dir,e.module)).href}?t=${Date.now()}`)}catch(e){r=e}if(!n&&e.main)try{let r=t.resolve(e.__dir,e.main);delete b.cache[b.resolve(r)],n=b(r)}catch(e){r=e}if(!n)throw new s(e.id,r);let i=n.default??n;if(!x(i))throw new c(e.id);return i}function C(e){if(e.main){let n=t.resolve(e.__dir,e.main);try{delete b.cache[b.resolve(n)]}catch{}}}function w(e){return{id:e.id,name:e.name,pluginType:e.pluginType,version:e.version,description:e.description,dependencies:e.dependencies,dir:e.__dir}}var T=class{constructor(){this.manifests=new Map,this.watcher=null,this.watchPatterns=[],this.context=new g}async loadPlugins(e){let t=[];for(let n of e){let e=await v(n);t.push(...e)}let n=y(t);for(let e of n)await this.loadSinglePlugin(e)}async loadSinglePlugin(e){let t=await S(e),n=await Promise.resolve(t.create(this.context)),r=w(e);this.context.register(e.id,n,r),this.manifests.set(e.id,e),n.onLoad&&await Promise.resolve(n.onLoad())}async unloadSinglePlugin(e){let t=this.context.getPluginInstance(e);t?.onUnload&&await Promise.resolve(t.onUnload()),this.context.unregister(e),this.manifests.delete(e)}async reloadPlugin(e){let t=this.manifests.get(e);if(!t)throw new d(e);C(t),await this.unloadSinglePlugin(e);let[n]=await v(t.__dir);if(!n)throw new f(e,t.__dir);await this.loadSinglePlugin(n)}async unloadPlugin(e){if(!this.hasPlugin(e))throw new d(e);await this.unloadSinglePlugin(e)}async watch(e,t){this.watcher&&await this.stopWatching(),this.watchPatterns=e;let n=[];for(let[,e]of this.manifests)n.push(e.__dir);n.length!==0&&(this.watcher=o(n,{ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:100,pollInterval:50}}),this.watcher.on(`change`,async e=>{for(let[n,r]of this.manifests)if(e.startsWith(r.__dir)){try{await this.reloadPlugin(n),t?.(n,`reload`)}catch(e){t?.(n,`error`,e instanceof Error?e:Error(String(e)))}break}}))}async stopWatching(){this.watcher&&=(await this.watcher.close(),null)}getPlugin(e){return this.context.getPlugin(e)}hasPlugin(e){return this.context.hasPlugin(e)}getPluginIds(){return this.context.getPluginIds()}getPluginMetadata(e){return this.context.getPluginMetadata(e)}getPluginsByType(e){return this.context.getPluginsByType(e)}getAllMetadata(){return this.context.getAllMetadata()}getContext(){return this.context}async shutdown(){await this.stopWatching();let e=[...this.manifests.keys()].reverse();for(let t of e)await this.unloadSinglePlugin(t)}};export{p as CircularDependencyError,g as DefaultPluginContext,m as DependencyNotFoundError,u as DuplicatePluginError,f as ManifestNotFoundError,c as PluginInvalidExportError,s as PluginLoadError,T as PluginManager,d as PluginNotFoundError,l as PluginTypeMismatchError,h as VersionMismatchError,C as clearPluginCache,_ as definePlugin,v as findPlugins,S as loadPlugin,y as topologicalSort}; | ||
| //# sourceMappingURL=index.mjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.mjs","names":[],"sources":["../src/define-plugin.ts","../src/find-plugins.ts","../src/types.ts","../src/load-plugin.ts","../src/load-all-plugins.ts"],"sourcesContent":["import {LibriaPlugin} from \"./types\";\r\n\r\nexport function definePlugin<T>(\r\n pluginType: string,\r\n name: string,\r\n api: T\r\n): LibriaPlugin<T> {\r\n return {\r\n pluginType: pluginType,\r\n name,\r\n api\r\n };\r\n}","import {PluginManifest} from \"./types\";\r\nimport fs from \"fs-extra\";\r\nimport fg from \"fast-glob\";\r\nimport path from \"path\";\r\n\r\nexport async function findPlugins(\r\n pattern: string,\r\n pluginType?: string\r\n): Promise<PluginManifest[]> {\r\n const manifests: PluginManifest[] = [];\r\n\r\n // Check if pattern is a glob pattern or a simple path\r\n const isGlob = pattern.includes('*') || pattern.includes('{') || pattern.includes('?');\r\n\r\n if (isGlob) {\r\n // Normalize path separators for fast-glob (it expects forward slashes)\r\n const normalizedPattern = pattern.replace(/\\\\/g, '/');\r\n\r\n // Use fast-glob to find all matching directories\r\n const pluginDirs = await fg(normalizedPattern, {\r\n onlyDirectories: true,\r\n absolute: true,\r\n });\r\n\r\n for (const dir of pluginDirs) {\r\n const manifestPath = path.join(dir, 'plugin.json');\r\n if (!(await fs.pathExists(manifestPath))) continue;\r\n\r\n const raw = await fs.readJson(manifestPath);\r\n\r\n if (pluginType && raw.pluginType !== pluginType) continue;\r\n\r\n manifests.push({\r\n ...raw,\r\n __dir: dir\r\n });\r\n }\r\n } else {\r\n // Original behavior for simple directory paths\r\n if (!(await fs.pathExists(pattern))) return [];\r\n\r\n const entries = await fs.readdir(pattern);\r\n\r\n for (const entry of entries) {\r\n const fullPath = path.join(pattern, entry);\r\n const stat = await fs.stat(fullPath);\r\n\r\n if (!stat.isDirectory()) continue;\r\n\r\n const manifestPath = path.join(fullPath, 'plugin.json');\r\n if (!(await fs.pathExists(manifestPath))) continue;\r\n\r\n const raw = await fs.readJson(manifestPath);\r\n\r\n if (pluginType && raw.pluginType !== pluginType) continue;\r\n\r\n manifests.push({\r\n ...raw,\r\n __dir: fullPath\r\n });\r\n }\r\n }\r\n\r\n return manifests;\r\n}","export interface LibriaPlugin<T = unknown> {\r\n readonly pluginType: string;\r\n readonly name: string;\r\n readonly api: T;\r\n}\r\n\r\nexport interface PluginManifest {\r\n readonly name: string;\r\n readonly pluginType: string;\r\n\r\n readonly main?: string; // cjs\r\n readonly module?: string; // esm\r\n readonly types?: string;\r\n\r\n // resolved absolute path to the plugin folder\r\n readonly __dir: string;\r\n}\r\n\r\n// Plugin Errors\r\n\r\nexport class PluginLoadError extends Error {\r\n readonly pluginName: string;\r\n readonly cause: unknown;\r\n\r\n constructor(pluginName: string, cause: unknown) {\r\n super(`Failed to load plugin \"${pluginName}\".\\n${String(cause)}`);\r\n this.name = 'PluginLoadError';\r\n this.pluginName = pluginName;\r\n this.cause = cause;\r\n }\r\n}\r\n\r\nexport class PluginInvalidExportError extends Error {\r\n readonly pluginName: string;\r\n\r\n constructor(pluginName: string) {\r\n super(`Plugin \"${pluginName}\" has invalid export`);\r\n this.name = 'PluginInvalidExportError';\r\n this.pluginName = pluginName;\r\n }\r\n}\r\n\r\nexport class PluginTypeMismatchError extends Error {\r\n readonly pluginName: string;\r\n readonly expected: string;\r\n readonly actual: string;\r\n\r\n constructor(pluginName: string, expected: string, actual: string) {\r\n super(\r\n `Plugin type mismatch for \"${pluginName}\": ` +\r\n `\"${actual}\" !== \"${expected}\"`\r\n );\r\n this.name = 'PluginTypeMismatchError';\r\n this.pluginName = pluginName;\r\n this.expected = expected;\r\n this.actual = actual;\r\n }\r\n}","import {\r\n LibriaPlugin,\r\n PluginManifest,\r\n PluginLoadError,\r\n PluginInvalidExportError,\r\n PluginTypeMismatchError\r\n} from \"./types\";\r\nimport path from \"path\";\r\nimport {pathToFileURL} from \"url\";\r\nimport {createRequire} from \"node:module\";\r\n\r\nconst require = createRequire(import.meta.url);\r\n\r\nexport async function loadPlugin<T = unknown>(\r\n manifest: PluginManifest\r\n): Promise<LibriaPlugin<T>> {\r\n let mod: any;\r\n let lastError: unknown;\r\n\r\n // 1 Try ESM first\r\n if (manifest.module) {\r\n try {\r\n const esmPath = path.resolve(manifest.__dir, manifest.module);\r\n mod = await import(pathToFileURL(esmPath).href);\r\n } catch (err) {\r\n lastError = err;\r\n }\r\n }\r\n\r\n // 2 Fallback to CJS\r\n if (!mod && manifest.main) {\r\n try {\r\n const cjsPath = path.resolve(manifest.__dir, manifest.main);\r\n mod = require(cjsPath);\r\n } catch (err) {\r\n lastError = err;\r\n }\r\n }\r\n\r\n if (!mod) {\r\n throw new PluginLoadError(manifest.name, lastError);\r\n }\r\n\r\n // 3️⃣ Normalize export\r\n const plugin = (mod.default ?? mod) as LibriaPlugin<T>;\r\n\r\n if (!plugin || typeof plugin !== 'object') {\r\n throw new PluginInvalidExportError(manifest.name);\r\n }\r\n\r\n if (plugin.pluginType !== manifest.pluginType) {\r\n throw new PluginTypeMismatchError(\r\n manifest.name,\r\n manifest.pluginType,\r\n plugin.pluginType\r\n );\r\n }\r\n\r\n return plugin;\r\n}","import {LibriaPlugin} from \"./types\";\r\nimport {findPlugins} from \"./find-plugins\";\r\nimport {loadPlugin} from \"./load-plugin\";\r\n\r\nexport async function loadAllPlugins<T = unknown>(\r\n pattern: string,\r\n pluginType?: string\r\n): Promise<LibriaPlugin<T>[]> {\r\n const manifests = await findPlugins(pattern, pluginType);\r\n const plugins: LibriaPlugin<T>[] = [];\r\n\r\n for (const manifest of manifests) {\r\n const plugin = await loadPlugin<T>(manifest);\r\n plugins.push(plugin);\r\n }\r\n\r\n return plugins;\r\n}\r\n"],"mappings":"qJAEA,SAAgB,EACZ,EACA,EACA,EACe,CACf,MAAO,CACS,aACZ,OACA,MACH,CCNL,eAAsB,EAClB,EACA,EACyB,CACzB,IAAM,EAA8B,EAAE,CAKtC,GAFe,EAAQ,SAAS,IAAI,EAAI,EAAQ,SAAS,IAAI,EAAI,EAAQ,SAAS,IAAI,CAE1E,CAKR,IAAM,EAAa,MAAM,EAHC,EAAQ,QAAQ,MAAO,IAAI,CAGN,CAC3C,gBAAiB,GACjB,SAAU,GACb,CAAC,CAEF,IAAK,IAAM,KAAO,EAAY,CAC1B,IAAM,EAAe,EAAK,KAAK,EAAK,cAAc,CAClD,GAAI,CAAE,MAAM,EAAG,WAAW,EAAa,CAAG,SAE1C,IAAM,EAAM,MAAM,EAAG,SAAS,EAAa,CAEvC,GAAc,EAAI,aAAe,GAErC,EAAU,KAAK,CACX,GAAG,EACH,MAAO,EACV,CAAC,MAEH,CAEH,GAAI,CAAE,MAAM,EAAG,WAAW,EAAQ,CAAG,MAAO,EAAE,CAE9C,IAAM,EAAU,MAAM,EAAG,QAAQ,EAAQ,CAEzC,IAAK,IAAM,KAAS,EAAS,CACzB,IAAM,EAAW,EAAK,KAAK,EAAS,EAAM,CAG1C,GAAI,EAFS,MAAM,EAAG,KAAK,EAAS,EAE1B,aAAa,CAAE,SAEzB,IAAM,EAAe,EAAK,KAAK,EAAU,cAAc,CACvD,GAAI,CAAE,MAAM,EAAG,WAAW,EAAa,CAAG,SAE1C,IAAM,EAAM,MAAM,EAAG,SAAS,EAAa,CAEvC,GAAc,EAAI,aAAe,GAErC,EAAU,KAAK,CACX,GAAG,EACH,MAAO,EACV,CAAC,EAIV,OAAO,EC3CX,IAAa,EAAb,cAAqC,KAAM,CAIvC,YAAY,EAAoB,EAAgB,CAC5C,MAAM,0BAA0B,EAAW,MAAM,OAAO,EAAM,GAAG,CACjE,KAAK,KAAO,kBACZ,KAAK,WAAa,EAClB,KAAK,MAAQ,IAIR,EAAb,cAA8C,KAAM,CAGhD,YAAY,EAAoB,CAC5B,MAAM,WAAW,EAAW,sBAAsB,CAClD,KAAK,KAAO,2BACZ,KAAK,WAAa,IAIb,EAAb,cAA6C,KAAM,CAK/C,YAAY,EAAoB,EAAkB,EAAgB,CAC9D,MACI,6BAA6B,EAAW,MACpC,EAAO,SAAS,EAAS,GAChC,CACD,KAAK,KAAO,0BACZ,KAAK,WAAa,EAClB,KAAK,SAAW,EAChB,KAAK,OAAS,IC5CtB,MAAM,EAAU,EAAc,OAAO,KAAK,IAAI,CAE9C,eAAsB,EAClB,EACwB,CACxB,IAAI,EACA,EAGJ,GAAI,EAAS,OACT,GAAI,CAEA,EAAM,MAAM,OAAO,EADH,EAAK,QAAQ,EAAS,MAAO,EAAS,OAAO,CACpB,CAAC,YACrC,EAAK,CACV,EAAY,EAKpB,GAAI,CAAC,GAAO,EAAS,KACjB,GAAI,CAEA,EAAM,EADU,EAAK,QAAQ,EAAS,MAAO,EAAS,KAAK,CACrC,OACjB,EAAK,CACV,EAAY,EAIpB,GAAI,CAAC,EACD,MAAM,IAAI,EAAgB,EAAS,KAAM,EAAU,CAIvD,IAAM,EAAU,EAAI,SAAW,EAE/B,GAAI,CAAC,GAAU,OAAO,GAAW,SAC7B,MAAM,IAAI,EAAyB,EAAS,KAAK,CAGrD,GAAI,EAAO,aAAe,EAAS,WAC/B,MAAM,IAAI,EACN,EAAS,KACT,EAAS,WACT,EAAO,WACV,CAGL,OAAO,ECtDX,eAAsB,EAClB,EACA,EAC0B,CAC1B,IAAM,EAAY,MAAM,EAAY,EAAS,EAAW,CAClD,EAA6B,EAAE,CAErC,IAAK,IAAM,KAAY,EAAW,CAC9B,IAAM,EAAS,MAAM,EAAc,EAAS,CAC5C,EAAQ,KAAK,EAAO,CAGxB,OAAO"} | ||
| {"version":3,"file":"index.mjs","names":[],"sources":["../src/types.ts","../src/default-plugin-context.ts","../src/define-plugin.ts","../src/find-plugins.ts","../src/helpers/topological-sort.ts","../src/load-plugin.ts","../src/plugin-manager.ts"],"sourcesContent":["// Lifecycle hooks that plugins can implement\nexport interface PluginLifecycle {\n /** Called after the plugin is registered and ready to use */\n onLoad?(): void | Promise<void>;\n /** Called before the plugin is unloaded (for hot-reload or shutdown) */\n onUnload?(): void | Promise<void>;\n}\n\nexport interface LibriaPlugin<T = unknown> extends PluginLifecycle {\n api: T;\n}\n\nexport interface PluginFactory<T = unknown> {\n readonly id: string;\n readonly pluginType: string;\n readonly name?: string;\n\n /** Create the plugin instance. Can be sync or async. */\n create<C extends PluginContext>(ctx: C): LibriaPlugin<T> | Promise<LibriaPlugin<T>>;\n}\n\nexport interface PluginContext {\n getPlugin<T = unknown>(id: string): T;\n hasPlugin(id: string): boolean;\n}\n\n/** Metadata stored for each loaded plugin */\nexport interface PluginMetadata {\n readonly id: string;\n readonly name?: string;\n readonly pluginType: string;\n readonly version: string;\n readonly description?: string;\n readonly dependencies?: { id: string; version: string }[];\n /** Absolute path to the plugin directory */\n readonly dir: string;\n}\n\nexport interface PluginManifest {\n readonly id: string;\n readonly name?: string;\n readonly pluginType: string;\n readonly version: string; // Semver\n readonly description?: string;\n\n readonly main?: string; // cjs\n readonly module?: string; // esm\n readonly types?: string;\n\n readonly dependencies?: { id: string; version: string }[];\n // resolved absolute path to the plugin folder\n readonly __dir: string;\n}\n\n// Plugin Errors\n\nexport class PluginLoadError extends Error {\n public readonly pluginName: string;\n public readonly cause: unknown;\n\n constructor(pluginName: string, cause: unknown) {\n super(`Failed to load plugin \"${pluginName}\".\\n${String(cause)}`);\n this.name = 'PluginLoadError';\n this.pluginName = pluginName;\n this.cause = cause;\n }\n}\n\nexport class PluginInvalidExportError extends Error {\n public readonly pluginName: string;\n\n constructor(pluginName: string) {\n super(`Plugin \"${pluginName}\" has invalid export`);\n this.name = 'PluginInvalidExportError';\n this.pluginName = pluginName;\n }\n}\n\nexport class PluginTypeMismatchError extends Error {\n public readonly pluginName: string;\n public readonly expected: string;\n public readonly actual: string;\n\n constructor(pluginName: string, expected: string, actual: string) {\n super(`Plugin type mismatch for \"${pluginName}\": ` + `\"${actual}\" !== \"${expected}\"`);\n this.name = 'PluginTypeMismatchError';\n this.pluginName = pluginName;\n this.expected = expected;\n this.actual = actual;\n }\n}\n\nexport class DuplicatePluginError extends Error {\n public readonly id: string;\n\n constructor(id: string) {\n super(`Duplicate plugin \"${id}\"`);\n\n this.id = id;\n }\n}\n\nexport class PluginNotFoundError extends Error {\n public readonly id: string;\n\n constructor(id: string) {\n super(`Plugin \"${id}\" not found`);\n this.name = 'PluginNotFoundError';\n this.id = id;\n }\n}\n\nexport class ManifestNotFoundError extends Error {\n public readonly pluginId: string;\n public readonly dir: string;\n\n constructor(pluginId: string, dir: string) {\n super(`Manifest not found for plugin \"${pluginId}\" in \"${dir}\"`);\n this.name = 'ManifestNotFoundError';\n this.pluginId = pluginId;\n this.dir = dir;\n }\n}\n\n// Dependency Resolution Errors\n\nexport class CircularDependencyError extends Error {\n public readonly cycle: string[];\n\n constructor(cycle: string[]) {\n super(`Circular dependency detected: ${cycle.join(' -> ')}`);\n this.name = 'CircularDependencyError';\n this.cycle = cycle;\n }\n}\n\nexport class DependencyNotFoundError extends Error {\n public readonly dependencyId: string;\n public readonly requestedBy?: string;\n\n constructor(dependencyId: string, requestedBy?: string) {\n const message = requestedBy\n ? `Dependency \"${dependencyId}\" not found (required by \"${requestedBy}\")`\n : `Dependency \"${dependencyId}\" not found`;\n super(message);\n this.name = 'DependencyNotFoundError';\n this.dependencyId = dependencyId;\n this.requestedBy = requestedBy;\n }\n}\n\nexport class VersionMismatchError extends Error {\n public readonly packageId: string;\n public readonly actualVersion: string;\n public readonly requiredVersion: string;\n public readonly requestedBy?: string;\n\n constructor(\n packageId: string,\n actualVersion: string,\n requiredVersion: string,\n requestedBy?: string\n ) {\n const message = requestedBy\n ? `Version mismatch: ${packageId}@${actualVersion} does not satisfy ${requiredVersion} (required by \"${requestedBy}\")`\n : `Version mismatch: ${packageId}@${actualVersion} does not satisfy ${requiredVersion}`;\n super(message);\n this.name = 'VersionMismatchError';\n this.packageId = packageId;\n this.actualVersion = actualVersion;\n this.requiredVersion = requiredVersion;\n this.requestedBy = requestedBy;\n }\n}\n","import {\n DuplicatePluginError,\n LibriaPlugin,\n PluginContext,\n PluginMetadata,\n PluginNotFoundError,\n} from './types';\n\ninterface RegisteredPlugin {\n plugin: LibriaPlugin;\n metadata: PluginMetadata;\n}\n\nexport class DefaultPluginContext implements PluginContext {\n private plugins: Map<string, RegisteredPlugin> = new Map();\n\n public register(id: string, plugin: LibriaPlugin, metadata: PluginMetadata): void {\n if (this.plugins.has(id)) {\n throw new DuplicatePluginError(id);\n }\n\n this.plugins.set(id, { plugin, metadata });\n }\n\n public unregister(id: string): LibriaPlugin | undefined {\n const registered = this.plugins.get(id);\n if (registered) {\n this.plugins.delete(id);\n return registered.plugin;\n }\n return undefined;\n }\n\n public getPlugin<T = unknown>(id: string): T {\n const registered = this.plugins.get(id);\n if (!registered) {\n throw new PluginNotFoundError(id);\n }\n\n return registered.plugin.api as T;\n }\n\n public hasPlugin(id: string): boolean {\n return this.plugins.has(id);\n }\n\n /** Get the raw LibriaPlugin instance (includes lifecycle hooks) */\n public getPluginInstance(id: string): LibriaPlugin | undefined {\n return this.plugins.get(id)?.plugin;\n }\n\n /** Get metadata for a specific plugin */\n public getPluginMetadata(id: string): PluginMetadata | undefined {\n return this.plugins.get(id)?.metadata;\n }\n\n /** Get all plugin IDs */\n public getPluginIds(): string[] {\n return [...this.plugins.keys()];\n }\n\n /** Get all plugins of a specific type */\n public getPluginsByType(pluginType: string): PluginMetadata[] {\n const result: PluginMetadata[] = [];\n for (const { metadata } of this.plugins.values()) {\n if (metadata.pluginType === pluginType) {\n result.push(metadata);\n }\n }\n return result;\n }\n\n /** Get all loaded plugin metadata */\n public getAllMetadata(): PluginMetadata[] {\n return [...this.plugins.values()].map(r => r.metadata);\n }\n}\n","import { PluginFactory } from './types';\n\nexport function definePlugin<T>(factory: PluginFactory<T>): PluginFactory<T> {\n return factory;\n}\n","import path from 'path';\n\nimport fg from 'fast-glob';\nimport fs from 'fs-extra';\n\nimport { PluginManifest } from './types';\n\nexport async function findPlugins(pattern: string, pluginType?: string): Promise<PluginManifest[]> {\n const manifests: PluginManifest[] = [];\n\n // Check if pattern is a glob pattern or a simple path\n const isGlob = pattern.includes('*') || pattern.includes('{') || pattern.includes('?');\n\n if (isGlob) {\n // Normalize path separators for fast-glob (it expects forward slashes)\n const normalizedPattern = pattern.replace(/\\\\/g, '/');\n\n // Use fast-glob to find all matching directories\n const pluginDirs = await fg(normalizedPattern, {\n onlyDirectories: true,\n absolute: true,\n });\n\n for (const dir of pluginDirs) {\n const manifestPath = path.join(dir, 'plugin.json');\n if (!(await fs.pathExists(manifestPath))) continue;\n\n const raw = await fs.readJson(manifestPath);\n\n if (pluginType && raw.pluginType !== pluginType) continue;\n\n manifests.push({\n ...raw,\n __dir: dir,\n });\n }\n } else {\n // Simple directory path - check if it exists\n if (!(await fs.pathExists(pattern))) return [];\n\n // First, check if the directory itself is a plugin (has plugin.json)\n const directManifestPath = path.join(pattern, 'plugin.json');\n if (await fs.pathExists(directManifestPath)) {\n const raw = await fs.readJson(directManifestPath);\n if (!pluginType || raw.pluginType === pluginType) {\n manifests.push({\n ...raw,\n __dir: path.resolve(pattern),\n });\n }\n return manifests;\n }\n\n // Otherwise, scan subdirectories for plugins\n const entries = await fs.readdir(pattern);\n\n for (const entry of entries) {\n const fullPath = path.join(pattern, entry);\n const stat = await fs.stat(fullPath);\n\n if (!stat.isDirectory()) continue;\n\n const manifestPath = path.join(fullPath, 'plugin.json');\n if (!(await fs.pathExists(manifestPath))) continue;\n\n const raw = await fs.readJson(manifestPath);\n\n if (pluginType && raw.pluginType !== pluginType) continue;\n\n manifests.push({\n ...raw,\n __dir: fullPath,\n });\n }\n }\n\n return manifests;\n}\n","import semver from 'semver';\n\nimport {\n CircularDependencyError,\n DependencyNotFoundError,\n PluginManifest,\n VersionMismatchError,\n} from '../types';\n\nexport function topologicalSort(packages: PluginManifest[]): PluginManifest[] {\n const packageMap = new Map(packages.map(pkg => [pkg.id, pkg]));\n const sorted: PluginManifest[] = [];\n const visiting = new Set<string>();\n const visited = new Set<string>();\n\n function visit(id: string, requiredVersion?: string, path: string[] = []): void {\n if (visited.has(id)) {\n // Still need to validate version even if already visited\n if (requiredVersion) {\n const pkg = packageMap.get(id);\n if (pkg && !semver.satisfies(pkg.version, requiredVersion)) {\n throw new VersionMismatchError(\n id,\n pkg.version,\n requiredVersion,\n path[path.length - 1]\n );\n }\n }\n return;\n }\n\n if (visiting.has(id)) {\n // Circular dependency detected\n const cycle = [...path, id].slice(path.indexOf(id));\n throw new CircularDependencyError(cycle);\n }\n\n const pkg = packageMap.get(id);\n if (!pkg) {\n throw new DependencyNotFoundError(id, path[path.length - 1]);\n }\n\n // Validate version requirement\n if (requiredVersion && !semver.satisfies(pkg.version, requiredVersion)) {\n throw new VersionMismatchError(id, pkg.version, requiredVersion, path[path.length - 1]);\n }\n\n visiting.add(id);\n\n // Visit all dependencies first\n if (pkg.dependencies) {\n for (const dep of pkg.dependencies) {\n visit(dep.id, dep.version, [...path, id]);\n }\n }\n\n visiting.delete(id);\n visited.add(id);\n sorted.push(pkg);\n }\n\n // Visit all packages\n for (const pkg of packages) {\n visit(pkg.id);\n }\n\n return sorted;\n}\n","import { createRequire } from 'node:module';\nimport path from 'path';\nimport { pathToFileURL } from 'url';\n\nimport { PluginFactory, PluginInvalidExportError, PluginLoadError, PluginManifest } from './types';\n\nconst require = createRequire(import.meta.url);\n\nfunction isPluginFactory(obj: unknown): obj is PluginFactory {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'id' in obj &&\n 'pluginType' in obj &&\n 'create' in obj &&\n typeof (obj as PluginFactory).create === 'function'\n );\n}\n\nexport async function loadPlugin<T = unknown>(manifest: PluginManifest): Promise<PluginFactory<T>> {\n let mod: unknown;\n let lastError: unknown;\n\n // 1. Try ESM first\n if (manifest.module) {\n try {\n const esmPath = path.resolve(manifest.__dir, manifest.module);\n const fileUrl = pathToFileURL(esmPath).href;\n // Add timestamp to bust ESM cache for hot-reload\n mod = await import(`${fileUrl}?t=${Date.now()}`);\n } catch (err) {\n lastError = err;\n }\n }\n\n // 2. Fallback to CJS\n if (!mod && manifest.main) {\n try {\n const cjsPath = path.resolve(manifest.__dir, manifest.main);\n // Clear CJS cache before requiring\n delete require.cache[require.resolve(cjsPath)];\n mod = require(cjsPath);\n } catch (err) {\n lastError = err;\n }\n }\n\n if (!mod) {\n throw new PluginLoadError(manifest.id, lastError);\n }\n\n // 3. Normalize export\n const moduleWithDefault = mod as { default?: unknown };\n const factory = (moduleWithDefault.default ?? mod) as PluginFactory<T>;\n\n if (!isPluginFactory(factory)) {\n throw new PluginInvalidExportError(manifest.id);\n }\n\n return factory;\n}\n\n/**\n * Clear the module cache for a plugin (used for hot-reload)\n */\nexport function clearPluginCache(manifest: PluginManifest): void {\n // Clear CJS cache\n if (manifest.main) {\n const cjsPath = path.resolve(manifest.__dir, manifest.main);\n try {\n delete require.cache[require.resolve(cjsPath)];\n } catch {\n // Ignore if not in cache\n }\n }\n\n // ESM cache is handled by timestamp query param in loadPlugin\n}\n","import { FSWatcher, watch } from 'chokidar';\n\nimport { DefaultPluginContext } from './default-plugin-context';\nimport { findPlugins } from './find-plugins';\nimport { topologicalSort } from './helpers';\nimport { loadPlugin, clearPluginCache } from './load-plugin';\nimport {\n ManifestNotFoundError,\n PluginManifest,\n PluginMetadata,\n PluginNotFoundError,\n} from './types';\n\nfunction manifestToMetadata(manifest: PluginManifest): PluginMetadata {\n return {\n id: manifest.id,\n name: manifest.name,\n pluginType: manifest.pluginType,\n version: manifest.version,\n description: manifest.description,\n dependencies: manifest.dependencies,\n dir: manifest.__dir,\n };\n}\n\nexport class PluginManager {\n private readonly context: DefaultPluginContext;\n private manifests: Map<string, PluginManifest> = new Map();\n private watcher: FSWatcher | null = null;\n private watchPatterns: string[] = [];\n\n public constructor() {\n this.context = new DefaultPluginContext();\n }\n\n public async loadPlugins(patterns: string[]): Promise<void> {\n const allManifests: PluginManifest[] = [];\n\n for (const pattern of patterns) {\n const manifests = await findPlugins(pattern);\n allManifests.push(...manifests);\n }\n\n // Sort the manifests so that the dependencies are loaded before their dependents\n const sortedManifests = topologicalSort(allManifests);\n\n for (const manifest of sortedManifests) {\n await this.loadSinglePlugin(manifest);\n }\n }\n\n private async loadSinglePlugin(manifest: PluginManifest): Promise<void> {\n const factory = await loadPlugin(manifest);\n const plugin = await Promise.resolve(factory.create(this.context));\n const metadata = manifestToMetadata(manifest);\n\n this.context.register(manifest.id, plugin, metadata);\n this.manifests.set(manifest.id, manifest);\n\n // Call onLoad lifecycle hook\n if (plugin.onLoad) {\n await Promise.resolve(plugin.onLoad());\n }\n }\n\n private async unloadSinglePlugin(id: string): Promise<void> {\n const plugin = this.context.getPluginInstance(id);\n\n // Call onUnload lifecycle hook before removing\n if (plugin?.onUnload) {\n await Promise.resolve(plugin.onUnload());\n }\n\n this.context.unregister(id);\n this.manifests.delete(id);\n }\n\n /**\n * Reload a specific plugin by ID (useful for hot-reload)\n */\n public async reloadPlugin(id: string): Promise<void> {\n const manifest = this.manifests.get(id);\n if (!manifest) {\n throw new PluginNotFoundError(id);\n }\n\n // Clear the module cache for this plugin\n clearPluginCache(manifest);\n\n // Unload the old plugin\n await this.unloadSinglePlugin(id);\n\n // Re-read the manifest from the plugin directory\n const [newManifest] = await findPlugins(manifest.__dir);\n if (!newManifest) {\n throw new ManifestNotFoundError(id, manifest.__dir);\n }\n\n // Load the new version\n await this.loadSinglePlugin(newManifest);\n }\n\n /**\n * Unload a plugin by ID\n */\n public async unloadPlugin(id: string): Promise<void> {\n if (!this.hasPlugin(id)) {\n throw new PluginNotFoundError(id);\n }\n await this.unloadSinglePlugin(id);\n }\n\n /**\n * Start watching for plugin file changes (hot-reload)\n * @param patterns Glob patterns to watch (same as loadPlugins)\n * @param onChange Callback when a plugin is reloaded\n */\n public async watch(\n patterns: string[],\n onChange?: (id: string, event: 'reload' | 'error', error?: Error) => void\n ): Promise<void> {\n if (this.watcher) {\n await this.stopWatching();\n }\n\n this.watchPatterns = patterns;\n\n // Get all plugin directories to watch\n const dirsToWatch: string[] = [];\n for (const [, manifest] of this.manifests) {\n dirsToWatch.push(manifest.__dir);\n }\n\n if (dirsToWatch.length === 0) {\n return;\n }\n\n this.watcher = watch(dirsToWatch, {\n ignoreInitial: true,\n awaitWriteFinish: {\n stabilityThreshold: 100,\n pollInterval: 50,\n },\n });\n\n this.watcher.on('change', async (filePath: string) => {\n // Find which plugin this file belongs to\n for (const [id, manifest] of this.manifests) {\n if (filePath.startsWith(manifest.__dir)) {\n try {\n await this.reloadPlugin(id);\n onChange?.(id, 'reload');\n } catch (err) {\n onChange?.(\n id,\n 'error',\n err instanceof Error ? err : new Error(String(err))\n );\n }\n break;\n }\n }\n });\n }\n\n /**\n * Stop watching for file changes\n */\n public async stopWatching(): Promise<void> {\n if (this.watcher) {\n await this.watcher.close();\n this.watcher = null;\n }\n }\n\n /**\n * Get a plugin's API by its id\n */\n public getPlugin<T = unknown>(id: string): T {\n return this.context.getPlugin<T>(id);\n }\n\n /**\n * Check if a plugin is loaded\n */\n public hasPlugin(id: string): boolean {\n return this.context.hasPlugin(id);\n }\n\n /**\n * Get all loaded plugin IDs\n */\n public getPluginIds(): string[] {\n return this.context.getPluginIds();\n }\n\n /**\n * Get metadata for a specific plugin\n */\n public getPluginMetadata(id: string): PluginMetadata | undefined {\n return this.context.getPluginMetadata(id);\n }\n\n /**\n * Get all plugins of a specific type\n */\n public getPluginsByType(pluginType: string): PluginMetadata[] {\n return this.context.getPluginsByType(pluginType);\n }\n\n /**\n * Get all loaded plugin metadata\n */\n public getAllMetadata(): PluginMetadata[] {\n return this.context.getAllMetadata();\n }\n\n /**\n * Get the plugin context (for advanced use cases)\n */\n public getContext(): DefaultPluginContext {\n return this.context;\n }\n\n /**\n * Shutdown the plugin manager - unloads all plugins and stops watching\n */\n public async shutdown(): Promise<void> {\n await this.stopWatching();\n\n // Unload plugins in reverse order (dependents before dependencies)\n const ids = [...this.manifests.keys()].reverse();\n for (const id of ids) {\n await this.unloadSinglePlugin(id);\n }\n }\n}\n"],"mappings":"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"} |
+4
-2
| { | ||
| "name": "@libria/plugin-loader", | ||
| "version": "0.1.1", | ||
| "version": "2.0.0-alpha", | ||
| "description": "Simple plugin loader for Node.js applications", | ||
@@ -37,5 +37,7 @@ "main": "dist/index.cjs", | ||
| "dependencies": { | ||
| "chokidar": "^5.0.0", | ||
| "fast-glob": "^3.3.3", | ||
| "fs-extra": "^11.3.3" | ||
| "fs-extra": "^11.3.3", | ||
| "semver": "^7.7.3" | ||
| } | ||
| } |
+305
-136
| # @libria/plugin-loader | ||
| A simple, type-safe plugin loader for Node.js applications. Supports both ESM and CommonJS plugins with glob pattern discovery. | ||
| A TypeScript-first plugin system for Node.js applications with dependency resolution, lifecycle hooks, and hot-reloading. | ||
| ## Features | ||
| - **Dependency Resolution** - Plugins can depend on other plugins with semver version requirements | ||
| - **Circular Dependency Detection** - Throws clear errors when circular dependencies are found | ||
| - **Async Plugin Initialization** - Factories can be async for loading configs, connecting to databases, etc. | ||
| - **Lifecycle Hooks** - `onLoad` and `onUnload` hooks for setup and cleanup | ||
| - **Hot-Reloading** - Watch for file changes and reload plugins on the fly | ||
| - **Plugin Queries** - Find plugins by type, get metadata, list all loaded plugins | ||
| - **TypeScript First** - Full type safety with generics for plugin APIs | ||
| - **ESM & CJS Support** - Load plugins in either module format | ||
| ## Installation | ||
@@ -13,217 +24,375 @@ | ||
| ### 1. Create a Plugin | ||
| ### 1. Define a Plugin | ||
| Create a `plugin.json` manifest in your plugin directory: | ||
| Create a plugin with `definePlugin`: | ||
| ```typescript | ||
| // plugins/greeter/src/index.ts | ||
| import { definePlugin } from '@libria/plugin-loader'; | ||
| interface GreeterAPI { | ||
| greet(name: string): string; | ||
| } | ||
| export default definePlugin<GreeterAPI>({ | ||
| id: 'greeter', | ||
| pluginType: 'util', | ||
| create(ctx) { | ||
| return { | ||
| api: { | ||
| greet(name) { | ||
| return `Hello, ${name}!`; | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| }); | ||
| ``` | ||
| ### 2. Create a Plugin Manifest | ||
| Each plugin needs a `plugin.json` in its directory: | ||
| ```json | ||
| { | ||
| "name": "my-plugin", | ||
| "pluginType": "greeting", | ||
| "module": "./dist/index.mjs" | ||
| "id": "greeter", | ||
| "name": "Greeter Plugin", | ||
| "pluginType": "util", | ||
| "version": "1.0.0", | ||
| "module": "./dist/index.mjs" | ||
| } | ||
| ``` | ||
| Create the plugin module: | ||
| ### 3. Load and Use Plugins | ||
| ```typescript | ||
| // src/index.ts | ||
| import { definePlugin } from '@libria/plugin-loader'; | ||
| import { PluginManager } from '@libria/plugin-loader'; | ||
| export default definePlugin('greeting', { | ||
| sayHello(name: string) { | ||
| return `Hello, ${name}!`; | ||
| } | ||
| }); | ||
| const manager = new PluginManager(); | ||
| // Load all plugins from a directory | ||
| await manager.loadPlugins(['./plugins/*']); | ||
| // Use a plugin | ||
| const greeter = manager.getPlugin<GreeterAPI>('greeter'); | ||
| console.log(greeter.greet('World')); // "Hello, World!" | ||
| ``` | ||
| ### 2. Load Plugins | ||
| ## Plugin Manifest | ||
| ```typescript | ||
| import { findPlugins, loadPlugin, loadAllPlugins } from '@libria/plugin-loader'; | ||
| The `plugin.json` file defines your plugin's metadata: | ||
| // Load all plugins from a directory | ||
| const plugins = await loadAllPlugins('./plugins', 'greeting'); | ||
| for (const plugin of plugins) { | ||
| console.log(plugin.api.sayHello('World')); | ||
| ```json | ||
| { | ||
| "id": "my-plugin", | ||
| "name": "My Plugin", | ||
| "pluginType": "feature", | ||
| "version": "1.0.0", | ||
| "description": "Optional description", | ||
| "module": "./dist/index.mjs", | ||
| "main": "./dist/index.cjs", | ||
| "dependencies": [ | ||
| { "id": "other-plugin", "version": "^1.0.0" } | ||
| ] | ||
| } | ||
| ``` | ||
| ## API | ||
| | Field | Required | Description | | ||
| |-------|----------|-------------| | ||
| | `id` | Yes | Unique identifier for the plugin | | ||
| | `pluginType` | Yes | Category/type of the plugin (for queries) | | ||
| | `version` | Yes | Semver version string | | ||
| | `name` | No | Human-readable name | | ||
| | `description` | No | Plugin description | | ||
| | `module` | No* | Path to ESM entry point | | ||
| | `main` | No* | Path to CJS entry point | | ||
| | `dependencies` | No | Array of plugin dependencies | | ||
| ### `definePlugin<T>(pluginType, api, name?)` | ||
| *At least one of `module` or `main` is required. | ||
| Helper function to create a type-safe plugin export. | ||
| ## Plugin Dependencies | ||
| ```typescript | ||
| import { definePlugin } from '@libria/plugin-loader'; | ||
| Plugins can depend on other plugins. Dependencies are loaded first, and the loading order is determined by topological sort. | ||
| export default definePlugin('my-type', { | ||
| myMethod() { | ||
| return 'Hello!'; | ||
| } | ||
| }); | ||
| ```json | ||
| { | ||
| "id": "plugin-a", | ||
| "pluginType": "feature", | ||
| "version": "1.0.0", | ||
| "module": "./dist/index.mjs", | ||
| "dependencies": [ | ||
| { "id": "plugin-b", "version": "^1.0.0" }, | ||
| { "id": "plugin-c", "version": ">=2.0.0" } | ||
| ] | ||
| } | ||
| ``` | ||
| ### `findPlugins(pattern, pluginType?)` | ||
| Access dependencies in your plugin via the context: | ||
| Discovers plugins by scanning directories for `plugin.json` manifests. | ||
| ```typescript | ||
| import { findPlugins } from '@libria/plugin-loader'; | ||
| export default definePlugin<MyAPI>({ | ||
| id: 'plugin-a', | ||
| pluginType: 'feature', | ||
| // Simple directory path (scans one level deep) | ||
| const manifests = await findPlugins('./plugins'); | ||
| create(ctx) { | ||
| // Dependencies are guaranteed to be loaded | ||
| const pluginB = ctx.getPlugin<PluginBAPI>('plugin-b'); | ||
| const pluginC = ctx.getPlugin<PluginCAPI>('plugin-c'); | ||
| // Glob pattern | ||
| const manifests = await findPlugins('./plugins/*-plugin'); | ||
| return { | ||
| api: { | ||
| doSomething() { | ||
| return pluginB.getValue() + pluginC.calculate(); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| }); | ||
| ``` | ||
| // Recursive glob | ||
| const manifests = await findPlugins('./plugins/**/dist'); | ||
| ### Version Requirements | ||
| // Filter by plugin type | ||
| const manifests = await findPlugins('./plugins', 'greeting'); | ||
| ``` | ||
| Dependencies use semver ranges: | ||
| ### `loadPlugin<T>(manifest)` | ||
| - `"1.0.0"` - Exact version | ||
| - `"^1.0.0"` - Compatible with 1.x.x | ||
| - `"~1.0.0"` - Compatible with 1.0.x | ||
| - `">=1.0.0 <2.0.0"` - Range | ||
| - `"*"` - Any version | ||
| Loads a single plugin from its manifest. Supports both ESM (`module`) and CommonJS (`main`) entry points. | ||
| ## Async Initialization | ||
| Plugin factories can be async for loading configs, connecting to services, etc: | ||
| ```typescript | ||
| import { findPlugins, loadPlugin } from '@libria/plugin-loader'; | ||
| export default definePlugin<DatabaseAPI>({ | ||
| id: 'database', | ||
| pluginType: 'service', | ||
| const [manifest] = await findPlugins('./plugins/my-plugin'); | ||
| const plugin = await loadPlugin<{ sayHello: (name: string) => string }>(manifest); | ||
| async create(ctx) { | ||
| const config = await loadConfig(); | ||
| const connection = await connectToDatabase(config); | ||
| console.log(plugin.api.sayHello('World')); | ||
| return { | ||
| api: { | ||
| query: (sql) => connection.query(sql), | ||
| close: () => connection.close() | ||
| } | ||
| }; | ||
| } | ||
| }); | ||
| ``` | ||
| ### `loadAllPlugins<T>(pattern, pluginType?)` | ||
| ## Lifecycle Hooks | ||
| Convenience function that combines `findPlugins` and `loadPlugin`. Discovers and loads all matching plugins. | ||
| Plugins can implement lifecycle hooks for setup and cleanup: | ||
| ```typescript | ||
| import { loadAllPlugins } from '@libria/plugin-loader'; | ||
| export default definePlugin<MyAPI>({ | ||
| id: 'my-plugin', | ||
| pluginType: 'feature', | ||
| const plugins = await loadAllPlugins<{ greet: () => string }>( | ||
| './plugins/*-plugin', | ||
| 'greeting' | ||
| ); | ||
| create(ctx) { | ||
| let intervalId: NodeJS.Timeout; | ||
| for (const plugin of plugins) { | ||
| console.log(plugin.api.greet()); | ||
| } | ||
| return { | ||
| api: { | ||
| // ... your API | ||
| }, | ||
| onLoad() { | ||
| // Called after plugin is registered | ||
| console.log('Plugin loaded!'); | ||
| intervalId = setInterval(() => { | ||
| console.log('heartbeat'); | ||
| }, 1000); | ||
| }, | ||
| async onUnload() { | ||
| // Called before plugin is unloaded | ||
| // Can be async for cleanup | ||
| clearInterval(intervalId); | ||
| await saveState(); | ||
| console.log('Plugin unloaded!'); | ||
| } | ||
| }; | ||
| } | ||
| }); | ||
| ``` | ||
| ## Plugin Manifest | ||
| ## Hot-Reloading | ||
| The `plugin.json` file defines plugin metadata: | ||
| Reload plugins without restarting your application: | ||
| | Field | Type | Required | Description | | ||
| |--------------|--------|----------|--------------------------------------| | ||
| | `name` | string | Yes | Unique plugin identifier | | ||
| | `pluginType` | string | Yes | Plugin category/type for filtering | | ||
| | `module` | string | No | ESM entry point (relative path) | | ||
| | `main` | string | No | CommonJS entry point (relative path) | | ||
| | `types` | string | No | TypeScript declaration file | | ||
| ```typescript | ||
| const manager = new PluginManager(); | ||
| await manager.loadPlugins(['./plugins/*']); | ||
| At least one of `module` or `main` must be specified. | ||
| // Manual reload | ||
| await manager.reloadPlugin('my-plugin'); | ||
| ### ESM Plugin Example | ||
| // Watch for file changes | ||
| await manager.watch(['./plugins/*'], (id, event, error) => { | ||
| if (event === 'reload') { | ||
| console.log(`Plugin ${id} reloaded successfully`); | ||
| } | ||
| if (event === 'error') { | ||
| console.error(`Failed to reload ${id}:`, error); | ||
| } | ||
| }); | ||
| // Stop watching | ||
| await manager.stopWatching(); | ||
| ``` | ||
| my-plugin/ | ||
| plugin.json | ||
| dist/ | ||
| index.mjs | ||
| ``` | ||
| ```json | ||
| { | ||
| "name": "my-plugin", | ||
| "pluginType": "feature", | ||
| "module": "./dist/index.mjs" | ||
| } | ||
| ``` | ||
| ## Plugin Queries | ||
| ### CommonJS Plugin Example | ||
| Query loaded plugins by type or get metadata: | ||
| ``` | ||
| my-plugin/ | ||
| plugin.json | ||
| dist/ | ||
| index.cjs | ||
| ``` | ||
| ```typescript | ||
| // Get all loaded plugin IDs | ||
| const ids = manager.getPluginIds(); | ||
| // ['greeter', 'database', 'logger'] | ||
| ```json | ||
| { | ||
| "name": "my-plugin", | ||
| "pluginType": "feature", | ||
| "main": "./dist/index.cjs" | ||
| // Check if a plugin is loaded | ||
| if (manager.hasPlugin('greeter')) { | ||
| // ... | ||
| } | ||
| ``` | ||
| ### Nested Manifest (dist folder) | ||
| // Get metadata for a plugin | ||
| const meta = manager.getPluginMetadata('greeter'); | ||
| // { id: 'greeter', name: 'Greeter Plugin', version: '1.0.0', ... } | ||
| You can place `plugin.json` inside the `dist` folder and use glob patterns to discover it: | ||
| // Get all plugins of a specific type | ||
| const services = manager.getPluginsByType('service'); | ||
| // [{ id: 'database', ... }, { id: 'cache', ... }] | ||
| // Get all metadata | ||
| const allMeta = manager.getAllMetadata(); | ||
| ``` | ||
| my-plugin/ | ||
| dist/ | ||
| plugin.json | ||
| index.mjs | ||
| ``` | ||
| ## Graceful Shutdown | ||
| Properly unload all plugins (calls `onUnload` hooks in reverse order): | ||
| ```typescript | ||
| // Discover plugins with manifest in dist/ | ||
| const plugins = await loadAllPlugins('./plugins/*/dist'); | ||
| process.on('SIGTERM', async () => { | ||
| await manager.shutdown(); | ||
| process.exit(0); | ||
| }); | ||
| ``` | ||
| ## Types | ||
| ## Error Handling | ||
| ### `LibriaPlugin<T>` | ||
| The library provides typed errors for common scenarios: | ||
| ```typescript | ||
| interface LibriaPlugin<T = unknown> { | ||
| readonly pluginType: string; | ||
| readonly name?: string; | ||
| readonly api: T; | ||
| import { | ||
| PluginLoadError, // Failed to load plugin module | ||
| PluginInvalidExportError, // Plugin doesn't export a valid factory | ||
| PluginNotFoundError, // Plugin not found (getPlugin, reload, unload) | ||
| ManifestNotFoundError, // plugin.json not found during reload | ||
| DuplicatePluginError, // Attempting to register same ID twice | ||
| CircularDependencyError, // Circular dependency detected | ||
| DependencyNotFoundError, // Required dependency not found | ||
| VersionMismatchError, // Dependency version doesn't match | ||
| } from '@libria/plugin-loader'; | ||
| try { | ||
| await manager.loadPlugins(['./plugins/*']); | ||
| } catch (err) { | ||
| if (err instanceof CircularDependencyError) { | ||
| console.error('Circular dependency:', err.cycle.join(' -> ')); | ||
| } | ||
| if (err instanceof VersionMismatchError) { | ||
| console.error( | ||
| `${err.packageId}@${err.actualVersion} doesn't satisfy ${err.requiredVersion}` | ||
| ); | ||
| } | ||
| } | ||
| ``` | ||
| ### `PluginManifest` | ||
| ```typescript | ||
| interface PluginManifest { | ||
| readonly name: string; | ||
| readonly pluginType: string; | ||
| readonly main?: string; | ||
| readonly module?: string; | ||
| readonly types?: string; | ||
| readonly __dir: string; // Resolved absolute path | ||
| try { | ||
| await manager.reloadPlugin('my-plugin'); | ||
| } catch (err) { | ||
| if (err instanceof PluginNotFoundError) { | ||
| console.error(`Plugin ${err.id} is not loaded`); | ||
| } | ||
| if (err instanceof ManifestNotFoundError) { | ||
| console.error(`Manifest missing for ${err.pluginId} in ${err.dir}`); | ||
| } | ||
| } | ||
| ``` | ||
| ## Error Handling | ||
| ## API Reference | ||
| The library throws specific errors for common issues: | ||
| ### `PluginManager` | ||
| - **`PluginLoadError`** - Failed to load the plugin module | ||
| - **`PluginInvalidExportError`** - Plugin export is not a valid object | ||
| - **`PluginTypeMismatchError`** - Plugin type doesn't match manifest | ||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `loadPlugins(patterns: string[])` | Load plugins from glob patterns | | ||
| | `getPlugin<T>(id: string): T` | Get a plugin's API by ID | | ||
| | `hasPlugin(id: string): boolean` | Check if a plugin is loaded | | ||
| | `getPluginIds(): string[]` | Get all loaded plugin IDs | | ||
| | `getPluginMetadata(id: string)` | Get metadata for a plugin | | ||
| | `getPluginsByType(type: string)` | Get all plugins of a type | | ||
| | `getAllMetadata()` | Get all plugin metadata | | ||
| | `reloadPlugin(id: string)` | Hot-reload a specific plugin | | ||
| | `unloadPlugin(id: string)` | Unload a specific plugin | | ||
| | `watch(patterns, callback)` | Watch for file changes | | ||
| | `stopWatching()` | Stop watching for changes | | ||
| | `shutdown()` | Unload all plugins and cleanup | | ||
| | `getContext()` | Get the internal plugin context | | ||
| ### `definePlugin<T>(factory: PluginFactory<T>)` | ||
| Helper function for defining plugins with proper typing. | ||
| ### `PluginContext` | ||
| Passed to the `create` function: | ||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `getPlugin<T>(id: string): T` | Get another plugin's API | | ||
| | `hasPlugin(id: string): boolean` | Check if a plugin is loaded | | ||
| ### `LibriaPlugin<T>` | ||
| The return type of `create`: | ||
| ```typescript | ||
| import { loadPlugin, PluginTypeMismatchError } from '@libria/plugin-loader'; | ||
| try { | ||
| const plugin = await loadPlugin(manifest); | ||
| } catch (error) { | ||
| if (error instanceof PluginTypeMismatchError) { | ||
| console.error(`Type mismatch: expected ${error.expected}, got ${error.actual}`); | ||
| } | ||
| interface LibriaPlugin<T> { | ||
| api: T; | ||
| onLoad?(): void | Promise<void>; | ||
| onUnload?(): void | Promise<void>; | ||
| } | ||
| ``` | ||
| ## Directory Structure | ||
| Recommended project structure: | ||
| ``` | ||
| my-app/ | ||
| ├── src/ | ||
| │ └── index.ts | ||
| ├── plugins/ | ||
| │ ├── greeter/ | ||
| │ │ ├── plugin.json | ||
| │ │ ├── src/ | ||
| │ │ │ └── index.ts | ||
| │ │ └── dist/ | ||
| │ │ └── index.mjs | ||
| │ └── database/ | ||
| │ ├── plugin.json | ||
| │ ├── src/ | ||
| │ │ └── index.ts | ||
| │ └── dist/ | ||
| │ └── index.mjs | ||
| └── package.json | ||
| ``` | ||
| ## License | ||
| MIT |
Sorry, the diff of this file is not supported yet
98839
205.4%54
200%398
73.8%4
100%+ Added
+ Added
+ Added
+ Added
+ Added