PhoenixKitProjects.Extensions (PhoenixKitProjects v0.21.1)

Copy Markdown View Source

Per-project extension enablement — the hub's context layer.

The catalog (which extension types exist) lives in PhoenixKitProjects.Extensions.Registry; this module owns the per-project state: which extensions a given project has enabled, each instance's config, and the enable/disable lifecycle (activity rows, PubSub, provider callbacks).

Semantics (locked by the 2026-08-05 plan + panel round)

  • Disable hides, never deletesdisable/3 flips enabled to false; the row, its config, and the extension's own data survive. Re-enabling restores everything.
  • Instance-keyed identity — every function threads instance_key (default "default"); v1 UI exposes one instance per extension.
  • Config writes are whitelisted against the extension's declared config_schema keys — raw params never reach the JSONB.
  • Effective enablement is an intersection: a row with enabled: true counts only while the extension is in the catalog AND its backing site module is enabled (Registry.available?/1) — a site-level disable wins instantly without touching rows.
  • Defaults: a project with NO row for an extension falls back to the catalog's default_enabled (the built-in Tasks extension ships default_enabled: true, preserving pre-hub behavior for every existing project). Presets (Step 3) write explicit rows at creation — absence always means "inherit the catalog default", never a frozen choice.

Authorization note: these functions trust their caller (the LiveView layer gates on PhoenixKitProjects.Authz.can?/5 with :manage_modules). Activity actor flows in via opts[:actor_uuid], LV-layer convention.

Summary

Functions

Disables an extension instance for a project. The row (and its config) survives — see the moduledoc. Creates an explicit disabled row when the enablement was implicit (catalog default_enabled), so "turn tasks off" works on a project that never had a row.

Enables an extension for a project (upserts the instance row).

Whether one extension is effectively enabled for a project.

The extensions EFFECTIVELY enabled for a project: catalog availability ∩ (explicit row OR catalog default). Returns [{%Extension{}, %ProjectModule{} | nil}] — the row is nil when enablement comes from default_enabled with no explicit row yet.

All enablement rows for a project (enabled AND disabled — panel UI needs both).

Updates one instance's config, whitelisted against the extension's config_schema keys. Unknown keys are silently dropped (never written); the stored map is REPLACED by the whitelisted input merged over it, so omitted known keys survive.

Functions

catalog()

See PhoenixKitProjects.Extensions.Registry.catalog/0.

disable(project_or_uuid, ext_key, opts \\ [])

@spec disable(map() | binary(), String.t(), keyword()) ::
  {:ok, PhoenixKitProjects.Schemas.ProjectModule.t()} | {:error, term()}

Disables an extension instance for a project. The row (and its config) survives — see the moduledoc. Creates an explicit disabled row when the enablement was implicit (catalog default_enabled), so "turn tasks off" works on a project that never had a row.

enable(project_or_uuid, ext_key, opts \\ [])

@spec enable(map() | binary(), String.t(), keyword()) ::
  {:ok, PhoenixKitProjects.Schemas.ProjectModule.t()} | {:error, term()}

Enables an extension for a project (upserts the instance row).

Options: :actor_uuid, :instance_key, :config (whitelisted), :name. Returns {:ok, row} or {:error, reason}. Unknown extension keys are rejected ({:error, :unknown_extension}); an extension whose site module is disabled is rejected ({:error, :module_disabled}) — enabling something that can't render is a config trap.

enabled?(project_or_uuid, ext_key, instance_key \\ "default")

@spec enabled?(map() | binary(), String.t(), String.t()) :: boolean()

Whether one extension is effectively enabled for a project.

Accepts a project struct or uuid. The intersection rule from the moduledoc applies; unknown keys are always false (fail-closed).

enabled_for_project(project_uuid)

The extensions EFFECTIVELY enabled for a project: catalog availability ∩ (explicit row OR catalog default). Returns [{%Extension{}, %ProjectModule{} | nil}] — the row is nil when enablement comes from default_enabled with no explicit row yet.

get_row(project_uuid, ext_key, instance_key \\ "default")

One enablement row (or nil).

get_type(key)

See PhoenixKitProjects.Extensions.Registry.get/1.

list_rows(project_uuid)

All enablement rows for a project (enabled AND disabled — panel UI needs both).

list_types()

See PhoenixKitProjects.Extensions.Registry.list/0.

refresh()

See PhoenixKitProjects.Extensions.Registry.refresh/0.

update_config(project_or_uuid, ext_key, config, opts \\ [])

@spec update_config(map() | binary(), String.t(), map(), keyword()) ::
  {:ok, PhoenixKitProjects.Schemas.ProjectModule.t()} | {:error, term()}

Updates one instance's config, whitelisted against the extension's config_schema keys. Unknown keys are silently dropped (never written); the stored map is REPLACED by the whitelisted input merged over it, so omitted known keys survive.