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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,25 @@ This file is the authoritative user-facing release chronology. The former
[release-notes ledger](docs/archive/2026/graphql-orm-release-notes.md) is retained
for historical context.

## 0.33.5 - 2026-10-01

Companion macros crate: `graphql-orm-macros` **0.33.5**.

- Fix generated relationship resolvers returning preloaded objects before current
target authorization and complete ownership-key resolution. Resolve the
authoritative database target even when a snapshot/cache was populated.
- Check current entity, row and selected-field policies. Nullable target denial
preserves the parent and reports a sanitized error on the relationship path.
Missing or ownership-mismatched targets ordinarily return null without errors.
- Apply SQL visibility before relation pages/counts, partition batches by current
predicate and database identity, and retain the existing uncached loader type.
Callback-only to-many policies require an explicit host scan budget and fail
closed on exhaustion; retain only the bounded visible page in memory.
- Add executable cross-crate SQLite/PostgreSQL regressions and a SQL-free
authoritative relationship example. Existing SDL/cursor formats are unchanged.

No stored-data migration or release publication is included.

## 0.33.4 - 2026-10-01

Companion macros crate: `graphql-orm-macros` **0.33.4**.
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 @@ -37,7 +37,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.33.4", default-features = false }
graphql-orm = { path = "crates/graphql-orm", version = "0.33.5", default-features = false }
graphql-orm-ai-tool-profiles = { path = "crates/graphql-orm-ai-tool-profiles", version = "0.15.0" }
graphql-orm-backup = { path = "crates/graphql-orm-backup", version = "0.7.2", default-features = false }
graphql-orm-operation-catalog = { path = "crates/graphql-orm-operation-catalog", version = "0.4.0" }
Expand Down
26 changes: 26 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,32 @@ supersedes: []
`graphql-orm` is distributed from GitHub only. Use a reviewed full 40-character commit in `rev`;
neither the runtime nor macros crate is published to crates.io.

## 0.33.4 to 0.33.5: authoritative generated relationship access

Adopt runtime and macros 0.33.5 together. Generated relationship resolvers no
longer trust preloaded objects as authoritative targets. Install `Database` in
request/schema data; target tables must remain available for current resolution.
Declare complete tenant/identity bindings. Existing public declarations, SDL and
cursor formats are unchanged; snapshots and cached objects grant no authority.

Entity, current row and selected field policies are enforced at traversal.
A denied nullable target is null with a safe child-path error; missing or moved
ownership targets ordinarily return null without errors. Field policy callbacks
may run during traversal preflight as well as ordinary field resolution; both
receive the actual child field context, including its arguments and alias/path.

For bounded batched pages/counts prefer a `ReadVisibility::Complete` SQL predicate.
Callback-only/prefilter policies on to-many links now require the existing
`AuthorizedScanConfig` on `Database`. The resolver scans bounded candidate batches,
counts authorized rows across the complete result and retains only the requested
visible window. Missing configuration or insufficient budget returns a safe error
instead of a partial count/exhaustion claim. This corrective behavior replaces
previously unchecked relation row policies. Concurrent external DML is not a
snapshot-isolation guarantee across separate read statements.

See [the authoritative relationship example](crates/graphql-orm-macros/fixtures/cross-crate-relations/source-models/examples/authorized_links.rs)
and [the relation contract](docs/reference/graphql-orm/entities-and-relations.md).

## 0.33.3 to 0.33.4: generated cross-crate relationships

Adopt runtime and macros 0.33.4 together from the same reviewed published tag.
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.33.4"
version = "0.33.5"
edition = "2024"
authors = ["Toby Martin"]
description = "Procedural macros for async-graphql and ORM-backed entities, relations, and CRUD operations."
Expand Down
6 changes: 3 additions & 3 deletions crates/graphql-orm-macros/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ macro/runtime versions aligned:

```toml
[dependencies]
graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.33.4", default-features = false, features = ["sqlite"] }
graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.33.5", default-features = false, features = ["sqlite"] }
```

Direct use is supported for tooling that needs the macro package:

