1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//! Resolution of a function-backed root query field (#1329).
//!
//! The engine half of the `request:query` trigger. A query that declares
//! [`function = "<name>"`](crate::schema::QueryDefinition::function) is answered by a
//! sandboxed function instead of by reading a relation, and this is the seam through
//! which the engine reaches one — parallel to
//! [`BeforeMutationGate`](crate::security::BeforeMutationGate), and for the same
//! reason: `fraiseql-core` knows nothing about function runtimes, so it holds a
//! trait object the application supplies. In `fraiseql-server` that object dispatches
//! into the compiled schema's `functions` section.
//!
//! # What the engine keeps
//!
//! Everything except the value. The resolver is asked for the field's *data*; the
//! engine still:
//!
//! - enforces `requires_role` and `requires_actor`, before asking;
//! - applies field-level RBAC to what comes back, so a function-backed field is not a hole in the
//! scope gates every other field passes through;
//! - projects the selection set, recases, and stamps `__typename` with the same projector the SQL
//! path uses.
//!
//! That list is the argument for option A in #1329. A field that resolved outside
//! the engine would have to re-implement every one of them, and would be wrong about
//! one of them within a release.
//!
//! # Root fields only
//!
//! There is no nested-field equivalent and there will not be one. A nested resolver
//! runs once per row, so a function there is an N+1 measured in V8 isolates —
//! ~5–8 ms each, against a documented cold read of ~5–15 ms for the whole query.
//! Root-only makes "one invocation per query" a property of the shape, and the
//! compiler refuses the nested spelling rather than documenting against it.
use ;
use crate::;
/// The field a [`QueryFunctionResolver`] is being asked to answer.
/// Answers a function-backed root query field.
///
/// Registered on [`RuntimeConfig`](crate::runtime::RuntimeConfig) via
/// [`with_query_function_resolver`](crate::runtime::RuntimeConfig::with_query_function_resolver),
/// parallel to
/// [`with_before_mutation_gate`](crate::runtime::RuntimeConfig::with_before_mutation_gate).
///
/// # The return value is the field's data, not a response
///
/// For a single-item field: the entity document, or `null`. For a list field: an
/// array of them. The engine projects the selection set over it, so the guest
/// returns whatever the type declares and never a GraphQL envelope — which also
/// means a guest cannot invent a field the schema does not declare, or return one
/// the caller's scopes deny.