Skip to content

Commit c5c658c

Browse files
committed
Improved Relay XML documentation comments
1 parent 5630cb8 commit c5c658c

3 files changed

Lines changed: 314 additions & 91 deletions

File tree

RELEASE_NOTES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,5 @@
237237
* Added `TimeOnly` GraphQL type
238238
* Added `map` function for Relay `Connection` and `Edge` types
239239
* Excluded `GraphQLWebSocketMiddleware` from exception stack trace if request not a Web Socket
240-
* Changed `GraphQLOptionsDefaults.WebSocketConnectionInitTimeoutInMs` const type to `double`
240+
* Changed `GraphQLOptionsDefaults.WebSocketConnectionInitTimeoutInMs` const type to `double`
241+
* Improved Relay XML documentation comments

src/FSharp.Data.GraphQL.Server.Relay/Connections.fs

Lines changed: 194 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,74 +6,98 @@ namespace FSharp.Data.GraphQL.Server.Relay
66
open FSharp.Data.GraphQL.Types
77
open FSharp.Data.GraphQL.Types.Patterns
88

9-
/// Record used to represent Relay node with cursor identifier.
9+
/// <summary>
10+
/// Represents a Relay edge – an object with a cursor and a node.
11+
/// Edges are used to traverse connections in Relay pagination.
12+
/// </summary>
13+
/// <typeparam name="Node">The type of the node at the end of this edge.</typeparam>
1014
type Edge<'Node> = {
11-
/// Cursor used to identify current node.
15+
/// <summary>Opaque cursor string used to identify this node's position in the connection.</summary>
1216
Cursor : string
13-
/// Object satisfying Relay Node interface definition.
17+
/// <summary>The object at the end of this edge. Must satisfy the Relay Node interface.</summary>
1418
Node : 'Node
1519
}
1620

17-
module Edge =
18-
19-
let map mapping (edge : Edge<'T>) : Edge<'U> =
20-
{ Cursor = edge.Cursor; Node = mapping edge.Node }
21-
22-
/// Record used to represent a information about single page of
23-
/// results to Relay. Relay uses cursor id to identify the order
24-
/// between the pages.
21+
/// <summary>
22+
/// Information about pagination in a Relay connection.
23+
/// Follows the Relay Cursor Connections Specification.
24+
/// </summary>
25+
/// <remarks>
26+
/// PageInfo provides pagination metadata that allows clients to determine
27+
/// whether more pages are available and where to continue pagination.
28+
/// </remarks>
2529
type PageInfo = {
26-
/// Should be true, if the next page is available.
27-
/// False if current page is the last page of results.
30+
/// <summary>
31+
/// Indicates whether more items exist following the current page when paginating forward.
32+
/// Returns <c>false</c> if this is the last page.
33+
/// </summary>
2834
HasNextPage : Async<bool>
29-
/// Should be true, if the previous page is available.
30-
/// False if current page is the first page of results.
35+
/// <summary>
36+
/// Indicates whether more items exist before the current page when paginating backward.
37+
/// Returns <c>false</c> if this is the first page.
38+
/// </summary>
3139
HasPreviousPage : Async<bool>
32-
/// Optional cursor used to identify begining of the results.
40+
/// <summary>
41+
/// The cursor corresponding to the first edge in the current page.
42+
/// Returns <c>None</c> if the page is empty.
43+
/// </summary>
3344
StartCursor : Async<string option>
34-
/// Optional cursor used to identify the end of the results.
45+
/// <summary>
46+
/// The cursor corresponding to the last edge in the current page.
47+
/// Returns <c>None</c> if the page is empty.
48+
/// </summary>
3549
EndCursor : Async<string option>
3650
}
3751

38-
/// Record representing Relay connection object. Connection describes
39-
/// a set of results (Relay nodes) returned from the server. Instead
40-
/// of statically identifying paged results, relay uses notion of the
41-
/// cursor, which allows to track result set windows, while the
42-
/// result set itself may change over time.
52+
/// <summary>
53+
/// Represents a Relay connection – a paginated set of edges with metadata.
54+
/// Follows the Relay Cursor Connections Specification.
55+
/// </summary>
56+
/// <typeparam name="Node">The type of nodes contained in this connection.</typeparam>
57+
/// <remarks>
58+
/// Unlike traditional offset-based pagination, Relay connections use cursors
59+
/// to navigate through results, allowing the result set to change between requests
60+
/// while maintaining consistent pagination behavior.
61+
/// </remarks>
4362
type Connection<'Node> = {
44-
/// Optional value describing total number of results avaiable
45-
/// at the time.
63+
/// <summary>
64+
/// The total count of items in the entire result set, ignoring pagination.
65+
/// Returns <c>None</c> when the count is not available or would be too expensive to compute.
66+
/// </summary>
4667
TotalCount : Async<int option>
47-
/// Information about current results page.
68+
/// <summary>
69+
/// Metadata about the current page, including cursors and availability of adjacent pages.
70+
/// </summary>
71+
/// <seealso cref="PageInfo"/>
4872
PageInfo : PageInfo
49-
/// List of edges (Relay nodes with cursors) returned as results.
73+
/// <summary>
74+
/// The list of edges in the current page. Each edge contains a cursor and a node.
75+
/// </summary>
76+
/// <seealso cref="Edge{T}"/>
5077
Edges : Async<Edge<'Node> seq>
5178
}
5279
// interface seq<'Node> with
5380
// member x.GetEnumerator () = (Seq.map (fun edge -> edge.Node) x.Edges).GetEnumerator()
5481
// member x.GetEnumerator () : System.Collections.IEnumerator = upcast (x :> seq<'Node>).GetEnumerator()
5582

