PhoenixKitWarehouse.SourceKinds (PhoenixKitWarehouse v0.1.0)

Copy Markdown View Source

Generic registry/dispatch for the source_refs decoupling contract.

phoenix_kit_warehouse documents (Internal Orders, Goods Issues) link back to arbitrary host-owned "source" records — a sub-order, a top-level order, or anything else a consuming app wants to link — via a source_refs JSONB column shaped [%{"kind" => "sub_order", "uuid" => "..."}, ...]. This module never queries a host table directly; instead the host registers, per kind, three callbacks in its own config:

config :phoenix_kit_warehouse,
  source_kinds: [
    %{
      kind: "sub_order",
      label: "Sub-order",
      search: {MyApp.Warehouse.Integration, :search_sub_orders, []},
      resolve: {MyApp.Warehouse.Integration, :resolve_sub_order, []},
      build_lines: {MyApp.Warehouse.Integration, :build_sub_order_lines, []}
    }
  ]

With no source_kinds configured at all, every function in this module degrades gracefully: search_candidates/1 returns [], resolve/2 returns :error (callers show a plain UUID), and build_lines/3 returns {:error, :unsupported_kind}. This is what makes the package usable standalone, with no host "order" concept at all.

build_lines is optional per kind — a kind can be searchable/resolvable (linkable, shows up in pickers and renders as a link) without being importable (lines cannot be built from it), by simply omitting the key.

Summary

Types

An {module, function, extra_args} tuple dispatched via apply(m, f, extra_args ++ dynamic_args).

Functions

Builds line-item data for a chosen import candidate — used when a draft Internal Order imports lines "from" a picked source. Returns {:error, :unsupported_kind} when the kind isn't registered or doesn't declare a build_lines callback.

All kinds registered by the host app, in config order.

Resolves an existing %{"kind" => kind, "uuid" => uuid} source ref to a %{label:, path:} map for rendering a link. Returns :error if the kind isn't registered, the callback doesn't return the expected shape, or the callback itself raises — callers fall back to showing the plain UUID.

Searches every registered kind for query and returns the merged candidate list, each tagged with its kind and label (as label_prefix) so a picker can group them. A kind whose search callback raises is skipped (logged), not fatal to the other kinds.

Types

kind_config()

@type kind_config() :: %{
  :kind => String.t(),
  :label => String.t(),
  :search => mf_args(),
  :resolve => mf_args(),
  optional(:build_lines) => mf_args()
}

mf_args()

@type mf_args() :: {module(), atom(), list()}

An {module, function, extra_args} tuple dispatched via apply(m, f, extra_args ++ dynamic_args).

Functions

build_lines(kind, uuid, actor_uuid)

@spec build_lines(String.t(), String.t(), String.t()) ::
  {:ok, [map()]} | :error | {:error, :unsupported_kind}

Builds line-item data for a chosen import candidate — used when a draft Internal Order imports lines "from" a picked source. Returns {:error, :unsupported_kind} when the kind isn't registered or doesn't declare a build_lines callback.

list_kinds()

@spec list_kinds() :: [kind_config()]

All kinds registered by the host app, in config order.

resolve(kind, uuid)

@spec resolve(String.t(), String.t()) ::
  %{label: String.t(), path: String.t()} | :error

Resolves an existing %{"kind" => kind, "uuid" => uuid} source ref to a %{label:, path:} map for rendering a link. Returns :error if the kind isn't registered, the callback doesn't return the expected shape, or the callback itself raises — callers fall back to showing the plain UUID.

search_candidates(query)

@spec search_candidates(String.t()) :: [
  %{
    kind: String.t(),
    label_prefix: String.t(),
    uuid: String.t(),
    label: String.t(),
    extra: term()
  }
]

Searches every registered kind for query and returns the merged candidate list, each tagged with its kind and label (as label_prefix) so a picker can group them. A kind whose search callback raises is skipped (logged), not fatal to the other kinds.