PhoenixKitCatalogue.Catalogue.PubSub (PhoenixKitCatalogue v0.1.17)

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.

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
  | :manufacturer
  | :supplier
  | :smart_rule
  | :links
  | :pdf

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.

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.