56-
module Connection =
57-
58-
let map mapping (conn : Connection<'T>) : Connection<'U> =
59-
{
60-
TotalCount = conn.TotalCount
61-
PageInfo = conn.PageInfo
62-
Edges = async {
63-
let! edges = conn.Edges
64-
return edges |> Seq.map (Edge.map mapping)
65-
}
66-
}
67-
68-
/// Slice info union describing Relay cursor progression.
83+
/// <summary>
84+
/// Describes pagination direction and parameters for slicing a Relay connection.
85+
/// </summary>
86+
/// <typeparam name="Cursor">The type used to represent cursor values.</typeparam>
87+
/// <remarks>
88+
/// SliceInfo encapsulates the "first/after" (forward) or "last/before" (backward)
89+
/// pagination arguments as defined in the Relay specification.
90+
/// </remarks>
6991
type SliceInfo<'Cursor> =
70-
/// Return page of `first` results `after` provided cursor value.
71-
/// If `after` value was not provided, start from the beginning of
72-
/// the result set.
92+
/// <summary>
93+
/// Forward pagination: retrieve the first N items after a given cursor.
94+
/// If <c>After</c> is <c>ValueNone</c>, starts from the beginning of the result set.
95+
/// </summary>
7396
| Forward of First : int * After : 'Cursor voption
74-
/// Return page of `last` results `before` provided cursor value.
75-
/// If `before` value was not provided, return `last` results of
76-
/// the result set.
97+
/// <summary>
98+
/// Backward pagination: retrieve the last N items before a given cursor.
99+
/// If <c>Before</c> is <c>ValueNone</c>, retrieves the last N items from the end of the result set.
100+
/// </summary>
77101
| Backward of Last : int * Before : 'Cursor voption
78102

79103
member this.PageSize =
@@ -86,24 +110,19 @@ type SliceInfo<'Cursor> =
86110
| Forward (_, after) -> after
87111
| Backward (_, before) -> before
88112

89-
[<RequireQualifiedAccess>]
90-
module Cursor =
91-
[<Literal>]
92-
let Prefix = "arrayconnection"
93-
let toOffset defaultValue cursor =
94-
match cursor with
95-
| GlobalId (Prefix, id) ->
96-
match System.Int32.TryParse id with
97-
| true, num -> num
98-
| false, _ -> defaultValue
99-
| _ -> defaultValue
100-
let ofOffset offset = toGlobalId Prefix (offset.ToString ())
101-
102113
[<AutoOpen>]
103114
module Definitions =
104115

105-
/// Active pattern used to match context arguments in order
106-
/// to construct Relay slice information.
116+
/// <summary>
117+
/// Active pattern that extracts Relay pagination arguments from a GraphQL field context.
118+
/// </summary>
119+
/// <param name="ctx">The GraphQL field resolution context.</param>
120+
/// <returns>
121+
/// <c>ValueSome(Forward)</c> if "first" argument is present (with optional "after"),
122+
/// <c>ValueSome(Backward)</c> if "last" argument is present (with optional "before"),
123+
/// or <c>ValueNone</c> if no pagination arguments are found.
124+
/// </returns>
125+
/// <seealso cref="SliceInfo{T}"/>
107126
[<return: Struct>]
108127
let (|SliceInfo|_|) (ctx : ResolveFieldContext) =
109128
match ctx.TryArg "first", ctx.TryArg "after" with
@@ -115,7 +134,10 @@ module Definitions =
115134
| ValueSome (last), (before) -> ValueSome (Backward (last, before))
116135
| _, _ -> ValueNone
117136

118-
/// Object defintion representing information about pagination in context of Relay connection
137+
/// <summary>
138+
/// GraphQL object type definition for <see cref="PageInfo"/>.
139+
/// Defines the schema for pagination metadata in Relay connections.
140+
/// </summary>
119141
let PageInfo =
120142
Define.Object<PageInfo> (
121143
name = "PageInfo",
@@ -148,12 +170,19 @@ module Definitions =
148170
]
149171
)
150172

