Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ for historical context.

## Unreleased

Companion runtime/macros version: **0.59.0**.
Companion runtime/macros version: **0.60.0**.

- Add explicit `query_shapes = "list,keyset,scan"` selection on generated entities. Omitted query shapes disappear from both the GraphQL root and operation catalogue, allowing a host to retain a differently shaped legacy singleton during migration. Default generation remains unchanged; repository APIs, relations, mutations and subscriptions are unaffected.

- Extend explicit `timestamp = "host"` to public GraphQLEntity fields and ordinary String timestamps (including optional forms). Caller-supplied source timestamps stay unchanged instead of receiving the ORM mutation clock. Existing automatic timestamp behavior remains the default. Policies, hooks and field restrictions still apply.

Expand All @@ -26,6 +28,9 @@ Companion runtime/macros version: **0.59.0**.

- Generate static keyset reads and authorized scans for entities with composite primary keys. Configured order must end with the complete primary-key tuple in declaration order, with no duplicate order fields. Every component is retained in cursor ordering; existing authorization, limits and read-only schema posture remain in force.

- Omit unselected query resolver methods entirely, including their GraphQL-only
argument attributes, so list-only generated roots compile beside legacy roots.

## 0.57.0 (2026-10-22)

- Reject private configured cursor fields despite explicit read/order surface
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ cynic-parser = { version = "=0.11.2", features = ["pretty"] }
futures = "0.3"
getrandom = "0.3"
graphql-composition = "=0.12.2"
graphql-orm = { path = "crates/graphql-orm", version = "0.59.0", default-features = false }
graphql-orm = { path = "crates/graphql-orm", version = "0.60.0", default-features = false }
graphql-orm-ai-tool-profiles = { path = "crates/graphql-orm-ai-tool-profiles", version = "0.16.1" }
graphql-orm-backup = { path = "crates/graphql-orm-backup", version = "0.10.0", default-features = false }
graphql-orm-operation-catalog = { path = "crates/graphql-orm-operation-catalog", version = "0.4.1" }
Expand Down
8 changes: 6 additions & 2 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ workspace release tag for every chosen workspace package and record its full
commit from the release manifest. Neither the runtime nor macros crate is
published to crates.io.

## 0.57.0 to 0.58.0: public host-owned timestamps
## 0.59.0 to 0.60.0: generated query-shape selection

Upgrade runtime/macros together. Public GraphQLEntity and RepositoryEntity timestamp fields may explicitly use `#[graphql_orm(timestamp = "host")]` on ordinary `i64`, `String`, or optional forms whose Rust or physical name is `created_at` or `updated_at`. For an external source timestamp mapped to physical `updated_at`, opt in on the mapped field to prevent automatic epoch assignment. Values are bound unchanged; String content is caller-owned and is not parsed or normalized as a date. Existing automatic behavior remains unchanged without the annotation. The annotation does not change stored type/default metadata; conventional Rust names still need `default = false` to suppress their implicit default when appropriate. Schema-only, generated/version/key/relation fields and date/JSON/boolean adapters remain rejected. No data or schema migration is required merely to opt in; preserve field/row authorization and distinguish source observation time from collection and mutation clocks.
Upgrade runtime/macros together. `#[graphql_entity(query_shapes = "list,keyset,scan")]` exposes only those generated query shapes. Supported selectors are `single`, `list`, `keyset`, `scan`, `search` and `aggregate`. The list must be nonempty and contain no duplicates; selected optional capabilities must already be configured. An omitted selector retains all existing generated queries. Disabled shapes are absent from both SDL and operation/semantic metadata; repository methods and entity relations remain available. Mutation and subscription exposure stay independent. This permits a recorded legacy root to coexist while its generated same-name query is deliberately omitted; it grants no authorization and does not make the legacy root an accepted permanent exception. No data or schema migration is required.

## 0.58.0 to 0.59.0: composite-primary-key keysets

Expand All @@ -34,6 +34,10 @@ Ordering, fingerprints and cursor bytes remain unchanged for authorized reads.

