# `PhoenixKitWarehouse.SourceKinds`
[🔗](https://github.com/BeamLabEU/phoenix_kit_warehouse/blob/0.4.0/lib/phoenix_kit_warehouse/source_kinds.ex#L1)

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.

# `kind_config`

```elixir
@type kind_config() :: %{
  :kind =&gt; String.t(),
  :label =&gt; String.t(),
  :search =&gt; mf_args(),
  :resolve =&gt; mf_args(),
  optional(:build_lines) =&gt; mf_args()
}
```

# `mf_args`

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

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

# `build_lines`

```elixir
@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`

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

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

# `resolve`

```elixir
@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`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
