Skip to content

Commit c993ab3

Browse files
committed
feat: gql query object for better control over materializing data
1 parent 132eadf commit c993ab3

9 files changed

Lines changed: 1313 additions & 22 deletions

File tree

.github/workflows/release.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,17 @@ on:
77
- "**.md"
88
- "docs/**"
99
- ".github/instructions/**"
10-
workflow_dispatch: ~
10+
workflow_dispatch:
11+
inputs:
12+
force_bump:
13+
description: "Force a version bump type (bypasses conventional commits). Defaults to 'minor' on manual dispatch."
14+
required: false
15+
default: minor
16+
type: choice
17+
options:
18+
- minor
19+
- patch
20+
- major
1121

1222
concurrency:
1323
group: ${{ github.workflow }}-${{ github.ref }}
@@ -127,7 +137,8 @@ jobs:
127137
CI: "true"
128138
run: |
129139
OLD_VERSION=$(node -p "require('./package.json').version")
130-
node scripts/release.mjs --from="${{ steps.last-published.outputs.tag }}"
140+
FORCE_BUMP_FLAG=${{ inputs.force_bump && format('--force-bump={0}', inputs.force_bump) || '' }}
141+
node scripts/release.mjs --from="${{ steps.last-published.outputs.tag }}" ${FORCE_BUMP_FLAG}
131142
NEW_VERSION=$(node -p "require('./package.json').version")
132143
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
133144
if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ The project uses NX for task orchestration. Common commands:
105105

106106
```bash
107107
dotnet build # Confirm solution builds fully
108-
nx run affected -t test # Run all test projects affected by current changes
108+
nx run affected -t test # IMPORTANT: Run all test projects affected by current changes
109109
dotnet test # Run all tests for the project.
110110
```
111111

examples/advanced/KedroSpaceflightsGQL/Data/_01_Raw/Catalog.Raw.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,20 @@ public partial class Catalog
5454
CreateItem(() => ItemFactory.Single.Memory<bool>("GqlDatabaseSeeded"));
5555

5656
// ── Raw GQL entries — consumed by the DataProcessing flow ───────────────
57+
//
58+
// These are DEFERRED query handles, not materialized collections. No network I/O happens
59+
// when the catalog is constructed or during pre-flight (beyond a lightweight connectivity
60+
// probe). The step that consumes these entries calls .ToList() to trigger the actual
61+
// GQL fetch — see CreateModelInputTableStep for the materialization point.
5762

