Skip to content

feat(plugin-management): Streamline plugin lifecycle with auto-load/u…#52

Merged
Its4Nik merged 1 commit intodevfrom
feat-automatic-loading-on-install-and-frontend-route-refresh
Jan 7, 2026
Merged

feat(plugin-management): Streamline plugin lifecycle with auto-load/u…#52
Its4Nik merged 1 commit intodevfrom
feat-automatic-loading-on-install-and-frontend-route-refresh

Conversation

@Its4Nik
Copy link
Copy Markdown
Owner

@Its4Nik Its4Nik commented Jan 7, 2026

…nload and UI refresh

This commit enhances the plugin management workflow by introducing automatic loading and unloading of plugins.

  • Automatic Loading: Newly installed plugins are now automatically loaded into memory via loadPlugin after insertion, ensuring they are immediately active without requiring a restart.
  • Automatic Unloading: Deleted plugins are now automatically unloaded from memory via unloadPlugin, ensuring resources are freed and their routes are removed from the system.
  • Frontend UI Refresh: On plugin installation or deletion, fetchFrontendPluginRoutes is invalidated, ensuring the UI immediately reflects changes in available plugin routes and extensions.
  • Removed a debug console.log from getPluginBundle.

Summary by Sourcery

Streamline plugin lifecycle by auto-loading on installation, unloading on deletion, and refreshing frontend plugin routes.

New Features:

  • Automatically load newly installed plugins after they are saved to make them immediately active.

Enhancements:

  • Automatically unload plugins from memory when they are deleted and report unload status in the delete response.
  • Refresh frontend plugin route data after plugin installation or deletion by invalidating the relevant queries.
  • Remove an unnecessary debug log from the plugin bundle retrieval helper.

…nload and UI refresh

This commit enhances the plugin management workflow by introducing automatic loading and unloading of plugins.

- **Automatic Loading**: Newly installed plugins are now automatically loaded into memory via `loadPlugin` after insertion, ensuring they are immediately active without requiring a restart.
- **Automatic Unloading**: Deleted plugins are now automatically unloaded from memory via `unloadPlugin`, ensuring resources are freed and their routes are removed from the system.
- **Frontend UI Refresh**: On plugin installation or deletion, `fetchFrontendPluginRoutes` is invalidated, ensuring the UI immediately reflects changes in available plugin routes and extensions.
- Removed a debug `console.log` from `getPluginBundle`.
@Its4Nik Its4Nik self-assigned this Jan 7, 2026
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Jan 7, 2026

Reviewer's Guide

Implements automatic plugin load/unload on create/delete in the backend and ensures the frontend query cache for plugin routes is invalidated alongside the plugin list, while removing a leftover debug log from the plugin bundle fetch utility.

Sequence diagram for automatic plugin installation and loading

sequenceDiagram
  actor User
  participant PluginBrowser
  participant QueryClient
  participant Backend
  participant PluginHandler
  participant PluginRuntime
  participant FrontendRouter

  User->>PluginBrowser: Click install plugin
  PluginBrowser->>Backend: installPlugin request
  Backend->>PluginHandler: savePlugin(plugin)
  alt plugin exists
    PluginHandler->>PluginHandler: updateExistingPlugin
  else new plugin
    PluginHandler->>PluginHandler: insertNewPlugin
    PluginHandler-->>PluginHandler: res(id, success, message)
    opt res.id present
      PluginHandler->>PluginHandler: loadPlugin(id)
      PluginHandler->>PluginRuntime: register plugin and routes
      PluginRuntime-->>PluginHandler: plugin loaded
    end
    PluginHandler-->>Backend: { id, success, message with load status }
  end
  Backend-->>PluginBrowser: installPlugin response

  PluginBrowser->>QueryClient: onSuccess installPluginMutation
  QueryClient->>QueryClient: invalidateQueries fetchAllPlugins
  QueryClient->>QueryClient: invalidateQueries fetchFrontendPluginRoutes
  QueryClient->>Backend: refetch fetchAllPlugins
  Backend-->>QueryClient: updated plugin list
  QueryClient->>Backend: refetch fetchFrontendPluginRoutes
  Backend->>FrontendRouter: compute frontend plugin routes
  Backend-->>QueryClient: updated plugin routes
  QueryClient-->>PluginBrowser: updated data
  PluginBrowser-->>User: UI shows new plugin and routes
Loading

File-Level Changes

Change Details Files
Automatically load newly inserted plugins and report load status in the save response.
  • Wrap insertNewPlugin result to attempt loadPlugin when a new plugin is created.
  • Track whether loadPlugin succeeds and log an error if it fails.
  • Augment the save response with a message indicating whether the plugin was successfully loaded.
