Skip to content

Conversation

dariuszkuc
Copy link
Member

@dariuszkuc dariuszkuc commented Oct 1, 2025

Updates connector spec to follow the same patterns as other federation spec blueprints (i.e. register types/directives in the constructor and use default logic for adding them to the schema that checks whether they need to be added or not).

Copy link

changeset-bot bot commented Oct 1, 2025

⚠️ No Changeset found

Latest commit: f406377

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@apollo-librarian
Copy link

apollo-librarian bot commented Oct 1, 2025

✅ Docs preview has no changes

The preview was not built because there were no changes.

Build ID: 03861b899de3664e080f80c8
Build Logs: View logs

Copy link

codesandbox-ci bot commented Oct 1, 2025

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@dariuszkuc dariuszkuc force-pushed the dk/connector_spec_fix branch from db13590 to 9b7d228 Compare October 1, 2025 17:05
@dariuszkuc dariuszkuc marked this pull request as ready for review October 1, 2025 17:05
@dariuszkuc dariuszkuc requested a review from a team as a code owner October 1, 2025 17:05
Copy link
Contributor

@lennyburdette lennyburdette left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for doing this!

Copy link
Contributor

@sachindshinde sachindshinde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's various changes we need to do to support spec input object types in the general case. For now though, we can do a one-off hack for connectors.

It's complicated to explain/implement, so I've filed a PR on top of this PR's branch at #3311. That PR also addresses all the comments I've made below. Once that PR merges into this one, it should be good to go.