5863
/// <summary>
59-
/// Raw company data queried from the GQL server.
64+
/// Deferred GQL query handle for company data. The consuming step materializes this
65+
/// via <c>ToList()</c>, which fires the <c>GetCompanies</c> query against the server.
6066
/// </summary>
61-
public IItem<IEnumerable<IGetCompanies_Companies>> Companies =>
67+
public IItem<GqlQuery<IGetCompaniesResult, IGetCompanies_Companies>> Companies =>
6268
CreateItem(
6369
() =>
64-
GqlItemFactory.Enumerable.Query<IGetCompaniesResult, IGetCompanies_Companies>(
70+
GqlItemFactory.Query.NonPaged<IGetCompaniesResult, IGetCompanies_Companies>(
6571
label: "GQLCompanies",
6672
queryFunc: ct => _client.GetCompanies.ExecuteAsync(ct),
6773
selectData: r => r.Companies,
@@ -70,12 +76,13 @@ public partial class Catalog
7076
);
7177

7278
/// <summary>
73-
/// Raw shuttle data queried from the GQL server.
79+
/// Deferred GQL query handle for shuttle data. The consuming step materializes this
80+
/// via <c>ToList()</c>, which fires the <c>GetShuttles</c> query against the server.
7481
/// </summary>
75-
public IItem<IEnumerable<IGetShuttles_Shuttles>> Shuttles =>
82+
public IItem<GqlQuery<IGetShuttlesResult, IGetShuttles_Shuttles>> Shuttles =>
7683
CreateItem(
7784
() =>
78-
GqlItemFactory.Enumerable.Query<IGetShuttlesResult, IGetShuttles_Shuttles>(
85+
GqlItemFactory.Query.NonPaged<IGetShuttlesResult, IGetShuttles_Shuttles>(
7986
label: "GQLShuttles",
8087
queryFunc: ct => _client.GetShuttles.ExecuteAsync(ct),
8188
selectData: r => r.Shuttles,
@@ -84,12 +91,13 @@ public partial class Catalog
8491
);
8592

8693
/// <summary>
87-
/// Raw review data queried from the GQL server.
94+
/// Deferred GQL query handle for review data. The consuming step materializes this
95+
/// via <c>ToList()</c>, which fires the <c>GetReviews</c> query against the server.
8896
/// </summary>
89-
public IItem<IEnumerable<IGetReviews_Reviews>> Reviews =>
97+
public IItem<GqlQuery<IGetReviewsResult, IGetReviews_Reviews>> Reviews =>
9098
CreateItem(
9199
() =>
92-
GqlItemFactory.Enumerable.Query<IGetReviewsResult, IGetReviews_Reviews>(
100+
GqlItemFactory.Query.NonPaged<IGetReviewsResult, IGetReviews_Reviews>(
93101
label: "GQLReviews",
94102
queryFunc: ct => _client.GetReviews.ExecuteAsync(ct),
95103
selectData: r => r.Reviews,

examples/advanced/KedroSpaceflightsGQL/Flows/DataProcessing/Steps/CreateModelInputTableStep.cs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,54 @@
11
using Flowthru.Core.Steps;
2+
using Flowthru.Extensions.GQL.Data;
23
using KedroSpaceflightsGQL.Data._03_Primary.Schemas;
34
using KedroSpaceflightsGQL.Infra.GqlClient;
45

56
namespace KedroSpaceflightsGQL.Flows.DataProcessing.Steps;
67

78
/// <summary>
8-
/// Joins typed shuttle, company, and review data from the GQL server into a unified model
9+
/// Materializes deferred GQL query handles and joins the results into a unified model
910
/// input table. All fields are already strongly-typed — no parsing required.
10-
/// The <c>bool</c> first input is the GqlDatabaseSeeded gate; it is consumed only to
11-
/// express the DAG dependency on Ingest and is otherwise unused.
1211
/// </summary>
12+
/// <remarks>
13+
/// <para>
14+
/// The <c>bool</c> first input is the <c>GqlDatabaseSeeded</c> gate; it is consumed only
15+
/// to express the DAG dependency on Ingest and is otherwise unused.
16+
/// </para>
17+
/// <para>
18+
/// The remaining three inputs are <see cref="GqlQuery{TResult,T}"/> handles — deferred
19+
/// query descriptors that carry the connection details and pagination config but have not
20+
/// yet executed any network calls. The calls to <c>ToList()</c> below are the
21+
/// materialization points: each one fires the corresponding GQL query (paginating as
22+
/// needed) and pulls the full dataset into memory before the join.
23+
/// </para>
24+
/// <para>
25+
/// This is the step-level analog of <c>TypedFrame&lt;T&gt;.ToList()</c> in the Spark
26+
/// extension: the catalog declares <em>what</em> to query; the step decides <em>when</em>
27+
/// to materialize and <em>how</em> to combine the results.
28+
/// </para>
29+
/// </remarks>
1330
[FlowthruStep]
1431
public static class CreateModelInputTableStep
1532
{
1633
public static Func<
1734
(
1835
bool,
19-
IEnumerable<IGetShuttles_Shuttles>,
20-
IEnumerable<IGetCompanies_Companies>,
21-
IEnumerable<IGetReviews_Reviews>
36+
GqlQuery<IGetShuttlesResult, IGetShuttles_Shuttles>,
37+
GqlQuery<IGetCompaniesResult, IGetCompanies_Companies>,
38+
GqlQuery<IGetReviewsResult, IGetReviews_Reviews>
2239
),
2340
IEnumerable<ModelInputTableSchema>
2441
> Create()
2542
{
2643
return (input) =>
2744
{
28-
var (_, shuttles, companies, reviews) = input;
45+
var (_, shuttlesQuery, companiesQuery, reviewsQuery) = input;
46+
47+
// Materialization — each call fires a GQL query (with pagination if configured).
48+
// Network I/O happens here, not in the catalog.
49+
var shuttles = shuttlesQuery.ToList();
50+
var companies = companiesQuery.ToList();
51+
var reviews = reviewsQuery.ToList();
2952

3053
// Join reviews to shuttles
3154
var ratedShuttles = reviews
@@ -38,7 +61,7 @@ public static Func<
3861
.ToList();
3962

4063
// Join with companies
41-
var modelInputTable = ratedShuttles
64+
return ratedShuttles
4265
.Join(
4366
companies,
4467
rs => rs.Shuttle.CompanyId,
@@ -61,8 +84,6 @@ public static Func<
6184
}
6285
)
6386
.ToList();
64-
65-
return modelInputTable;
6687
};
6788
}
6889
}

scripts/release.mjs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,55 @@ const ROOT = resolve(__dirname, '..');
2626
const DRY_RUN = process.argv.includes('--dry-run');
2727
const FROM_ARG = process.argv.find(a => a.startsWith('--from='));
2828
const FROM_TAG = FROM_ARG ? FROM_ARG.slice('--from='.length) : undefined;
29+
const FORCE_BUMP_ARG = process.argv.find(a => a.startsWith('--force-bump='));
30+
const FORCE_BUMP = FORCE_BUMP_ARG ? FORCE_BUMP_ARG.slice('--force-bump='.length) : undefined;
2931

3032
// ── 1. Version determination via NX Release ──────────────────────────────────
33+
//
34+
// NX's conventional-commits specifier detection filters commits by project-file
35+
// ownership — only commits that touch files belonging to a project in the release
36+
// group are considered. Since the release group only contains the workspace root
37+
// project (`flowthru`), commits that exclusively touch src/ library projects are
38+
// silently discarded by NX before it can compute a specifier.
39+
//
40+
// To compensate, we pre-scan the git log ourselves and derive the specifier from
41+
// commit types, then hand it to NX as an explicit override. NX still handles
42+
// version writing, changelog generation, and tagging — we're only fixing the
43+
// commit-detection blind spot.
44+
//
45+
// Commit types and their bumps mirror nx.json release.conventionalCommits.types:
46+
// feat → minor
47+
// fix/perf/revert → patch
48+
// breaking (! or BREAKING CHANGE) → major
49+
// everything else → not releasable
50+
function deriveSpecifierFromGitLog(fromRef) {
51+
const range = fromRef ? `${fromRef}..HEAD` : 'HEAD';
52+
const raw = execSync(`git log ${range} --format=%s`).toString().trim();
53+
if (!raw) return null;
54+
const lines = raw.split('\n').filter(Boolean);
55+
let hasFeat = false;
56+
let hasPatch = false;
57+
for (const msg of lines) {
58+
if (/^[a-z]+(\([^)]+\))?!:/.test(msg) || /^BREAKING CHANGE/.test(msg)) return 'major';
59+
if (/^feat(\([^)]+\))?:/.test(msg)) hasFeat = true;
60+
if (/^(fix|perf|revert)(\([^)]+\))?:/.test(msg)) hasPatch = true;
61+
}
62+
if (hasFeat) return 'minor';
63+
if (hasPatch) return 'patch';
64+
return null;
65+
}
66+
67+
const specifier = FORCE_BUMP ?? deriveSpecifierFromGitLog(FROM_TAG);
68+
69+
if (!specifier) {
70+
console.log('No version bump required — no releasable commits since last tag.');
71+
process.exit(0);
72+
}
3173

3274
const { workspaceVersion, projectsVersionData } = await releaseVersion({
3375
dryRun: DRY_RUN,
3476
verbose: false,
77+
specifier,
3578
...(FROM_TAG ? { from: FROM_TAG } : {}),
3679
});
3780

0 commit comments

Comments
 (0)