Skip to content

Commit 10e2711

Browse files
committed
feat: GQL catalog entry extension
1 parent e453bce commit 10e2711

16 files changed

Lines changed: 2017 additions & 0 deletions

File tree

Directory.Build.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<PackageReference Remove="Flowthru" />
2121
<PackageReference Remove="Flowthru.Extensions.Python" />
2222
<PackageReference Remove="Flowthru.Extensions.EFCore" />
23+
<PackageReference Remove="Flowthru.Extensions.GQL" />
2324
<PackageReference Remove="Flowthru.Misc.ML" />
2425

2526
<!-- Add Flowthru ProjectReferences -->

Flowthru.slnx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<Project Path="src\extensions\Flowthru.Extensions.Csv\Flowthru.Extensions.Csv.csproj" />
1111
<Project Path="src\extensions\Flowthru.Extensions.Excel\Flowthru.Extensions.Excel.csproj" />
1212
<Project Path="src\extensions\Flowthru.Extensions.EFCore\Flowthru.Extensions.EFCore.csproj" />
13+
<Project Path="src\extensions\Flowthru.Extensions.GQL\Flowthru.Extensions.GQL.csproj" />
1314
<Project Path="src\extensions\Flowthru.Extensions.MLNet\Flowthru.Extensions.MLNet.csproj" />
1415
<Project Path="src\extensions\Flowthru.Extensions.Parquet\Flowthru.Extensions.Parquet.csproj" />
1516
<Project Path="src\extensions\Flowthru.Extensions.Python\Flowthru.Extensions.Python.csproj" />
@@ -24,6 +25,7 @@
2425
<Project Path="tests\Flowthru.FUnit.Tests\Flowthru.FUnit.Tests.csproj" />
2526
<Project Path="tests\Flowthru.Extensions.Python.Tests\Flowthru.Extensions.Python.Tests.csproj" />
2627
<Project Path="tests\Flowthru.Extensions.EFCore.Tests\Flowthru.Extensions.EFCore.Tests.csproj" />
28+
<Project Path="tests\Flowthru.Extensions.GQL.Tests\Flowthru.Extensions.GQL.Tests.csproj" />
2729
</Folder>
2830
<Folder Name="/examples/">
2931
<Project Path="examples\starter\SpaceflightsEFCore\SpaceflightsEFCore.csproj" />
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SOURCE_TYPE=repo
2+
SOURCE_ADDRESS=https://github.com/ChilliCream/graphql-platform.git
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
3+
"name": "xdocs",
4+
"sourceRoot": "docs/reference/misc/external",
5+
"projectType": "library",
6+
"targets": {
7+
"list": {
8+
"executor": "nx:run-commands",
9+
"options": {
10+
"commands": [
11+
"./_utils/list-script.sh"
12+
],
13+
"cwd": "docs/reference/misc/external",
14+
"parallel": false
15+
}
16+
},
17+
"pull": {
18+
"executor": "nx:run-commands",
19+
"options": {
20+
"command": "./_utils/pull-script.sh",
21+
"cwd": "docs/reference/misc/external",
22+
"parallel": false
23+
}
24+
},
25+
"pull:all": {
26+
"executor": "nx:run-commands",
27+
"options": {
28+
"commands": [
29+
"./_utils/pull-all-script.sh"
30+
],
31+
"cwd": "docs/reference/misc/external",
32+
"parallel": false
33+
}
34+
},
35+
"clear": {
36+
"executor": "nx:run-commands",
37+
"options": {
38+
"commands": [
39+
"rm -rf ./*/repo"
40+
],
41+
"cwd": "docs/reference/misc/external",
42+
"parallel": false
43+
}
44+
},
45+
"add": {
46+
"executor": "nx:run-commands",
47+
"options": {
48+
"command": "./_utils/nx-args-helper.sh ./_utils/add-script.sh",
49+
"cwd": "docs/reference/misc/external",
50+
"parallel": false
51+
}
52+
}
53+
},
54+
"tags": [ ]
55+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SOURCE_TYPE=repo
2+
SOURCE_ADDRESS=https://github.com/testcontainers/testcontainers-dotnet.git
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using Flowthru.Core.Data;
2+
using Flowthru.Core.Data.Storage;
3+
using StrawberryShake;
4+
5+
namespace Flowthru.Extensions.GQL.Data;
6+
7+
/// <summary>
8+
/// Factory methods for creating collection GQL catalog entries.
9+
/// </summary>
10+
public static partial class GqlItemFactory
11+
{
12+
/// <summary>
13+
/// Factory methods for <see cref="Item{T}"/> backed by a collection GraphQL query.
14+
/// </summary>
15+
public static class Enumerable
16+
{
17+
/// <summary>
18+
/// Creates a non-paginated collection catalog entry from a StrawberryShake query.
19+
/// The server is expected to return all results in a single response.
20+
/// </summary>
21+
/// <typeparam name="TResult">
22+
/// The StrawberryShake-generated result data type (e.g. <c>IGetUsersResult</c>).
23+
/// </typeparam>
24+
/// <typeparam name="T">
25+
/// The target element type (e.g. <c>GetUsers_User</c>).
26+
/// </typeparam>
27+
/// <param name="label">Catalog entry label used in the pipeline DAG and validation messages.</param>
28+
/// <param name="queryFunc">Delegate that executes the StrawberryShake query operation.</param>
29+
/// <param name="selectData">
30+
/// Projects the result data envelope to the collection of <typeparamref name="T"/>.
31+
/// Return <c>null</c> to yield empty (subject to <paramref name="allowEmptyData"/>).
32+
/// </param>
33+
/// <param name="allowEmptyData">
34+
/// If <c>true</c>, an empty or null result collection is valid during pre-flight inspection.
35+
/// Defaults to <c>false</c>.
36+
/// </param>
37+
public static Item<IEnumerable<T>> Query<TResult, T>(
38+
string label,
39+
Func<CancellationToken, Task<IOperationResult<TResult>>> queryFunc,
40+
Func<TResult, IEnumerable<T>?> selectData,
41+
bool allowEmptyData = false
42+
)
43+
where TResult : class
44+
where T : class
45+
{
46+
var adapter = new GqlEnumerableStorageAdapter<TResult, T>(
47+
label,
48+
queryFunc,
49+
selectData,
50+
allowEmptyData
51+
);
52+
return new Item<IEnumerable<T>>(label, adapter);
53+
}
54+
55+
/// <summary>
56+
/// Creates a Relay cursor-paginated collection catalog entry.
57+
/// The adapter iterates pages until <c>HasNextPage</c> is <c>false</c>, yielding
58+
/// a flat <c>IEnumerable&lt;T&gt;</c> to the pipeline.
59+
/// </summary>
60+
/// <typeparam name="TResult">
61+
/// The StrawberryShake-generated result data type (e.g. <c>IGetSessionsResult</c>).
62+
/// </typeparam>
63+
/// <typeparam name="T">
64+
/// The target element type (e.g. <c>GetSessions_Session</c>).
65+
/// </typeparam>
66+
/// <param name="label">Catalog entry label used in the pipeline DAG and validation messages.</param>
67+
/// <param name="pagedQueryFunc">
68+
/// Delegate accepting <c>(cursor, pageSize, cancellationToken)</c>. Map <c>cursor</c> to
69+
/// the GraphQL <c>after</c> argument and <c>pageSize</c> to <c>first</c>.
70+
/// </param>
71+
/// <param name="pagination">
72+
/// Relay pagination strategy created via <see cref="Pagination.Relay{TResult,T}"/>.
73+
/// </param>
74+
/// <param name="pageSize">Items to fetch per page. Defaults to 100.</param>
75+
/// <param name="allowEmptyData">
76+
/// If <c>true</c>, an empty result set is valid during pre-flight inspection.
77+
/// Defaults to <c>false</c>.
78+
/// </param>
79+
public static Item<IEnumerable<T>> PagedQuery<TResult, T>(
80+
string label,
81+
Func<string?, int, CancellationToken, Task<IOperationResult<TResult>>> pagedQueryFunc,
82+
RelayPaginationStrategy<TResult, T> pagination,
83+
int pageSize = 100,
84+
bool allowEmptyData = false
85+
)
86+
where TResult : class
87+
where T : class
88+
{
89+
var adapter = new GqlEnumerableStorageAdapter<TResult, T>(
90+
label,
91+
pagedQueryFunc,
92+
pagination,
93+
pageSize,
94+
allowEmptyData
95+
);
96+
return new Item<IEnumerable<T>>(label, adapter);
97+
}
98+
99+
/// <summary>
100+
/// Creates an offset-paginated collection catalog entry.
101+
/// The adapter advances the offset until all items (per <c>getTotal</c>) are fetched
102+
/// or a page returns no items, yielding a flat <c>IEnumerable&lt;T&gt;</c> to the pipeline.
103+
/// </summary>
104+
/// <typeparam name="TResult">
105+
/// The StrawberryShake-generated result data type (e.g. <c>IGetProductsResult</c>).
106+
/// </typeparam>
107+
/// <typeparam name="T">
108+
/// The target element type (e.g. <c>GetProducts_Product</c>).
109+
/// </typeparam>
110+
/// <param name="label">Catalog entry label used in the pipeline DAG and validation messages.</param>
111+
/// <param name="pagedQueryFunc">
112+
/// Delegate accepting <c>(offset, limit, cancellationToken)</c>. Map directly to the
113+
/// GraphQL <c>skip</c>/<c>take</c> (or equivalent) arguments.
114+
/// </param>
115+
/// <param name="pagination">
116+
/// Offset pagination strategy created via <see cref="Pagination.Offset{TResult,T}"/>.
117+
/// </param>
118+
/// <param name="pageSize">Items to fetch per page. Defaults to 100.</param>
119+
/// <param name="allowEmptyData">
120+
/// If <c>true</c>, an empty result set is valid during pre-flight inspection.
121+
/// Defaults to <c>false</c>.
122+
/// </param>
123+
public static Item<IEnumerable<T>> PagedQuery<TResult, T>(
124+
string label,
125+
Func<int, int, CancellationToken, Task<IOperationResult<TResult>>> pagedQueryFunc,
126+
OffsetPaginationStrategy<TResult, T> pagination,
127+
int pageSize = 100,
128+
bool allowEmptyData = false
129+
)
130+
where TResult : class
131+
where T : class
132+
{
133+
var adapter = new GqlEnumerableStorageAdapter<TResult, T>(
134+
label,
135+
pagedQueryFunc,
136+
pagination,
137+
pageSize,
138+
allowEmptyData
139+
);
140+
return new Item<IEnumerable<T>>(label, adapter);
141+
}
142+
}
143+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using Flowthru.Core.Data;
2+
using Flowthru.Core.Data.Storage;
3+
using Flowthru.Extensions.GQL.Data;
4+
using StrawberryShake;
5+
6+
namespace Flowthru.Extensions.GQL.Data;
7+
8+
/// <summary>
9+
/// Factory methods for creating single-item GQL catalog entries.
10+
/// </summary>
11+
public static partial class GqlItemFactory
12+
{
13+
/// <summary>
14+
/// Factory methods for <see cref="Item{T}"/> backed by a single-item GraphQL query.
15+
/// </summary>
16+
public static class Single
17+
{
18+
/// <summary>
19+
/// Creates a read-only single-item catalog entry from a StrawberryShake query.
20+
/// </summary>
21+
/// <typeparam name="TResult">
22+
/// The StrawberryShake-generated result data type (e.g. <c>IGetCurrentUserResult</c>).
23+
/// </typeparam>
24+
/// <typeparam name="T">
25+
/// The target type surfaced to the catalog entry (e.g. <c>GetCurrentUser_Me</c>).
26+
/// </typeparam>
27+
/// <param name="label">Catalog entry label used in the pipeline DAG and validation messages.</param>
28+
/// <param name="queryFunc">Delegate that executes the StrawberryShake query operation.</param>
29+
/// <param name="selectData">
30+
/// Projects the result data envelope to the target type.
31+
/// Use a null-forgiving operator (<c>r => r.Me!</c>) when the field is non-null by schema contract.
32+
/// </param>
33+
/// <param name="allowEmptyData">
34+
/// If <c>true</c>, a null <see cref="IOperationResult{TResultData}.Data"/> is treated as
35+
/// valid during pre-flight inspection. Defaults to <c>false</c>.
36+
/// </param>
37+
public static Item<T> Query<TResult, T>(
38+
string label,
39+
Func<CancellationToken, Task<IOperationResult<TResult>>> queryFunc,
40+
Func<TResult, T> selectData,
41+
bool allowEmptyData = false
42+
)
43+
where TResult : class
44+
where T : class
45+
{
46+
var adapter = new GqlStorageAdapter<TResult, T>(
47+
label,
48+
queryFunc,
49+
selectData,
50+
allowEmptyData: allowEmptyData
51+
);
52+
return new Item<T>(label, adapter);
53+
}
54+
55+
/// <summary>
56+
/// Creates a read-write single-item catalog entry from a StrawberryShake query and mutation.
57+
/// </summary>
58+
/// <typeparam name="TResult">
59+
/// The StrawberryShake-generated result data type (e.g. <c>IGetCurrentUserResult</c>).
60+
/// </typeparam>
61+
/// <typeparam name="T">
62+
/// The target type surfaced to the catalog entry (e.g. <c>GetCurrentUser_Me</c>).
63+
/// </typeparam>
64+
/// <param name="label">Catalog entry label used in the pipeline DAG and validation messages.</param>
65+
/// <param name="queryFunc">Delegate that executes the StrawberryShake query operation.</param>
66+
/// <param name="selectData">Projects the result data envelope to the target type.</param>
67+
/// <param name="mutationFunc">
68+
/// Delegate that executes the StrawberryShake mutation when the catalog entry is saved.
69+
/// Enables <c>StorageTraits.CanWrite = true</c> on the resulting entry.
70+
/// </param>
71+
/// <param name="allowEmptyData">
72+
/// If <c>true</c>, a null <see cref="IOperationResult{TResultData}.Data"/> is treated as
73+
/// valid during pre-flight inspection. Defaults to <c>false</c>.
74+
/// </param>
75+
public static Item<T> Query<TResult, T>(
76+
string label,
77+
Func<CancellationToken, Task<IOperationResult<TResult>>> queryFunc,
78+
Func<TResult, T> selectData,
79+
Func<T, CancellationToken, Task<IOperationResult>> mutationFunc,
80+
bool allowEmptyData = false
81+
)
82+
where TResult : class
83+
where T : class
84+
{
85+
var adapter = new GqlStorageAdapter<TResult, T>(
86+
label,
87+
queryFunc,
88+
selectData,
89+
mutationFunc,
90+
allowEmptyData
91+
);
92+
return new Item<T>(label, adapter);
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)