151-
/// Converts existing output type defintion into an edge in a Relay connection.
152-
/// <paramref name="nodeType"/> must not be a List.
173+
/// <summary>
174+
/// Creates a GraphQL object type definition for a Relay edge wrapping the specified node type.
175+
/// </summary>
176+
/// <param name="nodeType">The GraphQL output type definition for nodes. Must not be a list type.</param>
177+
/// <typeparam name="Node">The .NET type of nodes in the edge.</typeparam>
178+
/// <returns>An <c>ObjectDef&lt;Edge&lt;'Node&gt;&gt;</c> with "cursor" and "node" fields.</returns>
179+
/// <exception cref="System.Exception">Thrown if <paramref name="nodeType"/> is a list type.</exception>
180+
/// <seealso cref="Edge{T}"/>
181+
/// <seealso cref="ConnectionOf"/>
153182
let EdgeOf (nodeType : #OutputDef<'Node>) =
154183
match nodeType with
155184
| List _ ->
156-
failwith $"{nodeType.ToString ()} cannot be used as a relay Edge or Connection - only non-list type definitions are allowed"
185+
failwith $"{nodeType.ToString ()} cannot be used as a relay Edge or Connection only non-list type definitions are allowed"
157186
| Named n ->
158187
Define.Object<Edge<'Node>> (
159188
name = n.Name + "Edge",
@@ -170,8 +199,17 @@ module Definitions =
170199
)
171200
| _ -> failwithf "Unexpected value of nodeType: %O" nodeType
172201

173-
/// Converts existing output type definition into Relay-compatible connection.
174-
/// <paramref name="nodeType"/> must not be a List.
202+
/// <summary>
203+
/// Creates a GraphQL object type definition for a Relay connection containing the specified node type.
204+
/// </summary>
205+
/// <param name="nodeType">The GraphQL output type definition for nodes. Must not be a list type.</param>
206+
/// <typeparam name="Node">The .NET type of nodes in the connection.</typeparam>
207+
/// <returns>
208+
/// An <c>ObjectDef&lt;Connection&lt;'Node&gt;&gt;</c> with "totalCount", "pageInfo", and "edges" fields.
209+
/// </returns>
210+
/// <exception cref="System.Exception">Thrown if <paramref name="nodeType"/> is a list type.</exception>
211+
/// <seealso cref="Connection{T}"/>
212+
/// <seealso cref="EdgeOf"/>
175213
let ConnectionOf (nodeType : #OutputDef<'Node>) =
176214
let n =
177215
match nodeType with
@@ -194,22 +232,105 @@ module Definitions =
194232
]
195233
)
196234

