# Changelog
## 2.2.0 — 2026-10-18
- Requires vantage-table 3.0.0.
- A bearer token can come from a `BearerSource` (`Arc<dyn Fn() -> Option<String> + Send + Sync>`),
asked each time a request goes out, retries included. `None` sends no `Authorization` header.
Until now a datasource had one fixed header or one refresher per client, so a client shared by
several sessions sent the same token for all of them.
- `RestApiBuilder::bearer_source` and `GraphqlApiBuilder::bearer_source` set one; like `auth` and
`auth_refresher`, each replaces the others.
- `RestApi::with_bearer_source` and `GraphqlApi::with_bearer_source` return a clone that sends its
own source's token and shares the original's client: connection pool, parallel cap, breaker
and observer. One clone per session costs no extra connections. The client still adds an
`auth_refresher`'s header itself, so build the shared API without one when clones bring tokens.
- A `401` on a request whose token came from a source is a final error, not refreshed; the next
request asks the source again.
- `in_values_condition`: GraphQL builds an `in` filter. REST takes one value, sent as the same
`field=value` param as an equality, and refuses any other list as `Unsupported`, since a URL
filter param carries one value and dropping the condition would widen the set.
- `describe_condition` and `condition_column` are implemented for REST and GraphQL.
- REST `list`, `count` and `fetch_window` run the table's `BeforeSelect` hooks before any request
goes out.
## 2.0.1 — 2026-10-15
- The in-crate unit tests move to the blackbox suite under `tests/`, duplicate tests are dropped,
and public behaviour the docs promise but nothing tested gets a test. No API or behaviour change.
- The README names `driver_name()`, and the book now says a Generic-dialect `contains` filter is
refused as unsupported.
## 2.0.0 — 2026-10-14
### Breaking changes
- `ModelResolver` is removed; `RestApiVistaFactory::with_model_resolver` takes
`Arc<dyn VistaResolver>`. A closure or a `Catalog` fits.
- `GraphqlSpecResolver` is removed; `GraphqlApiVistaFactory::with_resolver` takes
`Arc<dyn SpecResolver<GraphqlApiVistaSpec>>`. A closure passed there needs an annotated
parameter (`|name: &str| ..`).
- Both shells implement `TableShell` 2.0: `fetch_page` and GraphQL's page-number `fetch_next` are
gone, neither driver advertises `Cursor`, and `fetch_window` returns a `Page`. A REST
`total_key` becomes `Page.total` from the same response.
- Neither driver advertises writes, so Vista refuses them before the shell.
- `set_order`, `set_search` and `fetch_window` refuse with `unsupported(..)` when their capability
is off.
- REST conditions accept declared columns and endpoint `{placeholder}` names only; any other
column is an error. A non-`Eq` operator, or an `Eq` with a non-scalar value, is refused so it is
evaluated locally.
- GraphQL `Like` on the generic dialect is `unsupported(FilterOps)`; it was `Unimplemented`.
- A dotted path into a declared column is refused as `Unimplemented` on REST and on GraphQL with
filter push-down. An unknown root is "Unknown column for condition" with a `field` key.
- GraphQL types each text operand by its column for every caller: an integer column takes an
integer, and a non-integer text there is an error before any request.
- A has-one target without an id column is an error at traversal; REST fell back to `"id"`.
- A REST client-side filter keeps a row lacking the field only for a `request_param` column, an
endpoint placeholder or an undeclared field. A `hidden` column is a row field.
- `preview()` is `{query, note}`. REST: `query` is `GET <url>`, plus `Authorization: <set>` when
an auth header is configured, and `note` lists client filters and unresolved conditions.
GraphQL: `query` is `POST <endpoint>` and the document, with no `note`.
- A name the REST registry or GraphQL spec resolver doesn't know is `is_not_found()`; the REST
traversal message is "No registered spec for model name".
- GraphQL quicksearch reads the columns flagged `Searchable`, or, when none is, every text column
other than the id.
### Other changes
- A REST API that doesn't page and a GraphQL root without paging answer `fetch_window(0, n)` from
one unpaged listing, with the listing's length as `total`.
- REST `list()` applies `set_order`; it ignored it.
- REST and GraphQL lower YAML references through `ReferenceSpec::to_reference`, so `keys:` gives
a multi-key join.
- A reference's `target` is its spec name for YAML and its table name for a typed relation, so
`catalog.vista(&reference.target)` resolves.
- A REST spec's `label` flag reaches the vista column.
- A failed removal of the previous GraphQL quicksearch condition propagates from `set_search`.
Migration guide: https://vantage-ui.com/book/migration-2.0.html
## 1.0.0 — 2026-10-11
- **Breaking:** `#[non_exhaustive]` on `FilterDialect`, `GraphqlOp`, `GraphqlCondition`,
`ResponseShape`, `FilterStrategy`, `CountMode`, `PaginationMode`, `Supports`, `PaginationParams`,
`OrderingParams`, `RenderedQuery` and the YAML block structs; build them with `Default`,
`new` and `with_*`.
- **Breaking:** internal modules are private (`graphql::{api, condition, operation, select,
impls}`, `rest::{api, operation, table_source}`, the vista `source` modules); types are
re-exported from the crate root, `graphql`, `rest` and `prelude`.
- **Breaking:** `GraphqlSelect` fields are private (read accessors added); `group_by` and
`distinct` removed.
- **Breaking:** YAML `dialect:` is typed: `generic` or `hasura`, lowercase only (was lower-cased);
an unknown value fails to parse.
- **Breaking:** `PaginationParams` has `mode: PaginationMode` (`Page` | `Skip`) instead of
`skip_based: bool`.
- **Breaking:** `NoApiExtras`, `NoGraphqlExtras`, `RestApi::with_auth` and
`GraphqlApiBuilder::client` removed; use `builder().auth(..)` and `http_client(..)`.
- **Breaking:** requests time out after 30 s by default (no timeout before); `request_timeout(..)`
and `no_request_timeout()` on both builders.
- **Breaking:** the default HTTP client is built on the first request, not at construction.
- **Breaking:** re-exported pool types (`TransportEvent` and its variants, `ClientError`,
`ErrorKind`, `BreakerState`, `CallPolicy`, `RetryMode`) are `#[non_exhaustive]`; build them with
their constructors. `ErrorKind::Closed` removed.
- **Breaking:** `#[non_exhaustive]` on `FieldCondition` and `GraphqlCondition::DeferredField`; build
them with `FieldCondition::new` and `GraphqlCondition::deferred_field`.
- **Breaking:** `OrderingParams::value` and `RestApi::serves_windows` are no longer public.
- **Breaking:** GraphQL paging is advertised only with filter push-down (`paginate` and `filter`).
- **Breaking:** writes and `sum` / `min` / `max` return `Unsupported` errors on both drivers.
- **Breaking:** a row without an id (GraphQL: a null or empty id) is keyed `~row:<pos>`; two
rows with the same id in one response are an error.
- **Breaking:** GraphQL names (fields, root field, arguments, filter keys, order and count fields)
are validated; an invalid name fails before any request.
- GraphQL vistas with `paginate: true` serve `fetch_page`, `fetch_window` and `fetch_next`;
Hasura paged reads order by the id column when no order is set.
- Hasura `count: aggregate` opts in to counting through `<root>_aggregate` (counts still list by
default); `count_field:` overrides its name. A rejected aggregate is an error naming
`count: list`.
- `GraphqlApi::can_page`.
- A spec's `graphql:` block overrides the builder's `Supports` key by key.
- GraphQL ordering reaches the server (`Vista::add_order`); dotted columns are refused.
- GraphQL quicksearch covers `searchable:` columns, or else string-typed non-id columns; none
matches nothing. `%`, `_` and `\` are escaped.
- GraphQL get-by-id honours `Supports` and sends an int id column's value as an integer.
- Hasura `contains` renders `_ilike`.
- `GraphqlApiTableShell` implements `clone_shell`.
- YAML `references:` on GraphQL specs, with `register_yaml`, `build` and `with_resolver`
(`GraphqlSpecResolver`).
- YAML column `field:` aliases a column to a different server field.
- GraphQL strings escape every control character.
- `Supports`, `CountMode`, `RetryPolicy` and `ResilientClientBuilder` are exported from the crate
root.
- REST `column_table_values_expr` lists the table and returns the column (was a panic).
- `RestApi::http_request` returns an error for a bad header name or value (was a panic).
- Depends on `vantage-api-pool` 1.0.0.
## 0.6.20 — 2026-10-04
- The REST and GraphQL vista factories use the shared spec helpers.
- REST conditions resolve deferred values through `Expression::resolve_deferred`.
- Depends on `vantage-expressions` 1.0.1, `vantage-table` 1.1.1, `vantage-vista` 1.1.1.
## 0.6.19 — 2026-10-03
- Spec `lazy:` columns become Vista computed columns, on GraphQL too.
- Depends on `vantage-table` 1.1.0, `vantage-vista` 1.1.0.
## 0.6.18 — 2026-10-03
- Depends on `vantage-core` 1.0.0, `vantage-types` 1.0.0, `vantage-expressions` 1.0.0,
`vantage-dataset` 1.0.0, `vantage-table` 1.0.0, `vantage-vista` 1.0.0, `vantage-api-pool` 0.6.3.
## 0.6.17 — 2026-09-27
- `auth_refresher(AuthRefresher)` on the REST and GraphQL builders: the
bearer token is fetched lazily and fetched again after a 401.
- A REST vista built with `pagination_params(...)` serves windowed fetches
without a `total_key`; a short page marks the end of the data.
- Fix: a window starting mid-page on a page-numbered API returned the page's
first rows, and a page that lost rows to client-side filters or repeated
ids came back short, ending the data early.
- `auth(...)` set after `auth_refresher(...)` replaces the refresher.
- A bare JSON object response is read as one row.
## 0.6.16 — 2026-09-16
- `RestApi::builder().ordering(OrderingParams)`: the API's sort query param
(`?ordering=col`, prefix for descending). With it the REST vista reports
`can_order`, every column is orderable, and a pushed sort is sent with each
windowed fetch instead of being applied client-side.
- A REST call that gives up on a transient failure (5xx, network, open
breaker) logs at debug; only a final answer (4xx, auth) warns.
## 0.6.15 — 2026-09-16
- REST and GraphQL requests go through `vantage-api-pool`'s `ResilientClient`
(parallel cap, growing-cooldown breaker, rate limit, observer hook).
- The retry policy follows `vantage_core::Priority`: `Essential` retries until
the awaiting future is dropped, `Background` (the default) makes one attempt.
- Errors from a non-2xx response carry `status`, `attempts`, `kind` and the
first 500 chars of the server's `body`.
- `RowsPulled` is reported after each parsed page.
- Failures are now reported as `API request failed` / `GraphQL request failed`
with a `kind` attribute (`status`, `transport`, `breaker_open`, `auth`),
replacing the old `… returned error status` messages.
- Retries sleep on the tokio timer, so the runtime must have time enabled.
## 0.6.14 — 2026-09-08
- `add_op_condition` reports `Unimplemented` for `FilterOp::Like` — no
GraphQL dialect here spells a pattern match, so the consumer evaluates it
locally rather than having the filter silently dropped.
## 0.6.13 — 2026-09-05
- Follows vista 0.6.25: a lazy column's script compiles once through
`lazy_value_closure`, and a script that does not parse is reported when the
column is declared rather than on first read.
## 0.6.12 — 2026-08-16
- `GraphqlApiTableShell` implements `preview_query`, returning the endpoint and
the query document.
- `RestApiTableShell` implements `preview_query`, returning the `GET` a read
would issue. It shares the URL builder with the real fetch path, so a previewed
URL cannot drift from the one that gets sent. Conditions that never reach the
server under `FilterStrategy::Client` are reported separately as
`client_side_filters` — the difference between a filter you configured and a
filter the API applies.
- `RestApiTableShell::preview_query` no longer collapses to an error when a URI
template placeholder is filled by a condition that resolves at fetch time. The
real fetch awaits those before building the path; the preview cannot, so it
shows the unfilled template and counts them under `unresolved_conditions`.
## 0.6.11 — 2026-08-16
- GraphQL columns may be dotted paths. `run.commit.hash` groups into a nested
selection set at render time and flattens back to a dotted key on decode, so
a schema that nests its scalars needs no relation machinery to read them. A
null parent leaves the column present and null rather than missing.
- `args:` renders literal root-field arguments — for a field whose input object
is non-null there was previously no way to query it at all. `response_path:`
locates the rows inside a Relay-style connection, and shapes the query as
well as the response, so the two can't drift apart.
- Per-table `filter`/`order`/`search`/`paginate` declare what a root field
really accepts, defaulting to what the dialect implies. The shell implements
`add_op_condition`/`add_order`/`add_search`/`clear_*` and the factory
advertises the matching capabilities; previously every capability was left
false, so consumers sorted and searched in memory even against a Hasura
schema that could have done it server-side.
- With `filter: false` — a root field that takes no arguments at all —
conditions are applied client-side over the decoded rows instead of being
dropped. Vista treats equality push-down as universal, so dropping them
silently widened results; rendering them produced a query the server
rejected. A filter shape that can't be evaluated row-wise now errors rather
than quietly matching everything. A deferred filter value resolves once for
the whole set rather than per row, since resolving it fetches the parent
table; LIKE anchors are honoured, so `foo%` is a prefix rather than a
substring.
## 0.6.10 — 2026-08-12
- **Bug fix:** the REST and GraphQL `TableShell`s resolve relations whose target
constructor returns a typed table. `get_ref_target` went through an
entity-typed downcast that failed, so a reference field's list of eligible
rows came back as an error.
## 0.6.9 — 2026-07-28
- Every REST round trip is timed. Over a second it logs `slow REST GET` at info
with the URL and elapsed time; otherwise `REST GET done` at debug. Both carry
`count_probe`, which distinguishes a request for rows from one made only to
learn a total — the distinction that identified an 8.5s startup as a separate
count query on the open path.
## 0.6.8 — 2026-07-23
- REST and GraphQL `TableShell`s implement `get_ref_target` — the bare,
unconditioned relation target the edit-form reference dropdown lists. REST
handles both YAML-declared references (the resolver-built child with no join
condition) and hand-coded `with_one`/`with_many` registrations; GraphQL
forwards through the factory like `get_ref`. Previously this path returned
`Unimplemented`.
## 0.6.7 — 2026-07-20
- `lazy:` computed columns on REST tables, behind the new `rhai` feature — a
Rhai script runs per returned record via `vantage-vista`'s shared
lazy-expression evaluator. The windowed fetch path (`fetch_window`) applies
them directly, since it bypasses `list_values` where they'd normally run.
## 0.6.6 — 2026-07-16
- GraphQL CBOR bridges and the REST HTTP→CBOR boundary use the shared `vantage-types`
walker (total — no error branch, and tagged values no longer collapse to `null`).
## 0.6.5 — 2026-06-29
- REST: the count probe is quieter under `.debug(true)`. The count query and its
`window (0, 1)` probe GET now log at `debug`, while real data fetches stay at
`info` — so the log shows the fetches that matter without the count-probe noise.
## 0.6.4 — 2026-06-26
- REST: with `.debug(true)`, each GET now logs the resolved request URL
(`target: "vantage_api_client::rest"`) so a failing query can be replayed by hand.
## 0.6.3 — 2026-06-25
- `AnyGraphqlType` declares `null_when: serde_json::Value::Null`, so it implements `InvariantValue`
(vantage-types 0.6.2) and participates in vantage-table's set-invariant enforcement. (REST uses
`ciborium::Value` directly, which already implements the trait.)
## 0.6.2 — 2026-06-20
- REST: fix URL construction when a table path already carries a query string
(e.g. `launches/?mode=detailed`). Pagination and condition params now join with
`&` instead of emitting a second `?`, which the server rejected (HTTP 500).
## 0.6.1 — 2026-06-18
- REST lazy-load: `RestApiBuilder::total_key`/`debug`, `RestApi::fetch_window_records`/`fetch_total`,
a `RestApiTableShell::fetch_window` impl + envelope-total count, and `can_fetch_window` advertised
when a `total_key` is configured. Windows map onto skip-based (raw offset) or page-based
(`offset/limit+1`) pagination.
## 0.6.0 — 2026-06-17
- Coordinated 0.6 release; internal dependencies realigned to 0.6. No public API changes.
## 0.5.7 — 2026-06-06
### Changed
- Formatting only (`cargo fmt`): no functional or API change.
## 0.5.6 — 2026-06-02
- Track `vantage-table`'s new `TableSource::Source` associated type (set to `String`; no
user-visible change).
## 0.5.5 — 2026-05-23
- Align all internal dependency versions to 0.5+. No public API changes.
## 0.5.4 — 2026-05-23
- Doc comment touch-ups tracking
[vantage-table 0.5.2](https://docs.rs/vantage-table/0.5.2/vantage_table/)'s `AnyTable` removal. No
code change.
## 0.5.3 — 2026-05-23
- Dep-pin bump on `vantage-dataset` to 0.5; tracks the `ImTable` / `ImDataSource` parametrization.
No public API change.
## 0.5.2 — 2026-05-23
- GraphQL Vista shell rewritten on a typed `Table<GraphqlApi, EmptyEntity>` — `AnyTable` is gone
from the GraphQL path. `GraphqlApiTableShell` converts `AnyGraphqlType` ↔
[`CborValue`](https://docs.rs/ciborium/latest/ciborium/value/enum.Value.html) at the Vista
boundary via the symmetric `From` impls; the typed table keeps the native value flow for filters
and reference traversal.
- `GraphqlApiTableShell::get_ref` now resolves children through
[`Table::get_ref_from_row`](https://docs.rs/vantage-table/0.5.0/vantage_table/table/struct.Table.html#method.get_ref_from_row) +
a fresh `GraphqlApiVistaFactory::from_table` wrap — same row-based traversal pattern as REST /
MongoDB / SQL.
- [`GraphqlApi::eq_value_condition`](https://docs.rs/vantage-table/0.5.0/vantage_table/traits/table_source/trait.TableSource.html#method.eq_value_condition)
implemented so `Reference::resolve_from_row` can push a row-derived `AnyGraphqlType` join key
without a string round-trip.
- `GraphqlApiTableShell` implements `get_ref_kinds` via the typed table's reference registry. Vista
metadata's `references` map now carries real cardinality flags (`HasOne` / `HasMany`) instead of
the placeholder `HasMany`; target name and FK column remain blank because the typed `Reference`
doesn't expose them.
- Removed `graphql/vista/any_shell.rs` — no remaining consumers.
- Removed `tests/any_table.rs` — both halves are gone now that neither shell routes through
`AnyTable`.
## 0.5.1 — 2026-05-23
- REST Vista shell no longer routes typed `Table::with_many` / `with_one` references through
`AnyTable`. `RestApiTableShell` now carries a typed `Table<RestApi, EmptyEntity>` and
`TableShell::get_ref` resolves children via
[`Table::get_ref_from_row`](https://docs.rs/vantage-table/0.5.0/vantage_table/table/struct.Table.html#method.get_ref_from_row) +
a fresh `RestApiVistaFactory::from_table` wrap — mirroring the pattern used by the MongoDB, SQL,
and SurrealDB drivers.
- [`RestApi::eq_value_condition`](https://docs.rs/vantage-table/0.5.0/vantage_table/traits/table_source/trait.TableSource.html#method.eq_value_condition)
implemented so `Reference::resolve_from_row` can push a row-derived `CborValue` join key onto a
child REST table without a string round-trip.
- `AnyTableShell` is no longer re-exported from `vantage_api_client::rest` (or the crate root /
`prelude`). The legacy adapter survives as a `pub(crate)` helper under `graphql::vista` only as
long as the GraphQL shell still wraps `AnyTable`; the matching GraphQL rewrite ships in the next
release.
- `tests/any_table.rs` drops the REST half — REST no longer participates in the
`AnyTable::from_table` blanket bridge from the Vista layer. The GraphQL half stays until the
GraphQL shell is rewritten.
## 0.5.0 — 2026-05-23
- Bumped to the 0.5 line to track
[vantage-table 0.5.0](https://docs.rs/vantage-table/0.5.0/vantage_table/)'s opening of the
`AnyTable` decommission cycle, aligning with the rest of the workspace. No code changes beyond the
dependency pin.
## 0.1.9 — 2026-05-18
- Tracks [vantage-vista 0.4.10](https://docs.rs/vantage-vista/0.4.10/vantage_vista/)'s
schema-on-source refactor. `RestApiTableShell`, `GraphqlApiTableShell`, and `AnyTableShell` now
own their
[`VistaMetadata`](https://docs.rs/vantage-vista/0.4.10/vantage_vista/struct.VistaMetadata.html)
and implement the new `columns` / `references` / `id_column` shell methods. Factory call sites —
`RestApi::vista_factory(...).from_yaml(...)`, `AnyTableShell::into_vista_with(...)` — are
unchanged.
- Pins `vantage-vista = "0.4.10"`.
## 0.1.8 — 2026-05-16
- Internal dependency version refresh; no public API changes.
## 0.1.7 — 2026-05-16
- REST and GraphQL `TableShell::get_ref` impls updated for
[`Vista::get_ref(relation, &row)`](https://docs.rs/vantage-vista/0.4.7/vantage_vista/struct.Vista.html#method.get_ref)'s
row-based signature.
- REST's YAML-driven traversal drops the `DeferredFn`-based parent-fetch dance: with the parent row
passed in, the shell reads the join field directly and pushes a plain eq-condition on the
resolver-built child Vista. The `with_model_resolver` callback survives for cross-driver inventory
routing.
- The YAML factory's `ReferenceKind::HasForeign → HasMany` workaround is gone now that
`ReferenceKind` is two-variant; declared cardinality flows straight through to the child Vista.
- Pins `vantage-vista = "0.4.7"`, `vantage-table = "0.4.10"`.
## 0.1.6 — 2026-05-16
GraphQL joins REST inside `vantage-api-client`. The crate is now home to two protocol adapters, with
a YAML-driven Vista bridge for each. The example wires the SpaceX public GraphQL API end-to-end.
- **Breaking**: REST internals moved under `vantage_api_client::rest::*`. Crate-root re-exports
(`RestApi`, `RestApiBuilder`, `ResponseShape`, `PaginationParams`, `eq_condition`,
`AnyTableShell`, `RestApiTableShell`, `RestApiVistaFactory`, `RestApiVistaSpec`, `NoApiExtras`)
are unchanged, so most consumers compile without edits. Code that imported through
`vantage_api_client::vista::*` directly now lives at `vantage_api_client::rest::vista::*`.
- **New**:
[`GraphqlApi`](https://docs.rs/vantage-api-client/0.1.6/vantage_api_client/struct.GraphqlApi.html)
— POST-based GraphQL data source. Renders typed query documents with inline filters and
`$limit`/`$offset` variables. Two filter dialects ship out of the box: `Hasura`
(`where: { field: { _eq: v } }`) and `Generic` (`(find: { field: v })`, for hand-rolled schemas
like SpaceX).
- **New**:
[`GraphqlApiVistaFactory`](https://docs.rs/vantage-api-client/0.1.6/vantage_api_client/struct.GraphqlApiVistaFactory.html)
— builds a Vista from either a typed `Table<GraphqlApi, E>` or a hand-maintained YAML schema
(`build_from_spec`). The YAML carries per-table `graphql:` blocks for `root_field`, `dialect`, and
`filter_arg` overrides; column types map to typed Vantage columns (`int`, `bigint`, `bool`,
`float`, `string`, `datetime`, `date`, `time`, `uuid`, `json`).
- **New**: typed condition operators via
[`GraphqlOperation`](https://docs.rs/vantage-api-client/0.1.6/vantage_api_client/trait.GraphqlOperation.html)
— `.eq()`, `.ne()`, `.gt()`/`.gte()`/`.lt()`/`.lte()`, `.in_()`, `.like()`/`.ilike()`,
`.is_null()`/`.is_not_null()`. Blanket-implemented over `Expressive<T>` so typed columns get them
for free.
- **New**: relationship traversal via `with_many`/`with_one`. The adapter peels existing parent
eq-conditions in-sync and falls back to async `DeferredField` resolution otherwise; both paths
render dialect-correct (`_eq` for Hasura, flat for Generic).
- **New**:
[`AnyGraphqlType`](https://docs.rs/vantage-api-client/0.1.6/vantage_api_client/struct.AnyGraphqlType.html)
— type-erased value wrapping `serde_json::Value` with a 15-variant scalar enum (`Null`, `Bool`,
`Int`, `BigInt`, `Float`, `String`, `Id`, `DateTime`, `Date`, `Time`, `Uuid`, `Decimal`, `Json`,
`Object`, `Array`). Built-in Rust impls cover `bool`, `i32`/`i64`/`f64`, `String`/`&str`,
`chrono::DateTime<Utc>`/`NaiveDate`/`NaiveTime`, `uuid::Uuid`, `serde_json::Value`,
`Vec<AnyGraphqlType>`, `IndexMap<String, AnyGraphqlType>`. Downstream crates can plug their own
types onto existing variants, or call `vantage_type_system!` a second time for a fresh variant set
(e.g. a `vantage-graphql-geo` extension).
- **New**: `prelude` module for one-shot imports of both protocols. CBOR bridges round-trip cleanly
through `AnyTable`, so a `Vec<AnyTable>` can mix REST and GraphQL tables.
- **New example**: `examples/graphql_spacex.rs` — YAML-driven CLI over the public SpaceX GraphQL
API. Ten entities (`launches`, `rockets`, `capsules`, `cores`, `ships`, `payloads`, `missions`,
`dragons`, `landpads`, `launchpads`) in `examples/schema/*.yaml`. Run
`cargo run --example graphql_spacex -- launches`. Endpoint overridable via `$SPACEX_ENDPOINT`.
- Dependencies added: `chrono`, `uuid`, `paste` (runtime).
## 0.1.5 — 2026-05-15
YAML-driven model definitions: describe a REST resource in YAML, accumulate a registry, and traverse
references — including across models defined in separate YAML files — without writing Rust factory
closures.
- [`RestApiVistaFactory::register_yaml(&str)`](https://docs.rs/vantage-api-client/0.1.5/vantage_api_client/struct.RestApiVistaFactory.html#method.register_yaml)
parses a `RestApiVistaSpec` and stashes it under `spec.name`.
[`build(name)`](https://docs.rs/vantage-api-client/0.1.5/vantage_api_client/struct.RestApiVistaFactory.html#method.build)
materialises a `Vista` on demand; references resolve through the same registry so a parent's
`:relation` traversal finds the child's spec without further wiring.
- New table-level `api` block carries `endpoint: parent/{parentId}/child` for APIs that require
path-based filtering. Defaults to `spec.name` when absent — declaring `name: users` is enough for
a plain `/users` resource.
- [`RestApiVistaFactory::with_model_resolver(Arc<dyn Fn(&str) -> Result<Vista>>)`](https://docs.rs/vantage-api-client/0.1.5/vantage_api_client/struct.RestApiVistaFactory.html#method.with_model_resolver)
installs a callback that overrides the internal registry — for cross-driver setups (e.g. a UI
shell whose inventory layer routes some models through SQL and others through REST) the resolver
decides which factory builds each name.
- Cross-cutting public types:
[`ApiTableExtras`](https://docs.rs/vantage-api-client/0.1.5/vantage_api_client/struct.ApiTableExtras.html),
[`ApiTableBlock`](https://docs.rs/vantage-api-client/0.1.5/vantage_api_client/struct.ApiTableBlock.html),
`ApiColumnExtras`, `ApiReferenceExtras`, and
[`ModelResolver`](https://docs.rs/vantage-api-client/0.1.5/vantage_api_client/type.ModelResolver.html).
`NoApiExtras` stays as a legacy alias.
- New `examples/jsonplaceholder_yaml.rs` — same demo as `jsonplaceholder` with all three models in
`data/jsonplaceholder/*.yaml`. Run
`cargo run --example jsonplaceholder_yaml -- users id=1 :albums :photos`.
## 0.1.4 — 2026-05-14
REST API now speaks [`ciborium::Value`](https://docs.rs/ciborium/) end-to-end and bridges into the
universal [`Vista`](https://docs.rs/vantage-vista/0.4.5/vantage_vista/struct.Vista.html) surface.
- **Breaking**: `RestApi::Value` is now `ciborium::Value` instead of `serde_json::Value`.
`eq_condition(field, value)` accepts anything that implements `Into<CborValue>` — so
`eq_condition("userId", 1i64)` and `eq_condition("name", "Alice")` work without a wrapper macro.
Existing callers passing `json!(...)` need to switch to the matching CBOR variant. Records
returned from `list_values` carry `Record<CborValue>` now, which means master tables wrap into
`AnyTable::from_table` directly — no JSON↔CBOR adapter boilerplate at the call site.
- URI templates in table names get substituted from eq-conditions at request time. Declare a child
table as `Table::new("users/{userId}/albums", api)` and a parent's `id=1` condition lowers to
`GET /users/1/albums`, with `userId` peeled out of the query string. Lets `with_many` / `with_one`
traversal hit nested REST endpoints natively.
- [`related_in_condition`](https://docs.rs/vantage-api-client/0.1.4/vantage_api_client/struct.RestApi.html)
is implemented: `with_many` re-keys an existing eq-condition onto the child's FK field, and
`with_one` resolves the parent record on demand through a deferred condition that's executed at
fetch time. No more `unimplemented!()` panic for REST API references.
- New
[`vista_factory`](https://docs.rs/vantage-api-client/0.1.4/vantage_api_client/struct.RestApi.html#method.vista_factory)
method returns a `RestApiVistaFactory` that wraps a typed `Table<RestApi, E>` as a `Vista`.
Columns, id field, title fields, and references are harvested up front; the original entity stays
attached for reference traversal.
- New `examples/jsonplaceholder.rs` demo — model-driven CLI over
<https://jsonplaceholder.typicode.com> using the new Vista bridge and the `vista_cli` runner from
[`vantage-cli-util 0.4.2`](https://docs.rs/vantage-cli-util/0.4.2/vantage_cli_util/). Run
`cargo run --example jsonplaceholder -- users id=1 :albums`.
## 0.1.3 — 2026-05-09
Opt-in
[`RestApiBuilder::no_pagination()`](https://docs.rs/vantage-api-client/0.1.3/vantage_api_client/struct.RestApiBuilder.html#method.no_pagination)
for APIs that don't paginate. ([#230](https://github.com/romaninsh/vantage/pull/230))
```rust
let api = RestApi::builder(base_url).no_pagination().build();
```
FastAPI / Pydantic services often treat unknown query params as strict filters and silently return
empty rows, so adding `_page=1&_limit=50` makes the response empty. Setting this stops vantage from
sending those params, and a perpetual grid asking for page > 1 short-circuits to an empty result so
it actually marks itself exhausted instead of looping. Default behaviour is unchanged.
## 0.1.2 — 2026-04-26
`RestApi::builder` now configures response shape and pagination convention, so the same client works
against APIs with different conventions:
```rust
use vantage_api_client::{RestApi, ResponseShape, PaginationParams, eq_condition};
// JSONPlaceholder: bare-array responses, JSON-Server pagination.
let api = RestApi::builder("https://jsonplaceholder.typicode.com")
.response_shape(ResponseShape::BareArray)
.pagination_params(PaginationParams::page_limit("_page", "_limit"))
.build();
// DummyJSON: wrapped-by-table-name, skip-based pagination.
let api = RestApi::builder("https://dummyjson.com")
.response_shape(ResponseShape::WrappedByTableName)
.pagination_params(PaginationParams::skip_limit("skip", "limit"))
.build();
// Legacy / authed: original 0.1.x default shape `{ "data": [...] }`.
let api = RestApi::builder("https://my-api.example.com")
.response_shape(ResponseShape::Wrapped { array_key: "data".into() })
.auth("Bearer …")
.build();
```
Conditions get pushed into the URL automatically:
```rust
let mut comments = Table::<RestApi, EmptyEntity>::new("comments", api);
comments.add_condition(eq_condition("postId", json!(1)));
// list_values fetches: GET /comments?postId=1
```
What's new:
- `RestApi::builder(base_url)` builder. Methods: `.auth(value)`, `.response_shape(shape)`,
`.pagination_params(params)`, `.build()`. The legacy `RestApi::new(url)` still works and matches
the 0.1.x default.
- `ResponseShape::{BareArray, Wrapped { array_key }, WrappedByTableName}` — covers most public-API
shapes (bare arrays like JSONPlaceholder/GitHub, wrapped-under-fixed-key like the legacy default,
wrapped-under-table-name like DummyJSON).
- `PaginationParams::page_limit(page, limit)` — 1-based page index (JSON Server convention).
`PaginationParams::skip_limit(skip, limit)` — 0-based item offset (DummyJSON convention).
`RestApi` now appends pagination to the URL when a `Pagination` is set on the wrapping `Table`.
- `eq_condition(field, value)` helper for building conditions on `Table<RestApi, _>`. Required
because Rust's orphan rule blocks `Expressive<serde_json::Value>` impls for primitive types in
this crate. Conditions added via `add_condition` are translated to URL query params on
`list_values` (`?field=value`); multiple conditions AND together. Non-eq operators are silently
skipped for now.
- `urlencoding` joins the runtime deps so query params get encoded properly.
## 0.1.1 — 2026-04-19
- Pinned dependency versions for crates.io publishing.