# `Scrypath.Composition`
[🔗](https://github.com/szTheory/scrypath/blob/v0.3.7/lib/scrypath/composition.ex#L1)

Public plain-data composition seam for reusable search presets and additive scopes.

Host apps define feature-level or context-owned fragments such as `%{defaults: ...}`
and `%{fixed: ...}`. This module composes those fragments into the same plain-data
criteria vocabulary already accepted by `Scrypath.search/3`.

`Scrypath.Composition` is data-only:

- it never executes search
- it never exposes `%Scrypath.Query{}`
- it does not move composition ownership onto schemas or `Scrypath.Phoenix`

The composed result stays a plain map that includes final criteria plus coarse
visibility buckets: `applied`, `defaulted`, `fixed`, and optional `sources`
or `warnings`.

# `criteria`

```elixir
@type criteria() :: %{
  optional(:text) =&gt; String.t(),
  optional(:filter) =&gt; keyword(),
  optional(:sort) =&gt; keyword(),
  optional(:page) =&gt; keyword(),
  optional(:facets) =&gt; [atom()],
  optional(:facet_filter) =&gt; keyword(),
  optional(:per_query) =&gt; map()
}
```

Caller-facing criteria vocabulary aligned with `Scrypath.search/3`.

# `fixed_criteria`

```elixir
@type fixed_criteria() :: %{
  optional(:filter) =&gt; keyword(),
  optional(:facet_filter) =&gt; keyword()
}
```

Allowed fragment fixed constraints for filter-bearing fields only.

# `fragment`

```elixir
@type fragment() :: %{
  optional(:defaults) =&gt; fragment_criteria(),
  optional(:fixed) =&gt; fixed_criteria(),
  optional(:sources) =&gt; map(),
  optional(:warnings) =&gt; map()
}
```

Public fragment envelope used to compose presets and scopes.

# `fragment_criteria`

```elixir
@type fragment_criteria() :: criteria()
```

Allowed fragment defaults for all public search fields.

# `many_entry_spec`

```elixir
@type many_entry_spec() :: %{
  :schema =&gt; module() | :all,
  :text =&gt; String.t(),
  optional(:fragments) =&gt; fragment() | [fragment()],
  optional(:criteria) =&gt; criteria()
}
```

Public multi-search entry spec consumed by `compose_many/2`.

# `many_result`

```elixir
@type many_result() :: %{shared: result(), entries: [map()]}
```

Public multi-search composition result. Shared composition lowers defaults only,
per-entry composition stays canonical, and `to_search_many_args/1` emits the
existing tuple/shared-option contract for `Scrypath.search_many/2`.

# `result`

```elixir
@type result() :: Scrypath.Composition.Result.t()
```

Stable public result returned by `compose/2`.

# `compose`

```elixir
@spec compose(fragment() | [fragment()], criteria()) ::
  {:ok, result()} | {:error, term()}
```

Composes one fragment or a list of fragments with caller criteria.

# `compose!`

```elixir
@spec compose!(fragment() | [fragment()], criteria()) :: result()
```

Like `compose/2`, but raises `ArgumentError` instead of returning `{:error, reason}`.

# `compose_many`

```elixir
@spec compose_many(
  [many_entry_spec() | tuple()],
  keyword()
) :: {:ok, many_result()} | {:error, term()}
```

Composes multi-search entries into the existing tuple/shared-option contract.

Shared composition lowers defaults only. Shared fixed constraints are not
supported, and multi-search-only rails such as federation weights or
`max_schemas` stay outside this helper.

# `compose_many!`

```elixir
@spec compose_many!(
  [many_entry_spec() | tuple()],
  keyword()
) :: many_result()
```

Like `compose_many/2`, but raises `ArgumentError` instead of returning
`{:error, reason}`.

# `to_search_args`

```elixir
@spec to_search_args(result()) :: {String.t(), keyword()}
```

Converts the composed plain-data result into `{text, keyword_opts}` for a
context-owned `Scrypath.search/3` call.

# `to_search_many_args`

```elixir
@spec to_search_many_args(many_result()) :: {list(), keyword()}
```

Lowers a multi-search composition result into `{entries, shared_opts}` for a
context-owned `Scrypath.search_many/2` call.

---

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