235+
[<RequireQualifiedAccess>]
236+
module Cursor =
237+
/// <summary>Prefix used for array-based connection cursors when encoding as Global IDs.</summary>
238+
[<Literal>]
239+
let Prefix = "arrayconnection"
240+
241+
/// <summary>
242+
/// Decodes a cursor string to an integer offset.
243+
/// </summary>
244+
/// <param name="defaultValue">The value to return if decoding fails.</param>
245+
/// <param name="cursor">The cursor string to decode (expected to be a Global ID).</param>
246+
/// <returns>The decoded offset, or <paramref name="defaultValue"/> if parsing fails.</returns>
247+
let toOffset defaultValue cursor =
248+
match cursor with
249+
| GlobalId (Prefix, id) ->
250+
match System.Int32.TryParse id with
251+
| true, num -> num
252+
| false, _ -> defaultValue
253+
| _ -> defaultValue
254+
255+
/// <summary>
256+
/// Encodes an integer offset as a cursor string using the Global ID format.
257+
/// </summary>
258+
/// <param name="offset">The zero-based array offset to encode.</param>
259+
/// <returns>An opaque cursor string suitable for use in Relay pagination.</returns>
260+
let ofOffset offset = toGlobalId Prefix (offset.ToString ())
261+
262+
module Edge =
263+
264+
/// <summary>
265+
/// Transforms the node in an edge while preserving the cursor.
266+
/// </summary>
267+
/// <param name="mapping">The function to transform the node from type <typeparamref name="T"/> to type <typeparamref name="U"/>.</param>
268+
/// <param name="edge">The edge to transform.</param>
269+
/// <typeparam name="T">The type of the node in the source edge.</typeparam>
270+
/// <typeparam name="U">The type of the node in the resulting edge.</typeparam>
271+
/// <returns>A new edge with the transformed node and the same cursor.</returns>
272+
let map mapping (edge : Edge<'T>) : Edge<'U> =
273+
{ Cursor = edge.Cursor; Node = mapping edge.Node }
274+
197275
[<RequireQualifiedAccess>]
198276
module Connection =
199277

200-
/// List of argument definitions used to apply
201-
/// Relay's connection forwarding ability.
278+
/// <summary>
279+
/// Transforms a <see cref="Connection{T}"/> into a <see cref="Connection{U}"/>
280+
/// by applying a mapping function to each node while preserving cursor information and pagination metadata.
281+
/// </summary>
282+
/// <param name="mapping">The function to transform nodes from type <typeparamref name="T"/> to type <typeparamref name="U"/>.</param>
283+
/// <param name="conn">The source connection to transform.</param>
284+
/// <typeparam name="T">The type of nodes in the source connection.</typeparam>
285+
/// <typeparam name="U">The type of nodes in the resulting connection.</typeparam>
286+
/// <returns>A new connection with transformed nodes. Cursors, page info, and total count are preserved unchanged.</returns>
287+
/// <seealso cref="Edge.map"/>
288+
let map mapping (conn : Connection<'T>) : Connection<'U> =
289+
{
290+
TotalCount = conn.TotalCount
291+
PageInfo = conn.PageInfo
292+
Edges = async {
293+
let! edges = conn.Edges
294+
return edges |> Seq.map (Edge.map mapping)
295+
}
296+
}
297+
298+
/// <summary>
299+
/// Argument definitions for forward pagination ("first" and "after").
300+
/// Use these when defining GraphQL fields that support forward-only pagination.
301+
/// </summary>
302+
/// <seealso cref="backwardArgs"/>
303+
/// <seealso cref="allArgs"/>
202304
let forwardArgs = [ Define.Input ("first", Nullable IntType); Define.Input ("after", Nullable StringType) ]
203305

204-
/// List of argument definitions used to apply
205-
/// Relay's connection backwarding ability.
306+
/// <summary>
307+
/// Argument definitions for backward pagination ("last" and "before").
308+
/// Use these when defining GraphQL fields that support backward-only pagination.
309+
/// </summary>
310+
/// <seealso cref="forwardArgs"/>
311+
/// <seealso cref="allArgs"/>
206312
let backwardArgs = [ Define.Input ("last", Nullable IntType); Define.Input ("before", Nullable StringType) ]
207313

208-
/// List of argument definitions used to apply
209-
/// Relay's ability to move connections forwards and backwards.
314+
/// <summary>
315+
/// Complete set of argument definitions for bidirectional pagination.
316+
/// Combines <see cref="forwardArgs"/> and <see cref="backwardArgs"/>.
317+
/// Use these when defining GraphQL fields that support both forward and backward pagination.
318+
/// </summary>
210319
let allArgs = forwardArgs @ backwardArgs
211320

212-
/// Construct a Relay Connection object from the provided array.
321+
/// <summary>
322+
/// Creates a Relay connection from an array of nodes using array indices as cursors.
323+
/// </summary>
324+
/// <param name="array">The array of nodes to convert into a connection.</param>
325+
/// <typeparam name="Node">The type of nodes in the array.</typeparam>
326+
/// <returns>
327+
/// A <see cref="Connection{T}"/> containing all items from the array.
328+
/// The <see cref="PageInfo"/> indicates this is a complete result set (no adjacent pages).
329+
/// </returns>
330+
/// <remarks>
331+
/// This is a convenience function for simple scenarios. For proper pagination,
332+
/// use slicing based on <see cref="SliceInfo{T}"/>.
333+
/// </remarks>
213334
let ofArray array =
214335
let edges =
215336
array

0 commit comments

Comments
 (0)