From 96bd390c7ffa75baf7db0c0bb3a3117c6ef6f631 Mon Sep 17 00:00:00 2001 From: jdolle <1841898+jdolle@users.noreply.github.com> Date: Tue, 31 Mar 2026 00:03:58 -0700 Subject: [PATCH] fix: do not cache edge types in graphql eslint (#7936) --- .changeset/wicked-seals-study.md | 6 + .../schema/providers/models/composite.ts | 1 + .../modules/schema/providers/models/single.ts | 2 +- .../schema/providers/registry-checks.ts | 4 +- .../services/policy/__tests__/policy.spec.ts | 105 ++++++++++++++++++ ...graphql-eslint__eslint-plugin@3.20.1.patch | 28 ++++- pnpm-lock.yaml | 10 +- 7 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 .changeset/wicked-seals-study.md diff --git a/.changeset/wicked-seals-study.md b/.changeset/wicked-seals-study.md new file mode 100644 index 000000000..2a049da0b --- /dev/null +++ b/.changeset/wicked-seals-study.md @@ -0,0 +1,6 @@ +--- +'hive': patch +--- + +Do not cache edge types in graphql eslint. This fixes an issue where edge types were cached between +runs and only the cached edge types would be referenced for subsequent runs diff --git a/packages/services/api/src/modules/schema/providers/models/composite.ts b/packages/services/api/src/modules/schema/providers/models/composite.ts index 875336c2b..288f0af0c 100644 --- a/packages/services/api/src/modules/schema/providers/models/composite.ts +++ b/packages/services/api/src/modules/schema/providers/models/composite.ts @@ -241,6 +241,7 @@ export class CompositeModel { }), this.checks.policyCheck({ selector, + // if schema check does not compose then there's no point in linting. Pass in null in this case. incomingSdl: compositionCheck.result?.fullSchemaSdl ?? null, modifiedSdl: incoming.sdl, }), diff --git a/packages/services/api/src/modules/schema/providers/models/single.ts b/packages/services/api/src/modules/schema/providers/models/single.ts index 865210de2..b7c42b7fc 100644 --- a/packages/services/api/src/modules/schema/providers/models/single.ts +++ b/packages/services/api/src/modules/schema/providers/models/single.ts @@ -107,7 +107,7 @@ export class SingleModel { }); if (checksumResult === 'unchanged') { - this.logger.debug('No changes detected, skipping schema check'); + this.logger.info('No changes detected, skipping schema check'); return { conclusion: SchemaCheckConclusion.Skip, }; diff --git a/packages/services/api/src/modules/schema/providers/registry-checks.ts b/packages/services/api/src/modules/schema/providers/registry-checks.ts index 72bc425d2..86aa0215e 100644 --- a/packages/services/api/src/modules/schema/providers/registry-checks.ts +++ b/packages/services/api/src/modules/schema/providers/registry-checks.ts @@ -225,7 +225,7 @@ export class RegistryChecks { ); if (!args.existing) { - this.logger.debug('No exiting version'); + this.logger.debug('No existing version'); return 'initial' as const; } @@ -400,7 +400,7 @@ export class RegistryChecks { this.logger.debug('Skip policy check due to no SDL being composed.'); return { status: 'skipped', - }; + } satisfies CheckResult; } const policyResult = await this.policy.checkPolicy(incomingSdl, modifiedSdl, selector); diff --git a/packages/services/policy/__tests__/policy.spec.ts b/packages/services/policy/__tests__/policy.spec.ts index 9dd213604..6329cb32d 100644 --- a/packages/services/policy/__tests__/policy.spec.ts +++ b/packages/services/policy/__tests__/policy.spec.ts @@ -105,6 +105,111 @@ describe('policy checks', () => { `); }); + it('refreshes edge types per run', async () => { + const caller = schemaPolicyApiRouter.createCaller({ req: { log: console } as any }); + const userSchema = ` + type Query { + # 'first' limits results; 'after' is the cursor to start from + users(first: Int, after: String): UserConnection! + } + + type UserConnection { + edges: [UserEdge!]! + pageInfo: PageInfo! + totalCount: Int! + } + + type UserEdge { + node: User! + cursor: String! + } + + type PageInfo { + hasNextPage: Boolean! + endCursor: String + } + + interface Node { + id: ID! + } + + type User { + id: ID! + name: String! + } + `; + const result = await caller.checkPolicy({ + source: userSchema, + schema: userSchema, + target: '1', + policy: { + 'relay-edge-types': [ + 2, + { + withEdgeSuffix: true, + shouldImplementNode: true, + listTypeCanWrapOnlyEdgeType: false, + }, + ], + }, + }); + + expect(result.length).toBe(1); + expect(result[0].message).toBe("Edge type's field \`node\` must implement \`Node\` interface."); + + const noUserSchema = ` + type Query { + # 'first' limits results; 'after' is the cursor to start from + noUsers(first: Int, after: String): NoUserConnection! + } + + type NoUserConnection { + edges: [NoUserEdge!]! + pageInfo: PageInfo! + totalCount: Int! + } + + type NoUserEdge { + node: NoUser! + cursor: String! + } + + type PageInfo { + hasNextPage: Boolean! + endCursor: String + } + + interface Node { + id: ID! + } + + type NoUser { + id: ID! + name: String! + } + `; + const noUserResult = await caller.checkPolicy({ + source: noUserSchema, + schema: noUserSchema, + target: '1', + policy: { + 'relay-edge-types': [ + 2, + { + withEdgeSuffix: true, + shouldImplementNode: true, + listTypeCanWrapOnlyEdgeType: false, + }, + ], + }, + }); + + expect(noUserResult.length).toBe(1); + expect(noUserResult[0].message).toBe( + "Edge type's field \`node\` must implement \`Node\` interface.", + ); + }); + /** To ensure existing policies dont break during upgrades */ it.each(policies)('should support existing policies', async policy => { await expect( diff --git a/patches/@graphql-eslint__eslint-plugin@3.20.1.patch b/patches/@graphql-eslint__eslint-plugin@3.20.1.patch index fed5bc457..3a2801072 100644 --- a/patches/@graphql-eslint__eslint-plugin@3.20.1.patch +++ b/patches/@graphql-eslint__eslint-plugin@3.20.1.patch @@ -119,4 +119,30 @@ index d952cee1e10976459e7c5836b86ba6540c45fdb6..d314997bfd26477c7d823cad397eb5d6 + return { [ruleId]: { - meta: { \ No newline at end of file + meta: { +diff --git a/esm/rules/relay-edge-types.js b/esm/rules/relay-edge-types.js +index 7a7eb179786103d9d72a4a84fb4bf811d11a4286..6fb12a5f56b71f56c9a14018d0494e9c7584f935 100644 +--- a/esm/rules/relay-edge-types.js ++++ b/esm/rules/relay-edge-types.js +@@ -12,11 +12,7 @@ const MESSAGE_MUST_BE_OBJECT_TYPE = "MESSAGE_MUST_BE_OBJECT_TYPE"; + const MESSAGE_MISSING_EDGE_SUFFIX = "MESSAGE_MISSING_EDGE_SUFFIX"; + const MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE = "MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE"; + const MESSAGE_SHOULD_IMPLEMENTS_NODE = "MESSAGE_SHOULD_IMPLEMENTS_NODE"; +-let edgeTypesCache; + function getEdgeTypes(schema2) { +- if (edgeTypesCache) { +- return edgeTypesCache; +- } + const edgeTypes = /* @__PURE__ */ new Set(); + const visitor = { + ObjectTypeDefinition(node) { +@@ -38,8 +34,7 @@ function getEdgeTypes(schema2) { + }; + const astNode = getDocumentNodeFromSchema(schema2); + visit(astNode, visitor); +- edgeTypesCache = edgeTypes; +- return edgeTypesCache; ++ return edgeTypes; + } + const schema = { + type: "array", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d9f47a803..cf09510f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,7 +56,7 @@ patchedDependencies: hash: 2280c6d4f2e9268fc118d06dc95deea7e9b58200010db1b332004627d6793a9f path: patches/@graphql-codegen__schema-ast.patch '@graphql-eslint/eslint-plugin@3.20.1': - hash: 695fba67df25ba9d46472c8398c94c6a2ccf75d902321d8f95150f68e940313e + hash: 230c54b62a1b41bce5584acfdebdeadd5631d8cce18b137ecd712b9ebbe2244b path: patches/@graphql-eslint__eslint-plugin@3.20.1.patch '@oclif/core@3.26.6': hash: 7432c7b46bd5e3276faff9af4fc733575417c17a0ec31df3c7a229f766e3cffc @@ -134,7 +134,7 @@ importers: version: 3.0.1(graphql@16.9.0) '@graphql-eslint/eslint-plugin': specifier: 3.20.1 - version: 3.20.1(patch_hash=695fba67df25ba9d46472c8398c94c6a2ccf75d902321d8f95150f68e940313e)(@babel/core@7.28.5)(@types/node@24.10.9)(cosmiconfig-toml-loader@1.0.0)(encoding@0.1.13)(graphql@16.9.0) + version: 3.20.1(patch_hash=230c54b62a1b41bce5584acfdebdeadd5631d8cce18b137ecd712b9ebbe2244b)(@babel/core@7.28.5)(@types/node@24.10.9)(cosmiconfig-toml-loader@1.0.0)(encoding@0.1.13)(graphql@16.9.0) '@graphql-inspector/cli': specifier: 6.0.6 version: 6.0.6(@types/node@24.10.9)(encoding@0.1.13)(graphql@16.9.0) @@ -1434,7 +1434,7 @@ importers: devDependencies: '@graphql-eslint/eslint-plugin': specifier: 3.20.1 - version: 3.20.1(patch_hash=695fba67df25ba9d46472c8398c94c6a2ccf75d902321d8f95150f68e940313e)(@babel/core@7.28.5)(@types/node@25.5.0)(cosmiconfig-toml-loader@1.0.0)(encoding@0.1.13)(graphql@16.9.0) + version: 3.20.1(patch_hash=230c54b62a1b41bce5584acfdebdeadd5631d8cce18b137ecd712b9ebbe2244b)(@babel/core@7.28.5)(@types/node@25.5.0)(cosmiconfig-toml-loader@1.0.0)(encoding@0.1.13)(graphql@16.9.0) '@hive/service-common': specifier: workspace:* version: link:../service-common @@ -22294,7 +22294,7 @@ snapshots: parse-filepath: 1.0.2 tslib: 2.6.3 - '@graphql-eslint/eslint-plugin@3.20.1(patch_hash=695fba67df25ba9d46472c8398c94c6a2ccf75d902321d8f95150f68e940313e)(@babel/core@7.28.5)(@types/node@24.10.9)(cosmiconfig-toml-loader@1.0.0)(encoding@0.1.13)(graphql@16.9.0)': + '@graphql-eslint/eslint-plugin@3.20.1(patch_hash=230c54b62a1b41bce5584acfdebdeadd5631d8cce18b137ecd712b9ebbe2244b)(@babel/core@7.28.5)(@types/node@24.10.9)(cosmiconfig-toml-loader@1.0.0)(encoding@0.1.13)(graphql@16.9.0)': dependencies: '@babel/code-frame': 7.29.0 '@graphql-tools/code-file-loader': 7.3.23(@babel/core@7.28.5)(graphql@16.9.0) @@ -22317,7 +22317,7 @@ snapshots: - supports-color - utf-8-validate - '@graphql-eslint/eslint-plugin@3.20.1(patch_hash=695fba67df25ba9d46472c8398c94c6a2ccf75d902321d8f95150f68e940313e)(@babel/core@7.28.5)(@types/node@25.5.0)(cosmiconfig-toml-loader@1.0.0)(encoding@0.1.13)(graphql@16.9.0)': + '@graphql-eslint/eslint-plugin@3.20.1(patch_hash=230c54b62a1b41bce5584acfdebdeadd5631d8cce18b137ecd712b9ebbe2244b)(@babel/core@7.28.5)(@types/node@25.5.0)(cosmiconfig-toml-loader@1.0.0)(encoding@0.1.13)(graphql@16.9.0)': dependencies: '@babel/code-frame': 7.29.0 '@graphql-tools/code-file-loader': 7.3.23(@babel/core@7.28.5)(graphql@16.9.0)