packages/plugin-handler/src/index.ts
Automatically unload plugins on delete and reflect unload status in the delete response message.
  • Call unloadPlugin when deleting a plugin record.
  • Base the delete response message on whether unloadPlugin returned a successful result.
packages/plugin-handler/src/index.ts
Ensure frontend invalidates both plugin list and plugin routes when installing or deleting plugins.
  • Update installPluginMutation onSuccess handler to invalidate both fetchAllPlugins and fetchFrontendPluginRoutes queries in parallel.
  • Update deletePluginMutation onSuccess handler to invalidate both fetchAllPlugins and fetchFrontendPluginRoutes queries in parallel.
apps/dockstat/src/pages/extensions/plugins.tsx
Remove a debug log from plugin bundle fetching utility.
  • Delete console.log used when computing the plugin bundle URL before fetching.
packages/utils/src/repo/getPluginBundle.ts

Possibly linked issues

  • #: They match: the PR adds automatic plugin loading on installation, plus extra unload and UI refresh behavior.
  • #[FEAT PluginHandler] Automatic loading of plugins on startup: PR updates PluginHandler to auto-load plugins after insertion, directly implementing requested automatic plugin loading on startup

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In savePlugin, insertNewPlugin is now assigned to const res = this.insertNewPlugin(plugin) without await, so if it returns a promise you’ll be checking res.id on the promise object instead of the resolved result; consider making this const res = await this.insertNewPlugin(plugin).
  • The unloadPlugin(id) call in the delete path is not awaited or wrapped in a try/catch, which means any async errors will be unhandled and the unloaded flag may not represent the real outcome; consider making this await with error handling if unloadPlugin is asynchronous.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `savePlugin`, `insertNewPlugin` is now assigned to `const res = this.insertNewPlugin(plugin)` without `await`, so if it returns a promise you’ll be checking `res.id` on the promise object instead of the resolved result; consider making this `const res = await this.insertNewPlugin(plugin)`.
- The `unloadPlugin(id)` call in the delete path is not awaited or wrapped in a try/catch, which means any async errors will be unhandled and the `unloaded` flag may not represent the real outcome; consider making this `await` with error handling if `unloadPlugin` is asynchronous.

## Individual Comments

### Comment 1
<location> `packages/plugin-handler/src/index.ts:182-191` </location>
<code_context>
-    return this.insertNewPlugin(plugin)
+    let loadedPlugin = false
+
+    const res = this.insertNewPlugin(plugin)
+    if (res.id) {
+      try {
+        await this.loadPlugin(res.id)
+        loadedPlugin = true
+      } catch (err) {
+        loadedPlugin = false
+        this.logger.error(
+          `Could not load plugin on save ${err instanceof Error ? err.message : String(err)}`
+        )
+      }
+    }
+
+    return { ...res, message: `${res.message} - loaded plugin: ${loadedPlugin}` }
   }

</code_context>

<issue_to_address>
**issue (bug_risk):** Potential missing `await` if `insertNewPlugin` is asynchronous.

This used to return `this.insertNewPlugin(plugin)` directly, implying `insertNewPlugin` likely returns a Promise. Now the result is stored in `res` and used synchronously (`res.id`, spread in the return) while `await` is only used on `loadPlugin`. If `insertNewPlugin` is async, `res` will be a Promise and this logic will fail. In that case, this should be `const res = await this.insertNewPlugin(plugin)` to match previous behavior and ensure `res.id` and the spread work correctly. If it is truly synchronous, please confirm, as that would be unusual for an insert operation.
</issue_to_address>

### Comment 2
<location> `packages/plugin-handler/src/index.ts:290` </location>
<code_context>
       this.table.where({ id: id }).delete()
       this.logger.info(`Deleted Plugin: ${id}`)
+
+      const unloaded = this.unloadPlugin(id)
+
       return {
</code_context>

<issue_to_address>
**issue (bug_risk):** Consider awaiting `unloadPlugin` if it is asynchronous to avoid unhandled errors.

If `unloadPlugin` is async and returns a Promise, calling it without `await` means any rejection won’t be caught by this `try/catch`, and the response may be sent before unload completes. If it’s async, use `const unloaded = await this.unloadPlugin(id)` so errors are properly handled; if it’s guaranteed sync, this is fine.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread packages/plugin-handler/src/index.ts
Comment thread packages/plugin-handler/src/index.ts
@Its4Nik Its4Nik merged commit 4a21224 into dev Jan 7, 2026
3 checks passed
@Its4Nik Its4Nik deleted the feat-automatic-loading-on-install-and-frontend-route-refresh branch January 7, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant