PhoenixKitCatalogue.Catalogue.PubSub (PhoenixKitCatalogue v0.19.1)

Copy Markdown View Source

Real-time fan-out for catalogue mutations.

Every successful write in the Catalogue context broadcasts a small {:catalogue_data_changed, kind, uuid, parent_catalogue_uuid} event to a single shared topic. List/detail LiveViews subscribe/0 once in mount/3 (after connected?(socket)) and re-fetch the affected slice on any event, so two admins editing the same data converge without manual refresh.

parent_catalogue_uuid lets a detail LV cheaply ignore broadcasts for unrelated catalogues — without it, every item edit anywhere in the system would force every open detail page to reload its slice. Global resources (manufacturers, suppliers, manufacturer↔supplier links) carry nil here; consumers that care about them subscribe to the kind regardless of parent.

Payloads are intentionally minimal — UUID + kind + parent, no record data — to (a) avoid leaking field-level changes through PubSub, and (b) keep the consumer in charge of how much to re-load (single row vs full list).

Subscriptions are cleaned up automatically when the LV process terminates; callers don't need to unsubscribe.

Summary

Types

Event message format for handle_info/2.

Resource kind that mutated.

Functions

Broadcasts a {:catalogue_data_changed, kind, uuid, parent_catalogue_uuid} event after a successful write.

Broadcasts a bulk-change event so other open detail pages animate the affected items leaving / arriving on screen.

Broadcasts a card-refresh event so other open detail pages re-fetch a single category card's items after a reorder.

Broadcasts a category-reorder event so other open detail pages re-fetch the category list (positions changed). Heavier than broadcast_card_refresh/5 — receivers do a full reset_and_load since category order affects every streamed card on the page.

Broadcasts a shared-sort change for a global-sort table scope (see PhoenixKitCatalogue.Web.ViewConfig.global_sort?/1), so every open catalogues index switches to the new ordering live. from rides in the message — the originating LV already applied the change locally and skips its own event.

Subscribes the current process to the catalogue PubSub topic.

Returns the canonical topic name. Useful for tests.

Types

event()

@type event() ::
  {:catalogue_data_changed, kind(), Ecto.UUID.t() | nil, Ecto.UUID.t() | nil}

Event message format for handle_info/2.

kind()

@type kind() ::
  :catalogue
  | :category
  | :item
  | :folder
  | :manufacturer
  | :supplier
  | :smart_rule
  | :links
  | :pdf
  | :item_supplier_info
  | :attribute_group
  | :attribute_set
  | :supplier_field

Resource kind that mutated.

Functions

broadcast(kind, uuid, parent_catalogue_uuid \\ nil)

@spec broadcast(kind(), Ecto.UUID.t() | nil, Ecto.UUID.t() | nil) :: :ok

Broadcasts a {:catalogue_data_changed, kind, uuid, parent_catalogue_uuid} event after a successful write.

  • uuid — UUID of the resource that mutated; nil when the change isn't tied to a specific record (e.g. a bulk link sync).
  • parent_catalogue_uuid — UUID of the catalogue that contains the mutated resource, or the UUID itself for kind: :catalogue events. Pass nil for resources that aren't scoped to a single catalogue (:manufacturer, :supplier, :links); detail LVs use this to filter out cross-catalogue noise.

broadcast_bulk_change(catalogue_uuid, kind, uuids, from \\ self())

@spec broadcast_bulk_change(
  Ecto.UUID.t(),
  :trashed | :restored | :moved | :permanent_delete,
  [Ecto.UUID.t()],
  pid()
) :: :ok

Broadcasts a bulk-change event so other open detail pages animate the affected items leaving / arriving on screen.

kind:

  • :trashed — items are going away (red flash → state refresh).
  • :restored — items are coming back (state refresh → green flash).
  • :moved — items are leaving one scope and entering another (red flash on source DOM → state refresh → green flash on destination DOM).
  • :permanent_delete — items are gone for good (same animation as :trashed; the row removal is harder to undo but the visual cue is the same).

uuids is the affected item list. The receiver does a full reset_and_load since a bulk change can rearrange every visible card; per-scope filtering would only be a worthwhile optimisation once catalogues routinely render dozens of cards.

broadcast_card_refresh(catalogue_uuid, scope, flash_uuid, flash_status, from \\ self())

@spec broadcast_card_refresh(
  Ecto.UUID.t(),
  Ecto.UUID.t() | :uncategorized,
  Ecto.UUID.t() | nil,
  atom(),
  pid()
) :: :ok

Broadcasts a card-refresh event so other open detail pages re-fetch a single category card's items after a reorder.

scope is a category UUID or :uncategorized. from is the originating process; receivers compare against self() to skip self-originated events (the source LV already updated locally).

flash_uuid + flash_status let the receiver fire a sortable:flash push_event keyed to the moved row, so a second open tab sees the same green/red flash the originator did.

broadcast_category_reorder(catalogue_uuid, moved_id, status, from \\ self())

@spec broadcast_category_reorder(
  Ecto.UUID.t(),
  Ecto.UUID.t() | nil,
  atom(),
  pid()
) :: :ok

Broadcasts a category-reorder event so other open detail pages re-fetch the category list (positions changed). Heavier than broadcast_card_refresh/5 — receivers do a full reset_and_load since category order affects every streamed card on the page.

broadcast_view_sort_changed(scope, sort_by, sort_dir, from \\ self())

@spec broadcast_view_sort_changed(atom(), String.t(), :asc | :desc, pid()) :: :ok

Broadcasts a shared-sort change for a global-sort table scope (see PhoenixKitCatalogue.Web.ViewConfig.global_sort?/1), so every open catalogues index switches to the new ordering live. from rides in the message — the originating LV already applied the change locally and skips its own event.

subscribe()

@spec subscribe() :: :ok | {:error, term()}

Subscribes the current process to the catalogue PubSub topic.

Call from mount/3 guarded by connected?(socket) so the disconnected (initial render) pass doesn't subscribe and never unsubscribes. Do this after any subscription requirements but before the initial DB load to avoid a race where a write between the load and the subscribe leaves the UI stale.

topic()

@spec topic() :: String.t()

Returns the canonical topic name. Useful for tests.