Upgrade runtime/macros together. Composite-key entities may now declare `keyset = "rank desc, partition_key asc, item_no asc"` when `(partition_key, item_no)` is the primary key in declaration order. Every primary-key member must appear as the final unique tiebreaker tuple; order fields may not repeat. The generated keyset and scan read surfaces use the existing bounded, authorized cursor implementation. Existing valid single-key orders/cursors are unchanged. No generated GraphQL writes or subscriptions are enabled for composite-key entities by this change. No data or schema migration is required, and an externally managed source stays externally managed.

## 0.57.0 to 0.58.0: public host-owned timestamps

Upgrade runtime/macros together. Public GraphQLEntity and RepositoryEntity timestamp fields may explicitly use `#[graphql_orm(timestamp = "host")]` on ordinary `i64`, `String`, or optional forms whose Rust or physical name is `created_at` or `updated_at`. For an external source timestamp mapped to physical `updated_at`, opt in on the mapped field to prevent automatic epoch assignment. Values are bound unchanged; String content is caller-owned and is not parsed or normalized as a date. Existing automatic behavior remains unchanged without the annotation. The annotation does not change stored type/default metadata; conventional Rust names still need `default = false` to suppress their implicit default when appropriate. Schema-only, generated/version/key/relation fields and date/JSON/boolean adapters remain rejected. No data or schema migration is required merely to opt in; preserve field/row authorization and distinguish source observation time from collection and mutation clocks.

## 0.56.0 to 0.57.0: exact SQL Server MONEY reads

Upgrade `graphql-orm` and `graphql-orm-macros` together to 0.57.0 using one
Expand Down
2 changes: 1 addition & 1 deletion crates/graphql-orm-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "graphql-orm-macros"
version = "0.59.0"
version = "0.60.0"
edition = "2024"
authors = ["Toby Martin"]
description = "Procedural macros for async-graphql and ORM-backed entities, relations, and CRUD operations."
Expand Down
4 changes: 4 additions & 0 deletions crates/graphql-orm-macros/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ supersedes: []

# `graphql-orm-macros`

Version 0.60.0 adds explicit generated query-shape selection for controlled
coexistence with recorded legacy roots. Defaults are unchanged; see the
[migration guide](../../MIGRATION.md#0590-to-0600-generated-query-shape-selection).

Version 0.58.0 extends explicit host-owned timestamps to public entities and String
values without replacing source timestamps with the mutation clock. See the
[migration guide](../../MIGRATION.md#0570-to-0580-public-host-owned-timestamps).
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions crates/graphql-orm-macros/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub(crate) struct EntityMetadata {
/// Explicit AI execution policy by generated mutation category.
pub(crate) ai_mutations: std::collections::BTreeMap<String, String>,
pub(crate) keyset: Option<String>,
pub(crate) query_shapes: Option<Vec<String>>,
pub(crate) backup_enabled: Option<bool>,
pub(crate) backup_export_order: Option<i32>,
pub(crate) backup_restore_order: Option<i32>,
Expand Down Expand Up @@ -269,6 +270,26 @@ pub(crate) fn parse_entity_metadata(attrs: &[syn::Attribute]) -> syn::Result<Ent
}
Ok(())
})?;
} else if meta.path.is_ident("query_shapes") {
if attribute_name == "repository_entity" {
return Err(meta.error("query_shapes requires a GraphQL entity"));
}
if metadata.query_shapes.is_some() {
return Err(meta.error("query_shapes may only be declared once"));
}
let lit: syn::LitStr = meta.value()?.parse()?;
let mut shapes = Vec::new();
for shape in lit.value().split(',').map(str::trim) {
if !matches!(shape, "single" | "list" | "keyset" | "scan" | "search" | "aggregate") {
return Err(syn::Error::new(lit.span(), "query_shapes accepts single, list, keyset, scan, search, aggregate"));
}
if shapes.iter().any(|existing| existing == shape) {
return Err(syn::Error::new(lit.span(), "query_shapes must not repeat a shape"));
}
shapes.push(shape.to_owned());
}
shapes.sort();
metadata.query_shapes = Some(shapes);
} else if meta.path.is_ident("keyset") {
let value = meta.value()?;
let lit: syn::LitStr = value.parse()?;
Expand Down
Loading
Loading