```toml
graphql-orm-macros = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.33.4", default-features = false, features = ["sqlite"] }
graphql-orm-macros = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.33.5", default-features = false, features = ["sqlite"] }
```

The direct dependency still requires a compatible `graphql-orm` runtime in the
Expand Down Expand Up @@ -166,6 +166,6 @@ aggregate enums keep their GraphQL derive and naming attributes. See the
[external runtime consumer](../graphql-orm/tests/fixtures/repository-aggregate-consumer/src/lib.rs)
for compiled plain Rust aggregate calls without a direct async-graphql dependency.

Version 0.33.4 gives the repository aggregate compatibility fix a distinct package
Version 0.33.3 gives the repository aggregate compatibility fix a distinct package
identity after the independent 0.33.2 fixture maintenance. Generated behavior is
unchanged from the reviewed aggregate fix.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! Disposable, SQL-free host example: cached snapshots grant no target access.
#[cfg(feature = "sqlite")]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
use async_graphql::{
Context, EmptyMutation, EmptySubscription, Object, Schema, dataloader::DataLoader,
};
use cross_crate_source_models::Session;
use cross_crate_target_models::{CreateEndpointInput, Endpoint};
use graphql_orm::{db::Database, graphql::loaders::RelationLoader, prelude::*};
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
struct Policy(Arc<AtomicBool>);
impl EntityPolicy<SqliteBackend> for Policy {
fn can_access_entity<'a>(
&'a self,
_: Option<&'a Context<'_>>,
_: &'a Database<SqliteBackend>,
entity: &'static str,
_: Option<&'static str>,
kind: EntityAccessKind,
_: EntityAccessSurface,
) -> graphql_orm::futures::future::BoxFuture<'a, async_graphql::Result<bool>> {
Box::pin(async move {
Ok(entity != "Endpoint"
|| kind != EntityAccessKind::Read
|| self.0.load(Ordering::SeqCst))
})
}
}
struct Query;
#[Object]
impl Query {
async fn session(&self) -> Session {
Session {
id: "session-1".into(),
endpoint_id: "endpoint-1".into(),
tenant_id: "tenant-1".into(),
endpoint: Some(Endpoint {
id: "forged".into(),
tenant_id: "tenant-2".into(),
name: "private snapshot".into(),
}),
}
}
}
let allowed = Arc::new(AtomicBool::new(true));
let pool = graphql_orm::sqlx::sqlite::SqlitePoolOptions::new()
.max_connections(1)
.connect("sqlite::memory:")
.await?;
let db = Database::<SqliteBackend>::with_entity_policy(pool, Policy(allowed.clone()));
let plan = db
.schema()
.plan_migration_to_entities("example", "owned fixture", &[Endpoint::metadata()])
.await?;
db.schema()
.apply_migration(&plan, ApplyOptions::default())
.await?;
Endpoint::insert(
&db,
CreateEndpointInput {
id: "endpoint-1".into(),
tenant_id: "tenant-1".into(),
name: "Current authorized target".into(),
},
)
.await?;
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
.data(db.clone())
.data("example-user".to_owned())
.data(DataLoader::new(
RelationLoader::<Endpoint, SqliteBackend>::new(db),
tokio::spawn,
))
.finish();
let query =
"{ session { id link: endpoint { ...Target } } } fragment Target on Endpoint { id name }";
let current = schema.execute(query).await;
assert!(current.errors.is_empty());
assert_eq!(
current.data.into_json()?["session"]["link"]["name"],
"Current authorized target"
);
allowed.store(false, Ordering::SeqCst);
let denied = schema.execute(query).await;
let data = denied.data.into_json()?;
assert_eq!(data["session"]["id"], "session-1");
assert!(data["session"]["link"].is_null());
assert_eq!(denied.errors[0].message, "forbidden");
println!("current target resolved; denied nullable target is null; parent preserved");
Ok(())
}
#[cfg(not(feature = "sqlite"))]
fn main() {
println!("Run with the sqlite feature for disposable execution.");
}
Loading
Loading