return errors;
}
assert(isInputObjectType(existing), 'Should be an input object type');
for (const { name: field_name, type, defaultValue } of expectedFields) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop over input fields resembles createObjectTypeSpecification(), but it should resemble ensureSameArguments() more, as input fields are really more like arguments. (There's some further restrictions I've made in my PR specifically, it's in the code comments.)

const createdType = schema.addType(new InputObjectType(actualName, asBuiltIn));
for (const { name, type, defaultValue } of expectedFields) {
const newField = createdType.addField(name, type);
if (defaultValue) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaultValue is just a POJO in this case, so we can't rely on its JS truthiness; we can just unequivocally set newField.defaultValue = defaultValue instead.

# added in v0.2
path: JSONSelection
query: JSONSelection
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bug in the original code's comment, but it should be queryParams.

# added in v0.2
path: JSONSelection
query: JSONSelection
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bug in the original code's comment, but it should be queryParams.

return [
{
name: 'baseURL',
type: schema.stringType()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The field is baseURL: String!, so this needs a non-null wrapper.

defaultValue: false
},
{
name: 'errors',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The errors argument is ordered between batch and selection in the original code.

{
name: 'selection',
type: (schema, feature) =>
lookupFeatureTypeInSchema<ScalarType>(JSON_SELECTION, 'ScalarType', schema, feature)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The argument is selection: JSONSelection!, so this needs a non-null wrapper.

}
directive @source(
name: String!
http: SourceHttp!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type should be SourceHTTP! instead of SourceHttp!.

sachindshinde added a commit that referenced this pull request Oct 6, 2025
This PR implements one-off support for input objects in the connect
spec. Note that this implementation is hacky, and has various edge cases
where it won't behave as expected. In the short-term, we should avoid
using input objects in specs. In the long-term, there's a proper
strategy to supporting input objects in specs, which is described below.

To give some background:
1. Our schema representation (`Schema`) uses JS singleton objects for
schema elements.
2. Type references in these schema elements (e.g. the type of a field or
argument) will refer directly to these JS singletons.
3. This schema representation also tracks type/directive references in
the referenced schema element.

This means that schema building has to proceed in a two-phase process.
1. The first phase declares the types/directive definitions/extensions
(referred to as "shallow building" of the type/directive).
2. The second phase defines the types/directive definitions/extensions
(referred to as just "building" of the type/directive).

This is necessary because types/directives are allowed to form
referential cycles, so we can't just e.g. topologically sort the
types/directives by referential order and fully build in that order.

A problem arises, however, with subgraph schemas (a.k.a. schemas using
`FederationBlueprint`). In that case, schemas are allowed to omit
definitions/extensions of spec types/directives, for both the
`federation` and `connect` specs. These missing definitions/extensions
are added (or if already added, they're checked) by the `checkOrAdd()`
methods of type/directive specifications.

The `checkOrAdd()` pattern has a design flaw in that it bundles shallow
building, building, and checking into one single function. Specifically:
1. When building a schema, during the first phase, there may be missing
spec types/directives that are not written.
2. This blocks us from the second phase of building types/directives, as
they may depend on these missing types/directives.
3. This results in us calling `checkOrAdd()` for a type/directive spec
to fill in the missing definitions.
4. However, `checkOrAdd()` for a type/directive specification, in
bundling everything together, requires the types/directives it
references to be fully built.

This has several implications:
1. Specs with circular type/directive references just won't work.
2. Even if there's some topological order of references that can be
established, that order is currently not computed or followed. The
current `checkOrAdd()` calling logic just calls it for all registered
type specifications, and then all registered directive specifications.
This order has to be a topological referential order for schemas that
omit all spec type/directive definitions/extensions to build properly,
and that registration can be error-prone to manually specify/maintain.
- Also, this isn't done in a way that's interleaved with the building of
those elements if they already exist; for those elements, they need to
be built prior to the `checkOrAdd()` calls, otherwise the checking part
fails.
4. This has led to a very bespoke fragile pattern in schema building
where we first fully build enums and scalars while skipping directive
applications (since they can't reference other types/directives in that
case), then directive definitions while skipping directive applications,
and then call `checkOrAdd()` (after processing `@link`s).
5. This bespoke fragile pattern only worked before because the
federation spec just has type/directive definitions that only reference
enum/scalar types. And even in that case, if someone e.g. wrote in a
spec directive definition that referenced spec enums/scalars, they must
also write the definitions for those referenced enums/scalars, since
otherwise that directive definition would fail to build.
6. This bespoke fragile pattern does not work for connectors, since
those spec types/directives reference spec input object types, and some
of those spec input object types even reference other spec input object
types.

In order to support connectors usage of input object types in the
short-term, we must do a few things.
1. Verify there are no referential cycles (there aren't thankfully), and
register types/directives in the appropriate topological order.
2. Before calling `checkOrAdd()` for the connect spec, ensure that input
objects are built if they exist in the schema. This is going to have the
same limitation that the federation spec has with directive definitions,
where if someone wrote in a spec input object definition that references
other spec types/directives, they also need to write the definitions for
those referenced types/directives.

This will handle the main cases we wish to support, which are when no
connect spec definitions/extensions are provided and when they're all
provided. That's what this PR does (it also adds a test).

The actual long-term solution involves splitting the `checkOrAdd()`
logic into three separate functions:
1. Shallow building of spec types/directives that aren't in the schema
(accounting for renames as needed).
2. Full building of spec types/directives that aren't in the schema.
3. Checking of spec types/directives to ensure they conform to expected
definitions.

The gist is that we first do some processing on schema
definitions/extensions to bootstrap the `link` spec, then we couple spec
shallow building with the rest of shallow building (phase 1), couple
spec full building with the rest of full building (phase 2), and save
checking for after everything's built. There could be some additional
tracking to avoid doing checking for types/directives that weren't
originally in the schema, but that's not strictly necessary.

This PR also implements various fixes for bugs noticed in
#3310; these will be
pointed out in that PR's review.
Copy link
Contributor

@sachindshinde sachindshinde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving since #3311 has merged.

@dariuszkuc dariuszkuc marked this pull request as ready for review October 6, 2025 22:05
dariuszkuc and others added 3 commits October 6, 2025 17:05
Updates connector spec to follow the same patterns as other federation spec blueprints (i.e. register types/directives in the constructor and use default logic for adding them to the schema that checks whether they need to be added or not).
This PR implements one-off support for input objects in the connect
spec. Note that this implementation is hacky, and has various edge cases
where it won't behave as expected. In the short-term, we should avoid
using input objects in specs. In the long-term, there's a proper
strategy to supporting input objects in specs, which is described below.

To give some background:
1. Our schema representation (`Schema`) uses JS singleton objects for
schema elements.
2. Type references in these schema elements (e.g. the type of a field or
argument) will refer directly to these JS singletons.
3. This schema representation also tracks type/directive references in
the referenced schema element.

This means that schema building has to proceed in a two-phase process.
1. The first phase declares the types/directive definitions/extensions
(referred to as "shallow building" of the type/directive).
2. The second phase defines the types/directive definitions/extensions
(referred to as just "building" of the type/directive).

This is necessary because types/directives are allowed to form
referential cycles, so we can't just e.g. topologically sort the
types/directives by referential order and fully build in that order.

A problem arises, however, with subgraph schemas (a.k.a. schemas using
`FederationBlueprint`). In that case, schemas are allowed to omit
definitions/extensions of spec types/directives, for both the
`federation` and `connect` specs. These missing definitions/extensions
are added (or if already added, they're checked) by the `checkOrAdd()`
methods of type/directive specifications.

The `checkOrAdd()` pattern has a design flaw in that it bundles shallow
building, building, and checking into one single function. Specifically:
1. When building a schema, during the first phase, there may be missing
spec types/directives that are not written.
2. This blocks us from the second phase of building types/directives, as
they may depend on these missing types/directives.
3. This results in us calling `checkOrAdd()` for a type/directive spec
to fill in the missing definitions.
4. However, `checkOrAdd()` for a type/directive specification, in
bundling everything together, requires the types/directives it
references to be fully built.

This has several implications:
1. Specs with circular type/directive references just won't work.
2. Even if there's some topological order of references that can be
established, that order is currently not computed or followed. The
current `checkOrAdd()` calling logic just calls it for all registered
type specifications, and then all registered directive specifications.
This order has to be a topological referential order for schemas that
omit all spec type/directive definitions/extensions to build properly,
and that registration can be error-prone to manually specify/maintain.
- Also, this isn't done in a way that's interleaved with the building of
those elements if they already exist; for those elements, they need to
be built prior to the `checkOrAdd()` calls, otherwise the checking part
fails.
4. This has led to a very bespoke fragile pattern in schema building
where we first fully build enums and scalars while skipping directive
applications (since they can't reference other types/directives in that
case), then directive definitions while skipping directive applications,
and then call `checkOrAdd()` (after processing `@link`s).
5. This bespoke fragile pattern only worked before because the
federation spec just has type/directive definitions that only reference
enum/scalar types. And even in that case, if someone e.g. wrote in a
spec directive definition that referenced spec enums/scalars, they must
also write the definitions for those referenced enums/scalars, since
otherwise that directive definition would fail to build.
6. This bespoke fragile pattern does not work for connectors, since
those spec types/directives reference spec input object types, and some
of those spec input object types even reference other spec input object
types.

In order to support connectors usage of input object types in the
short-term, we must do a few things.
1. Verify there are no referential cycles (there aren't thankfully), and
register types/directives in the appropriate topological order.
2. Before calling `checkOrAdd()` for the connect spec, ensure that input
objects are built if they exist in the schema. This is going to have the
same limitation that the federation spec has with directive definitions,
where if someone wrote in a spec input object definition that references
other spec types/directives, they also need to write the definitions for
those referenced types/directives.

This will handle the main cases we wish to support, which are when no
connect spec definitions/extensions are provided and when they're all
provided. That's what this PR does (it also adds a test).

The actual long-term solution involves splitting the `checkOrAdd()`
logic into three separate functions:
1. Shallow building of spec types/directives that aren't in the schema
(accounting for renames as needed).
2. Full building of spec types/directives that aren't in the schema.
3. Checking of spec types/directives to ensure they conform to expected
definitions.

The gist is that we first do some processing on schema
definitions/extensions to bootstrap the `link` spec, then we couple spec
shallow building with the rest of shallow building (phase 1), couple
spec full building with the rest of full building (phase 2), and save
checking for after everything's built. There could be some additional
tracking to avoid doing checking for types/directives that weren't
originally in the schema, but that's not strictly necessary.

This PR also implements various fixes for bugs noticed in
#3310; these will be
pointed out in that PR's review.
@dariuszkuc dariuszkuc force-pushed the dk/connector_spec_fix branch from 3df4612 to f406377 Compare October 6, 2025 22:05
@dariuszkuc dariuszkuc merged commit 11f7a9c into main Oct 6, 2025
17 checks passed
@dariuszkuc dariuszkuc deleted the dk/connector_spec_fix branch October 6, 2025 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants