Skip to content

feat(plugins): port plugins/extensibility to Phoenix#96

Merged
mpge merged 1 commit into
masterfrom
feat/plugins-extensibility
Jul 1, 2026
Merged

feat(plugins): port plugins/extensibility to Phoenix#96
mpge merged 1 commit into
masterfrom
feat/plugins-extensibility

Conversation

@mpge

@mpge mpge commented Jul 1, 2026

Copy link
Copy Markdown
Member

Ports the plugins / extensibility feature to escalated-phoenix, reaching parity with the Laravel reference. This was the last open item in the phoenix parity backlog.

Approach — extensibility, translated to the BEAM

The Laravel reference is dual-track: WordPress-style PHP hooks plus a Node.js SDK subprocess bridge (JSON-RPC over stdio), ZIP upload of plugin files, and require-at-runtime. The runtime-loading half does not map to a compiled Elixir release, so plugins here are host-app modules implementing a behaviour and registered in config — the same convention this repo already uses for Escalated.Api.HostAuth. The extensibility contract (hook dispatch, activation lifecycle, per-plugin data store, admin management) is fully preserved.

config :escalated, plugins: [MyApp.Plugins.Billing]

What's included

  • Escalated.Plugins.Hooks — action/filter dispatch. Handlers come from config (:hooks / :filters callback maps) and from active plugin modules (handle_action/2, handle_filter/3). A raising handler is logged and skipped: actions swallowed, filters fall back to the running value — a bad plugin can never crash a host request.
  • Escalated.Plugins — registry + activate / deactivate / uninstall lifecycle. Persists state in escalated_plugins and fires the plugin_activated / plugin_deactivated / plugin_uninstalling hooks (matching Laravel's order) plus the plugin's own on_activate/0 / on_deactivate/0.
  • Escalated.Plugins.Store — queryable per-plugin key/value store over escalated_plugin_store, with the reference operator set ($eq $ne $gt $gte $lt $lte $in $nin), filtered in-memory so semantics are DB-agnostic.
  • Admin PluginController + routes — list / activate / deactivate / uninstall under /admin/plugins. No ZIP-upload endpoint (plugins are compiled host modules).
  • Schemas + migration for escalated_plugins and escalated_plugin_store, and a plugins: [] config entry.
  • Escalated.Plugins.Plugin behaviour documenting the host contract.

Intentionally out of scope (documented in module docs)

  • The Node SDK subprocess bridge / JSON-RPC runtime — Elixir plugins are BEAM modules.
  • ZIP upload + runtime file loading — not applicable to a compiled release.
  • Instrumenting every one of the reference's 40+ domain hook call sites — this lands the dispatch mechanism and fires the lifecycle hooks; domain call-site wiring can follow. The admin Vue page lives in the shared frontend package.

Verification

Ran the exact CI toolchain (Elixir 1.15 / OTP 26) in a container:

  • mix format --check-formatted — clean
  • mix credo --strict on all new files — no issues
  • mix test544 tests, 0 failures

Adds the extensibility layer to reach parity with the Laravel reference,
translated to idiomatic BEAM: plugins are host-app modules implementing
the Escalated.Plugins.Plugin behaviour and registered in config, rather
than uploaded PHP files driven by a Node SDK subprocess (neither the file
upload nor the JSON-RPC runtime bridge maps to a compiled release).

  * Escalated.Plugins.Hooks — WordPress-style action/filter dispatch over
    config-registered callbacks (:hooks/:filters) and active plugin
    handlers; a raising handler is logged and skipped (actions swallowed,
    filters fall back to the running value).
  * Escalated.Plugins — registry + activate/deactivate/uninstall lifecycle,
    persisting activation state in escalated_plugins and firing the
    plugin_activated/deactivated/uninstalling hooks.
  * Escalated.Plugins.Store — queryable per-plugin key/value store over
    escalated_plugin_store, with Mongo-style operators (////
    $lt/$lte/$in/$nin).
  * Admin PluginController + routes (list / activate / deactivate /
    uninstall). No ZIP upload — plugins are compiled host modules.
  * Plugin + PluginStoreRecord schemas, migration for both tables, and
    config :plugins. Verified: mix format, credo --strict, mix test (544, 0
    failures).
@mpge mpge merged commit 3d52ffd into master Jul 1, 2026
2 checks passed
@mpge mpge deleted the feat/plugins-extensibility branch July 1, 2026 19:36
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