From bc69e86325686a025c0ba6b0f71cc8a7d657e837 Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Thu, 23 Oct 2025 14:49:46 -0400 Subject: [PATCH 01/13] add isValueExpectedForType to handle abstract types --- src/methods/validate-fixture-input.ts | 51 +++++++++++-- test/methods/validate-fixture-input.test.ts | 85 +++++++++++++++++++-- 2 files changed, 125 insertions(+), 11 deletions(-) diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index c551274..4b362f3 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -10,6 +10,7 @@ import { isAbstractType, isObjectType, getNamedType, + GraphQLCompositeType, } from "graphql"; import { inlineNamedFragmentSpreads } from "../utils/inline-named-fragment-spreads.js"; @@ -56,7 +57,15 @@ export function validateFixtureInput( // Field is missing from fixture if (valueForResponseKey === undefined) { - errors.push(`Missing expected fixture data for ${responseKey}`); + const parentType = typeInfo.getParentType(); + if (!parentType) { + // This shouldn't happen with a valid query and schema - TypeInfo should always + // provide parent type information when traversing fields. This check is here to + // satisfy TypeScript's type requirements (getParentType() can return null). + errors.push(`Cannot validate ${responseKey}: missing parent type information`); + } else if (isValueExpectedForType(currentValue, parentType)) { + errors.push(`Missing expected fixture data for ${responseKey}`); + } } // Scalars and Enums (including wrapped types) else if (isInputType(fieldType)) { @@ -100,10 +109,6 @@ export function validateFixtureInput( } } // Objects - validate and add to traversal stack - // Note: Abstract types (unions/interfaces) are handled in a limited way. - // We add them to the traversal stack but don't use __typename to discriminate - // between concrete types. This works for simple cases where all items are the - // same type, but doesn't support mixed-type arrays (see skipped test). else if (isObjectType(unwrappedFieldType) || isAbstractType(unwrappedFieldType)) { if (valueForResponseKey === null) { errors.push(`Expected object for ${responseKey}, but got null`); @@ -219,4 +224,38 @@ function processNestedArrays( } return { values: result, errors }; -} \ No newline at end of file +} + +/** + * Determines if a fixture value is expected for a given parent type based on its __typename. + * + * @param fixtureValue - The fixture value to check + * @param parentType - The parent type from typeInfo (concrete type if inside inline fragment, abstract if on union/interface) + * @returns True if the value is expected for the parent type, false otherwise + * + * @remarks + * When the parent type is abstract (union/interface), all values are expected. + * When the parent type is concrete (inside an inline fragment), only values + * whose __typename matches the concrete type are expected. + */ +function isValueExpectedForType( + fixtureValue: any, + parentType: GraphQLCompositeType +): boolean { + // If parent type is abstract (union/interface), all values are expected. + // This means we're validating a field selected directly on the abstract type (e.g., __typename on a union), + // so it should be present on all values regardless of their concrete type. + if (isAbstractType(parentType)) { + return true; + } + + // Parent is a concrete type - check if fixture value's __typename matches + const valueTypename = fixtureValue.__typename; + if (!valueTypename) { + // No __typename in value - can't discriminate, so expect it + return true; + } + + // Only expect the value for this type if its __typename matches the parent type + return valueTypename === parentType.name; +} diff --git a/test/methods/validate-fixture-input.test.ts b/test/methods/validate-fixture-input.test.ts index 51b4d5f..ac7471b 100644 --- a/test/methods/validate-fixture-input.test.ts +++ b/test/methods/validate-fixture-input.test.ts @@ -129,11 +129,7 @@ describe("validateFixtureInput", () => { expect(result.errors).toHaveLength(0); }); - // This test is skipped because the validator doesn't yet support unions where - // different items in the array can be different types. Currently, it expects - // all fields from all inline fragments to be present in every item, instead of - // filtering by __typename. - it.skip("handles inline fragments with multiple types in union", () => { + it("handles inline fragments with multiple types in union", () => { const queryAST = parse(` query { data { @@ -174,6 +170,37 @@ describe("validateFixtureInput", () => { expect(result.errors).toHaveLength(0); }); + it("handles single inline fragment on union without __typename", () => { + const queryAST = parse(` + query { + data { + searchResults { + ... on Item { + id + count + } + } + } + } + `); + + const fixtureInput = { + data: { + searchResults: [ + { + id: "gid://test/Item/1", + count: 5 + } + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // With only one inline fragment, no __typename is needed for discrimination + expect(result.errors).toHaveLength(0); + }); + it("handles nested inline fragments", () => { const queryAST = parse(` query { @@ -938,5 +965,53 @@ describe("validateFixtureInput", () => { expect(result.errors).toHaveLength(1); expect(result.errors[0]).toBe('Cannot validate nonExistentField: missing type information'); }); + + it("detects missing fields when __typename is not selected in union with inline fragments", () => { + const queryAST = parse(` + query { + data { + searchResults { + ... on Item { + id + count + } + ... on Metadata { + email + phone + } + } + } + } + `); + + const fixtureInput = { + data: { + searchResults: [ + { + id: "gid://test/Item/1", + count: 5 + }, + { + email: "test@example.com", + phone: "555-0001" + } + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // Without __typename, we can't discriminate which fields are expected for each object + // So the validator conservatively expects all fields from all inline fragments + // First error: Missing __typename field for abstract type (required for discrimination) + // First object is missing email and phone (from Metadata fragment) + // Second object is missing id and count (from Item fragment) + expect(result.errors).toHaveLength(5); + expect(result.errors[0]).toBe("Missing __typename field for abstract type SearchResult"); + expect(result.errors[1]).toBe("Missing expected fixture data for id"); + expect(result.errors[2]).toBe("Missing expected fixture data for count"); + expect(result.errors[3]).toBe("Missing expected fixture data for email"); + expect(result.errors[4]).toBe("Missing expected fixture data for phone"); + }); }); }); From 75b72805a60de32dc7408fe440c651575308ffb5 Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Mon, 27 Oct 2025 13:35:04 -0400 Subject: [PATCH 02/13] typenameResponseStack --- src/methods/validate-fixture-input.ts | 38 +++++++++++++++++-- test/methods/validate-fixture-input.test.ts | 41 +++++++++++++++++++++ 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index 4b362f3..1932b95 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -39,6 +39,8 @@ export function validateFixtureInput( const typeInfo = new TypeInfo(schema); const valueStack: any[][] = [[value]]; const errors: string[] = []; + const typenameResponseKeyStack: (string | undefined)[] = []; + visit( inlineFragmentSpreadsAst, visitWithTypeInfo(typeInfo, { @@ -63,8 +65,11 @@ export function validateFixtureInput( // provide parent type information when traversing fields. This check is here to // satisfy TypeScript's type requirements (getParentType() can return null). errors.push(`Cannot validate ${responseKey}: missing parent type information`); - } else if (isValueExpectedForType(currentValue, parentType)) { - errors.push(`Missing expected fixture data for ${responseKey}`); + } else { + const typenameResponseKey = typenameResponseKeyStack[typenameResponseKeyStack.length - 1]; + if (isValueExpectedForType(currentValue, parentType, typenameResponseKey)) { + errors.push(`Missing expected fixture data for ${responseKey}`); + } } } // Scalars and Enums (including wrapped types) @@ -137,6 +142,21 @@ export function validateFixtureInput( }, SelectionSet: { enter(node) { + // Look ahead to find __typename field and track its response key + const typenameField = node.selections.find( + (selection) => + selection.kind === Kind.FIELD && + selection.name.value === "__typename" + ); + + // If this SelectionSet has __typename, use its response key. + // Otherwise, inherit from parent. + const typenameResponseKey = typenameField && typenameField.kind === Kind.FIELD + ? typenameField.alias?.value || "__typename" + : typenameResponseKeyStack[typenameResponseKeyStack.length - 1]; + + typenameResponseKeyStack.push(typenameResponseKey); + if (isAbstractType(getNamedType(typeInfo.getType()))) { const hasTypename = node.selections.some( (selection) => @@ -157,6 +177,9 @@ export function validateFixtureInput( } } }, + leave() { + typenameResponseKeyStack.pop(); + }, }, }) ); @@ -231,6 +254,7 @@ function processNestedArrays( * * @param fixtureValue - The fixture value to check * @param parentType - The parent type from typeInfo (concrete type if inside inline fragment, abstract if on union/interface) + * @param typenameKey - The response key for the __typename field (supports aliases like `type: __typename`) * @returns True if the value is expected for the parent type, false otherwise * * @remarks @@ -240,7 +264,8 @@ function processNestedArrays( */ function isValueExpectedForType( fixtureValue: any, - parentType: GraphQLCompositeType + parentType: GraphQLCompositeType, + typenameKey?: string ): boolean { // If parent type is abstract (union/interface), all values are expected. // This means we're validating a field selected directly on the abstract type (e.g., __typename on a union), @@ -250,7 +275,12 @@ function isValueExpectedForType( } // Parent is a concrete type - check if fixture value's __typename matches - const valueTypename = fixtureValue.__typename; + // If __typename wasn't selected in the query, we can't discriminate, so expect all values + if (!typenameKey) { + return true; + } + + const valueTypename = fixtureValue[typenameKey]; if (!valueTypename) { // No __typename in value - can't discriminate, so expect it return true; diff --git a/test/methods/validate-fixture-input.test.ts b/test/methods/validate-fixture-input.test.ts index ac7471b..365e33f 100644 --- a/test/methods/validate-fixture-input.test.ts +++ b/test/methods/validate-fixture-input.test.ts @@ -170,6 +170,47 @@ describe("validateFixtureInput", () => { expect(result.errors).toHaveLength(0); }); + it("handles aliased __typename in inline fragments", () => { + const queryAST = parse(` + query { + data { + searchResults { + type: __typename + ... on Item { + id + count + } + ... on Metadata { + email + phone + } + } + } + } + `); + + const fixtureInput = { + data: { + searchResults: [ + { + type: "Item", + id: "gid://test/Item/1", + count: 5 + }, + { + type: "Metadata", + email: "test@example.com", + phone: "555-0001" + } + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + expect(result.errors).toHaveLength(0); + }); + it("handles single inline fragment on union without __typename", () => { const queryAST = parse(` query { From 2a6aa92b30630fe73526e85aae89dde96c4ddbf0 Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Mon, 27 Oct 2025 14:28:16 -0400 Subject: [PATCH 03/13] use schema to compare possibleTypes for abstract types --- src/methods/validate-fixture-input.ts | 27 ++++++------ test/fixtures/test-schema.graphql | 27 ++++++++++++ test/methods/validate-fixture-input.test.ts | 46 +++++++++++++++++++++ 3 files changed, 87 insertions(+), 13 deletions(-) diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index 1932b95..4e6bdfa 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -67,7 +67,7 @@ export function validateFixtureInput( errors.push(`Cannot validate ${responseKey}: missing parent type information`); } else { const typenameResponseKey = typenameResponseKeyStack[typenameResponseKeyStack.length - 1]; - if (isValueExpectedForType(currentValue, parentType, typenameResponseKey)) { + if (isValueExpectedForType(currentValue, parentType, schema, typenameResponseKey)) { errors.push(`Missing expected fixture data for ${responseKey}`); } } @@ -253,28 +253,23 @@ function processNestedArrays( * Determines if a fixture value is expected for a given parent type based on its __typename. * * @param fixtureValue - The fixture value to check - * @param parentType - The parent type from typeInfo (concrete type if inside inline fragment, abstract if on union/interface) + * @param parentType - The parent type from typeInfo + * @param schema - The GraphQL schema to resolve possible types for abstract types * @param typenameKey - The response key for the __typename field (supports aliases like `type: __typename`) * @returns True if the value is expected for the parent type, false otherwise * * @remarks - * When the parent type is abstract (union/interface), all values are expected. - * When the parent type is concrete (inside an inline fragment), only values + * When the parent type is abstract (union/interface), checks if the value's __typename + * is one of the possible types for that abstract type. + * When the parent type is concrete (e.g., inside `... on ConcreteType`), only values * whose __typename matches the concrete type are expected. */ function isValueExpectedForType( fixtureValue: any, parentType: GraphQLCompositeType, + schema: GraphQLSchema, typenameKey?: string ): boolean { - // If parent type is abstract (union/interface), all values are expected. - // This means we're validating a field selected directly on the abstract type (e.g., __typename on a union), - // so it should be present on all values regardless of their concrete type. - if (isAbstractType(parentType)) { - return true; - } - - // Parent is a concrete type - check if fixture value's __typename matches // If __typename wasn't selected in the query, we can't discriminate, so expect all values if (!typenameKey) { return true; @@ -286,6 +281,12 @@ function isValueExpectedForType( return true; } - // Only expect the value for this type if its __typename matches the parent type + // If parent type is abstract (union/interface), check if the value's type is one of the possible types + if (isAbstractType(parentType)) { + const possibleTypes = schema.getPossibleTypes(parentType); + return possibleTypes.some(type => type.name === valueTypename); + } + + // Parent is a concrete type - check if fixture value's __typename matches return valueTypename === parentType.name; } diff --git a/test/fixtures/test-schema.graphql b/test/fixtures/test-schema.graphql index 32b6987..83bfd2e 100644 --- a/test/fixtures/test-schema.graphql +++ b/test/fixtures/test-schema.graphql @@ -15,10 +15,18 @@ type DataContainer { searchResults: [SearchResult!]! itemMatrix: [[Item]] metadataCube: [[[Metadata]]] + products: [Product!]! } union SearchResult = Item | Metadata +union Product = PhysicalProduct | DigitalProduct | GiftCard + +interface Purchasable { + price: Int! + currency: String! +} + type Item { id: ID count: Int! @@ -78,4 +86,23 @@ input HttpRequest { method: String! headers: String body: String +} + +type PhysicalProduct implements Purchasable { + price: Int! + currency: String! + weight: Int! + sku: String! +} + +type DigitalProduct implements Purchasable { + price: Int! + currency: String! + downloadUrl: String! + fileSize: Int! +} + +type GiftCard { + code: String! + balance: Int! } \ No newline at end of file diff --git a/test/methods/validate-fixture-input.test.ts b/test/methods/validate-fixture-input.test.ts index 365e33f..ca05559 100644 --- a/test/methods/validate-fixture-input.test.ts +++ b/test/methods/validate-fixture-input.test.ts @@ -211,6 +211,52 @@ describe("validateFixtureInput", () => { expect(result.errors).toHaveLength(0); }); + it("handles inline fragment on interface type", () => { + const queryAST = parse(` + query { + data { + products { + __typename + ... on Purchasable { + price + currency + } + ... on GiftCard { + code + balance + } + } + } + } + `); + + const fixtureInput = { + data: { + products: [ + { + __typename: "PhysicalProduct", + price: 1000, + currency: "USD" + }, + { + __typename: "DigitalProduct", + price: 500, + currency: "USD" + }, + { + __typename: "GiftCard", + code: "GIFT123", + balance: 5000 + } + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + expect(result.errors).toHaveLength(0); + }); + it("handles single inline fragment on union without __typename", () => { const queryAST = parse(` query { From bf8e68bcfb8dc3edb383641499f0e59a3ef88a31 Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Tue, 28 Oct 2025 09:50:13 -0400 Subject: [PATCH 04/13] use grandparentType to determine if in non-selected type --- src/methods/validate-fixture-input.ts | 19 ++- test/methods/validate-fixture-input.test.ts | 132 ++++++++++++++++++++ 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index 4e6bdfa..e0b1303 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -67,7 +67,7 @@ export function validateFixtureInput( errors.push(`Cannot validate ${responseKey}: missing parent type information`); } else { const typenameResponseKey = typenameResponseKeyStack[typenameResponseKeyStack.length - 1]; - if (isValueExpectedForType(currentValue, parentType, schema, typenameResponseKey)) { + if (isValueExpectedForType(currentValue, parentType, schema, typeInfo, typenameResponseKey)) { errors.push(`Missing expected fixture data for ${responseKey}`); } } @@ -255,6 +255,7 @@ function processNestedArrays( * @param fixtureValue - The fixture value to check * @param parentType - The parent type from typeInfo * @param schema - The GraphQL schema to resolve possible types for abstract types + * @param typeInfo - TypeInfo instance to check for abstract types in ancestry * @param typenameKey - The response key for the __typename field (supports aliases like `type: __typename`) * @returns True if the value is expected for the parent type, false otherwise * @@ -263,16 +264,28 @@ function processNestedArrays( * is one of the possible types for that abstract type. * When the parent type is concrete (e.g., inside `... on ConcreteType`), only values * whose __typename matches the concrete type are expected. + * + * Special case: Empty objects {} are treated as valid when __typename is not selected. + * This handles GraphQL responses where union/interface members that don't match any + * selected inline fragments are returned as empty objects. */ function isValueExpectedForType( fixtureValue: any, parentType: GraphQLCompositeType, schema: GraphQLSchema, + typeInfo: TypeInfo, typenameKey?: string ): boolean { - // If __typename wasn't selected in the query, we can't discriminate, so expect all values + // If __typename wasn't selected in the query, we can't discriminate if (!typenameKey) { - return true; + // Empty objects {} are valid if the parent field returns a union/interface + // Check if the grandparent type (one level back in the stack) is abstract + const parentStack = (typeInfo as any)._parentTypeStack; + const grandparentType = parentStack[parentStack.length - 2]; + if (grandparentType && isAbstractType(grandparentType) && Object.keys(fixtureValue).length === 0) { + return false; // Don't expect any fields on empty objects in union/interface contexts + } + return true; // Otherwise, expect all values } const valueTypename = fixtureValue[typenameKey]; diff --git a/test/methods/validate-fixture-input.test.ts b/test/methods/validate-fixture-input.test.ts index ca05559..b0f9847 100644 --- a/test/methods/validate-fixture-input.test.ts +++ b/test/methods/validate-fixture-input.test.ts @@ -288,6 +288,106 @@ describe("validateFixtureInput", () => { expect(result.errors).toHaveLength(0); }); + it("handles empty objects in union when inline fragment doesn't match", () => { + const queryAST = parse(` + query { + data { + searchResults { + ... on Item { + id + count + } + } + } + } + `); + + const fixtureInput = { + data: { + searchResults: [ + { + id: "gid://test/Item/1", + count: 5 + }, + {} // Empty object - represents Metadata that didn't match the Item fragment + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // Empty object {} is valid - GraphQL returns this for union members that don't match any fragments + expect(result.errors).toHaveLength(0); + }); + + it("handles empty objects in interface fragment inside union", () => { + const queryAST = parse(` + query { + data { + products { + ... on Purchasable { + price + currency + } + } + } + } + `); + + const fixtureInput = { + data: { + products: [ + { + price: 1000, + currency: "USD" + }, + {} // Empty object - GiftCard that doesn't implement Purchasable + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // Empty object {} is valid - represents a union member that doesn't implement the interface + expect(result.errors).toHaveLength(0); + }); + + it("handles objects with only __typename when inline fragment doesn't match", () => { + const queryAST = parse(` + query { + data { + searchResults { + __typename + ... on Item { + id + count + } + } + } + } + `); + + const fixtureInput = { + data: { + searchResults: [ + { + __typename: "Item", + id: "gid://test/Item/1", + count: 5 + }, + { + __typename: "Metadata" // Only typename, no other fields + } + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // Object with only __typename is valid - Metadata doesn't match the Item fragment + expect(result.errors).toHaveLength(0); + }); + it("handles nested inline fragments", () => { const queryAST = parse(` query { @@ -1053,6 +1153,38 @@ describe("validateFixtureInput", () => { expect(result.errors[0]).toBe('Cannot validate nonExistentField: missing type information'); }); + it("detects empty objects in non-union context", () => { + const queryAST = parse(` + query { + data { + items { + id + count + } + } + } + `); + + const fixtureInput = { + data: { + items: [ + { + id: "gid://test/Item/1", + count: 5 + }, + {} // Empty object in non-union context - should error + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // Empty object {} is invalid in non-union context - missing required fields + expect(result.errors).toHaveLength(2); + expect(result.errors[0]).toBe("Missing expected fixture data for id"); + expect(result.errors[1]).toBe("Missing expected fixture data for count"); + }); + it("detects missing fields when __typename is not selected in union with inline fragments", () => { const queryAST = parse(` query { From afc513b37737479d253c32d006b6b40896e54a7b Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Tue, 28 Oct 2025 10:37:02 -0400 Subject: [PATCH 05/13] track our own typeStack instead of relying on typeInfo._parentTypeStack --- src/methods/validate-fixture-input.ts | 40 ++++++++++++++++----------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index e0b1303..caf8d6d 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -1,16 +1,21 @@ -import { visit, DocumentNode, Kind } from "graphql"; -import { TypeInfo, visitWithTypeInfo, coerceInputValue } from "graphql"; import { + coerceInputValue, + DocumentNode, + getNamedType, + getNullableType, + GraphQLCompositeType, + GraphQLList, + GraphQLNamedType, + GraphQLSchema, + isAbstractType, isInputType, isListType, isNullableType, - GraphQLSchema, - GraphQLList, - getNullableType, - isAbstractType, isObjectType, - getNamedType, - GraphQLCompositeType, + Kind, + TypeInfo, + visit, + visitWithTypeInfo, } from "graphql"; import { inlineNamedFragmentSpreads } from "../utils/inline-named-fragment-spreads.js"; @@ -38,9 +43,11 @@ export function validateFixtureInput( const inlineFragmentSpreadsAst = inlineNamedFragmentSpreads(queryAST); const typeInfo = new TypeInfo(schema); const valueStack: any[][] = [[value]]; - const errors: string[] = []; + const typeStack: (GraphQLNamedType | undefined)[] = []; const typenameResponseKeyStack: (string | undefined)[] = []; + const errors: string[] = []; + visit( inlineFragmentSpreadsAst, visitWithTypeInfo(typeInfo, { @@ -54,6 +61,8 @@ export function validateFixtureInput( const fieldDefinition = typeInfo.getFieldDef(); const fieldType = fieldDefinition?.type; + typeStack.push(getNamedType(fieldType)); + for (const currentValue of currentValues) { const valueForResponseKey = currentValue[responseKey]; @@ -67,7 +76,8 @@ export function validateFixtureInput( errors.push(`Cannot validate ${responseKey}: missing parent type information`); } else { const typenameResponseKey = typenameResponseKeyStack[typenameResponseKeyStack.length - 1]; - if (isValueExpectedForType(currentValue, parentType, schema, typeInfo, typenameResponseKey)) { + const grandparentType = typeStack[typeStack.length - 2]; + if (isValueExpectedForType(currentValue, parentType, grandparentType, schema, typenameResponseKey)) { errors.push(`Missing expected fixture data for ${responseKey}`); } } @@ -138,6 +148,7 @@ export function validateFixtureInput( }, leave() { valueStack.pop(); + typeStack.pop(); }, }, SelectionSet: { @@ -254,8 +265,8 @@ function processNestedArrays( * * @param fixtureValue - The fixture value to check * @param parentType - The parent type from typeInfo + * @param grandparentType - The type returned by the grandparent field (used to detect union/interface contexts) * @param schema - The GraphQL schema to resolve possible types for abstract types - * @param typeInfo - TypeInfo instance to check for abstract types in ancestry * @param typenameKey - The response key for the __typename field (supports aliases like `type: __typename`) * @returns True if the value is expected for the parent type, false otherwise * @@ -272,16 +283,13 @@ function processNestedArrays( function isValueExpectedForType( fixtureValue: any, parentType: GraphQLCompositeType, + grandparentType: GraphQLNamedType | undefined, schema: GraphQLSchema, - typeInfo: TypeInfo, typenameKey?: string ): boolean { // If __typename wasn't selected in the query, we can't discriminate if (!typenameKey) { - // Empty objects {} are valid if the parent field returns a union/interface - // Check if the grandparent type (one level back in the stack) is abstract - const parentStack = (typeInfo as any)._parentTypeStack; - const grandparentType = parentStack[parentStack.length - 2]; + // Empty objects {} are valid if the grandparent type is a union/interface if (grandparentType && isAbstractType(grandparentType) && Object.keys(fixtureValue).length === 0) { return false; // Don't expect any fields on empty objects in union/interface contexts } From c2a81b9f47700a636b34000920ac64a65160ffd5 Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Tue, 28 Oct 2025 15:18:45 -0400 Subject: [PATCH 06/13] compare possibleTypes between parent and grandparent --- src/methods/validate-fixture-input.ts | 20 +++++++++--- test/fixtures/test-schema.graphql | 1 + test/methods/validate-fixture-input.test.ts | 34 +++++++++++++++++++-- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index caf8d6d..afce704 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -276,9 +276,8 @@ function processNestedArrays( * When the parent type is concrete (e.g., inside `... on ConcreteType`), only values * whose __typename matches the concrete type are expected. * - * Special case: Empty objects {} are treated as valid when __typename is not selected. - * This handles GraphQL responses where union/interface members that don't match any - * selected inline fragments are returned as empty objects. + * Special case: Empty objects {} are valid when __typename is not selected and the inline + * fragment narrows the possible types. */ function isValueExpectedForType( fixtureValue: any, @@ -289,9 +288,20 @@ function isValueExpectedForType( ): boolean { // If __typename wasn't selected in the query, we can't discriminate if (!typenameKey) { - // Empty objects {} are valid if the grandparent type is a union/interface + // Empty objects {} are valid only if we've narrowed the type through inline fragments + // Compare the set of possible types at grandparent vs parent level if (grandparentType && isAbstractType(grandparentType) && Object.keys(fixtureValue).length === 0) { - return false; // Don't expect any fields on empty objects in union/interface contexts + const grandparentPossibleTypes = schema.getPossibleTypes(grandparentType); + const parentPossibleTypes = isAbstractType(parentType) + ? schema.getPossibleTypes(parentType) + : [parentType]; + + // If the sets are different, we've narrowed the type → empty objects are valid (return false) + // If the sets are equal, no narrowing happened → empty objects are invalid (return true) + const setsAreEqual = grandparentPossibleTypes.length === parentPossibleTypes.length && + grandparentPossibleTypes.every(t => parentPossibleTypes.includes(t)); + + return setsAreEqual; } return true; // Otherwise, expect all values } diff --git a/test/fixtures/test-schema.graphql b/test/fixtures/test-schema.graphql index 83bfd2e..5537917 100644 --- a/test/fixtures/test-schema.graphql +++ b/test/fixtures/test-schema.graphql @@ -16,6 +16,7 @@ type DataContainer { itemMatrix: [[Item]] metadataCube: [[[Metadata]]] products: [Product!]! + purchasable: Purchasable } union SearchResult = Item | Metadata diff --git a/test/methods/validate-fixture-input.test.ts b/test/methods/validate-fixture-input.test.ts index b0f9847..7053d15 100644 --- a/test/methods/validate-fixture-input.test.ts +++ b/test/methods/validate-fixture-input.test.ts @@ -320,7 +320,7 @@ describe("validateFixtureInput", () => { expect(result.errors).toHaveLength(0); }); - it("handles empty objects in interface fragment inside union", () => { + it("handles empty objects when narrowing from union to interface subset", () => { const queryAST = parse(` query { data { @@ -348,7 +348,10 @@ describe("validateFixtureInput", () => { const result = validateFixtureInput(queryAST, schema, fixtureInput); - // Empty object {} is valid - represents a union member that doesn't implement the interface + // Empty object {} is valid + // Grandparent (Product union) has 3 types: {PhysicalProduct, DigitalProduct, GiftCard} + // Parent (Purchasable interface) has 2 types: {PhysicalProduct, DigitalProduct} + // Sets are different → type was narrowed → empty object represents GiftCard expect(result.errors).toHaveLength(0); }); @@ -1185,6 +1188,33 @@ describe("validateFixtureInput", () => { expect(result.errors[1]).toBe("Missing expected fixture data for count"); }); + it("detects empty objects when inline fragment is on same type as field", () => { + const queryAST = parse(` + query { + data { + purchasable { + ... on Purchasable { + price + } + } + } + } + `); + + const fixtureInput = { + data: { + purchasable: {} // Empty object when selecting on interface itself - should error + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // Empty object {} is invalid when inline fragment is on the same type as the field + // We're not discriminating between union members, so all fields are required + expect(result.errors).toHaveLength(1); + expect(result.errors[0]).toBe("Missing expected fixture data for price"); + }); + it("detects missing fields when __typename is not selected in union with inline fragments", () => { const queryAST = parse(` query { From 78d6c30675ac6ea36bf085b35528e33da62e2901 Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Wed, 29 Oct 2025 15:27:42 -0400 Subject: [PATCH 07/13] use possibleTypesStack --- package.json | 2 +- pnpm-lock.yaml | 615 ++++++++------------ src/methods/validate-fixture-input.ts | 111 ++-- test/fixtures/test-schema.graphql | 34 ++ test/methods/validate-fixture-input.test.ts | 200 ++++++- tsconfig.json | 3 +- 6 files changed, 559 insertions(+), 406 deletions(-) diff --git a/package.json b/package.json index 2ed83cc..76591e4 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "vitest": "^3.2.4" }, "engines": { - "node": ">=14.0.0" + "node": ">=22.0.0" }, "author": "lopert", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa72b32..43482b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,13 +17,7 @@ importers: version: 50.0.0(@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(prettier@3.6.2)(typescript@5.9.3) '@types/node': specifier: ^22.18.6 - version: 22.18.10 - '@typescript-eslint/eslint-plugin': - specifier: ^8.46.2 - version: 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': - specifier: ^8.46.2 - version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + version: 22.18.13 eslint: specifier: ^9.38.0 version: 9.38.0(jiti@2.6.1) @@ -35,7 +29,7 @@ importers: version: 5.9.3 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1) + version: 3.2.4(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1) test-app: {} @@ -43,11 +37,11 @@ importers: dependencies: '@shopify/shopify_function': specifier: 2.0.0 - version: 2.0.0(@types/node@22.18.10)(graphql-sock@1.0.1(graphql@16.11.0)) + version: 2.0.0(@types/node@22.18.13)(graphql-sock@1.0.1(graphql@16.11.0)) devDependencies: vitest: specifier: 3.2.4 - version: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1) + version: 3.2.4(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1) test-app/extensions/cart-validation-js/tests: dependencies: @@ -56,7 +50,7 @@ importers: version: link:../../../.. vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1) + version: 3.2.4(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1) test-app/extensions/discount-function/tests: dependencies: @@ -65,7 +59,7 @@ importers: version: link:../../../.. vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1) + version: 3.2.4(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1) packages: @@ -83,16 +77,16 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.4': - resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.4': - resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -121,8 +115,8 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.27.1': @@ -133,8 +127,8 @@ packages: resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.4': - resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -152,12 +146,12 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.4': - resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} '@emnapi/core@1.6.0': @@ -343,8 +337,8 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/config-array@0.21.1': @@ -690,113 +684,113 @@ packages: '@repeaterjs/repeater@3.0.6': resolution: {integrity: sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==} - '@rollup/rollup-android-arm-eabi@4.52.4': - resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} + '@rollup/rollup-android-arm-eabi@4.52.5': + resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.52.4': - resolution: {integrity: sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==} + '@rollup/rollup-android-arm64@4.52.5': + resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.52.4': - resolution: {integrity: sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==} + '@rollup/rollup-darwin-arm64@4.52.5': + resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.52.4': - resolution: {integrity: sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==} + '@rollup/rollup-darwin-x64@4.52.5': + resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.52.4': - resolution: {integrity: sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==} + '@rollup/rollup-freebsd-arm64@4.52.5': + resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.4': - resolution: {integrity: sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==} + '@rollup/rollup-freebsd-x64@4.52.5': + resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.52.4': - resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.52.4': - resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==} + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.52.4': - resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==} + '@rollup/rollup-linux-arm64-gnu@4.52.5': + resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.52.4': - resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==} + '@rollup/rollup-linux-arm64-musl@4.52.5': + resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.52.4': - resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==} + '@rollup/rollup-linux-loong64-gnu@4.52.5': + resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.52.4': - resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==} + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.52.4': - resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==} + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.52.4': - resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==} + '@rollup/rollup-linux-riscv64-musl@4.52.5': + resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.52.4': - resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==} + '@rollup/rollup-linux-s390x-gnu@4.52.5': + resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.52.4': - resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==} + '@rollup/rollup-linux-x64-gnu@4.52.5': + resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.52.4': - resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==} + '@rollup/rollup-linux-x64-musl@4.52.5': + resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.52.4': - resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==} + '@rollup/rollup-openharmony-arm64@4.52.5': + resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.52.4': - resolution: {integrity: sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==} + '@rollup/rollup-win32-arm64-msvc@4.52.5': + resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.4': - resolution: {integrity: sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==} + '@rollup/rollup-win32-ia32-msvc@4.52.5': + resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.52.4': - resolution: {integrity: sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==} + '@rollup/rollup-win32-x64-gnu@4.52.5': + resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.4': - resolution: {integrity: sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==} + '@rollup/rollup-win32-x64-msvc@4.52.5': + resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} cpu: [x64] os: [win32] @@ -808,17 +802,14 @@ packages: '@shopify/shopify_function@2.0.0': resolution: {integrity: sha512-i00I0hkmYzD5TkoSwxk81FP0tKaG1M0bbgftF4nigi2NRvFwR5PJy6r1/kcB9w2zBx9xKPG/Z8C4cc2TanFf5Q==} - '@theguild/federation-composition@0.20.1': - resolution: {integrity: sha512-lwYYKCeHmstOtbMtzxC0BQKWsUPYbEVRVdJ3EqR4jSpcF4gvNf3MOJv6yuvq6QsKqgYZURKRBszmg7VEDoi5Aw==} + '@theguild/federation-composition@0.20.2': + resolution: {integrity: sha512-QI4iSdrc4JvCWnMb1QbiHnEpdD33KGdiU66qfWOcM8ENebRGHkGjXDnUrVJ8F9g1dmCRMTNfn2NFGqTcDpeYXw==} engines: {node: '>=18'} peerDependencies: graphql: ^16.0.0 - '@tybys/wasm-util@0.10.1': - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -829,11 +820,8 @@ packages: '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/node@22.18.10': - resolution: {integrity: sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==} + '@types/node@22.18.13': + resolution: {integrity: sha512-Bo45YKIjnmFtv6I1TuC8AaHBbqXtIo+Om5fE4QiU1Tj8QR/qt+8O3BAtOimG5IFmwaWiPmB3Mv3jtYzBA4Us2A==} '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -1153,8 +1141,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.18: - resolution: {integrity: sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==} + baseline-browser-mapping@2.8.21: + resolution: {integrity: sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q==} hasBin: true bl@4.1.0: @@ -1170,8 +1158,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.26.3: - resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} + browserslist@4.27.0: + resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1331,23 +1319,6 @@ packages: debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1402,12 +1373,8 @@ packages: resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} engines: {node: '>=4'} - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - electron-to-chromium@1.5.237: - resolution: {integrity: sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==} + electron-to-chromium@1.5.243: + resolution: {integrity: sha512-ZCphxFW3Q1TVhcgS9blfut1PX8lusVi2SvXQgmEEnK4TCmE1JhH2JkjJN+DNt0pJJwfBri5AROBnz2b/C+YU9g==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2216,8 +2183,8 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - magic-string@0.30.19: - resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} map-cache@0.2.2: resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} @@ -2298,8 +2265,8 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-releases@2.0.25: - resolution: {integrity: sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} normalize-path@2.1.1: resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} @@ -2525,8 +2492,13 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rollup@4.52.4: - resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==} + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.52.5: + resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2847,11 +2819,8 @@ packages: resolution: {integrity: sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==} engines: {node: '>=0.10.0'} - unrs-resolver@1.11.1: - resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2880,8 +2849,8 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@7.1.10: - resolution: {integrity: sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==} + vite@7.1.12: + resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3046,8 +3015,8 @@ snapshots: '@ardatan/relay-compiler@12.0.3(graphql@16.11.0)': dependencies: - '@babel/generator': 7.28.3 - '@babel/parser': 7.28.4 + '@babel/generator': 7.28.5 + '@babel/parser': 7.28.5 '@babel/runtime': 7.28.4 chalk: 4.1.2 fb-watchman: 2.0.2 @@ -3068,23 +3037,23 @@ snapshots: '@babel/code-frame@7.27.1': dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.4': {} + '@babel/compat-data@7.28.5': {} - '@babel/core@7.28.4': + '@babel/core@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -3094,19 +3063,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.3': + '@babel/generator@7.28.5': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.3 + browserslist: 4.27.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -3114,17 +3083,17 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -3132,22 +3101,22 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-option@7.27.1': {} '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 - '@babel/parser@7.28.4': + '@babel/parser@7.28.5': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 - '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 '@babel/runtime@7.28.4': {} @@ -3155,25 +3124,25 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 - '@babel/traverse@7.28.4': + '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.4': + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@emnapi/core@1.6.0': dependencies: @@ -3291,7 +3260,7 @@ snapshots: eslint: 9.38.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} '@eslint/config-array@0.21.1': dependencies: @@ -3340,23 +3309,23 @@ snapshots: graphql: 16.11.0 tslib: 2.6.3 - '@graphql-codegen/cli@5.0.5(@types/node@22.18.10)(graphql-sock@1.0.1(graphql@16.11.0))(graphql@16.11.0)(typescript@5.8.3)': + '@graphql-codegen/cli@5.0.5(@types/node@22.18.13)(graphql-sock@1.0.1(graphql@16.11.0))(graphql@16.11.0)(typescript@5.8.3)': dependencies: - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@graphql-codegen/client-preset': 4.8.3(graphql-sock@1.0.1(graphql@16.11.0))(graphql@16.11.0) '@graphql-codegen/core': 4.0.2(graphql@16.11.0) '@graphql-codegen/plugin-helpers': 5.1.1(graphql@16.11.0) '@graphql-tools/apollo-engine-loader': 8.0.22(graphql@16.11.0) '@graphql-tools/code-file-loader': 8.1.22(graphql@16.11.0) '@graphql-tools/git-loader': 8.0.26(graphql@16.11.0) - '@graphql-tools/github-loader': 8.0.22(@types/node@22.18.10)(graphql@16.11.0) + '@graphql-tools/github-loader': 8.0.22(@types/node@22.18.13)(graphql@16.11.0) '@graphql-tools/graphql-file-loader': 8.1.2(graphql@16.11.0) '@graphql-tools/json-file-loader': 8.0.20(graphql@16.11.0) '@graphql-tools/load': 8.1.2(graphql@16.11.0) - '@graphql-tools/prisma-loader': 8.0.17(@types/node@22.18.10)(graphql@16.11.0) - '@graphql-tools/url-loader': 8.0.16(@types/node@22.18.10)(graphql@16.11.0) + '@graphql-tools/prisma-loader': 8.0.17(@types/node@22.18.13)(graphql@16.11.0) + '@graphql-tools/url-loader': 8.0.16(@types/node@22.18.13)(graphql@16.11.0) '@graphql-tools/utils': 10.9.1(graphql@16.11.0) '@whatwg-node/fetch': 0.10.11 chalk: 4.1.2 @@ -3364,8 +3333,8 @@ snapshots: debounce: 1.2.1 detect-indent: 6.1.0 graphql: 16.11.0 - graphql-config: 5.1.3(@types/node@22.18.10)(graphql@16.11.0)(typescript@5.8.3) - inquirer: 8.2.7(@types/node@22.18.10) + graphql-config: 5.1.3(@types/node@22.18.13)(graphql@16.11.0)(typescript@5.8.3) + inquirer: 8.2.7(@types/node@22.18.13) is-glob: 4.0.3 jiti: 1.21.7 json-to-pretty-yaml: 1.2.2 @@ -3583,7 +3552,7 @@ snapshots: - bufferutil - utf-8-validate - '@graphql-tools/executor-http@1.3.3(@types/node@22.18.10)(graphql@16.11.0)': + '@graphql-tools/executor-http@1.3.3(@types/node@22.18.13)(graphql@16.11.0)': dependencies: '@graphql-hive/signal': 1.0.0 '@graphql-tools/executor-common': 0.0.4(graphql@16.11.0) @@ -3593,7 +3562,7 @@ snapshots: '@whatwg-node/fetch': 0.10.11 '@whatwg-node/promise-helpers': 1.3.2 graphql: 16.11.0 - meros: 1.3.2(@types/node@22.18.10) + meros: 1.3.2(@types/node@22.18.13) tslib: 2.8.1 transitivePeerDependencies: - '@types/node' @@ -3632,9 +3601,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@graphql-tools/github-loader@8.0.22(@types/node@22.18.10)(graphql@16.11.0)': + '@graphql-tools/github-loader@8.0.22(@types/node@22.18.13)(graphql@16.11.0)': dependencies: - '@graphql-tools/executor-http': 1.3.3(@types/node@22.18.10)(graphql@16.11.0) + '@graphql-tools/executor-http': 1.3.3(@types/node@22.18.13)(graphql@16.11.0) '@graphql-tools/graphql-tag-pluck': 8.3.21(graphql@16.11.0) '@graphql-tools/utils': 10.9.1(graphql@16.11.0) '@whatwg-node/fetch': 0.10.11 @@ -3659,11 +3628,11 @@ snapshots: '@graphql-tools/graphql-tag-pluck@8.3.21(graphql@16.11.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/parser': 7.28.4 - '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.4) - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.5) + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 '@graphql-tools/utils': 10.9.1(graphql@16.11.0) graphql: 16.11.0 tslib: 2.8.1 @@ -3673,7 +3642,7 @@ snapshots: '@graphql-tools/import@7.1.2(graphql@16.11.0)': dependencies: '@graphql-tools/utils': 10.9.1(graphql@16.11.0) - '@theguild/federation-composition': 0.20.1(graphql@16.11.0) + '@theguild/federation-composition': 0.20.2(graphql@16.11.0) graphql: 16.11.0 resolve-from: 5.0.0 tslib: 2.8.1 @@ -3707,9 +3676,9 @@ snapshots: graphql: 16.11.0 tslib: 2.6.3 - '@graphql-tools/prisma-loader@8.0.17(@types/node@22.18.10)(graphql@16.11.0)': + '@graphql-tools/prisma-loader@8.0.17(@types/node@22.18.13)(graphql@16.11.0)': dependencies: - '@graphql-tools/url-loader': 8.0.16(@types/node@22.18.10)(graphql@16.11.0) + '@graphql-tools/url-loader': 8.0.16(@types/node@22.18.13)(graphql@16.11.0) '@graphql-tools/utils': 10.9.1(graphql@16.11.0) '@types/js-yaml': 4.0.9 '@whatwg-node/fetch': 0.10.11 @@ -3749,11 +3718,11 @@ snapshots: graphql: 16.11.0 tslib: 2.8.1 - '@graphql-tools/url-loader@8.0.16(@types/node@22.18.10)(graphql@16.11.0)': + '@graphql-tools/url-loader@8.0.16(@types/node@22.18.13)(graphql@16.11.0)': dependencies: '@ardatan/sync-fetch': 0.0.1 '@graphql-tools/executor-graphql-ws': 1.3.7(graphql@16.11.0) - '@graphql-tools/executor-http': 1.3.3(@types/node@22.18.10)(graphql@16.11.0) + '@graphql-tools/executor-http': 1.3.3(@types/node@22.18.13)(graphql@16.11.0) '@graphql-tools/executor-legacy-ws': 1.1.19(graphql@16.11.0) '@graphql-tools/utils': 10.9.1(graphql@16.11.0) '@graphql-tools/wrap': 10.1.4(graphql@16.11.0) @@ -3803,12 +3772,12 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@inquirer/external-editor@1.0.2(@types/node@22.18.10)': + '@inquirer/external-editor@1.0.2(@types/node@22.18.13)': dependencies: chardet: 2.1.0 iconv-lite: 0.7.0 optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 22.18.13 '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -3852,118 +3821,80 @@ snapshots: '@repeaterjs/repeater@3.0.6': {} - '@rollup/rollup-android-arm-eabi@4.52.4': + '@rollup/rollup-android-arm-eabi@4.52.5': optional: true - '@rollup/rollup-android-arm64@4.52.4': + '@rollup/rollup-android-arm64@4.52.5': optional: true - '@rollup/rollup-darwin-arm64@4.52.4': + '@rollup/rollup-darwin-arm64@4.52.5': optional: true - '@rollup/rollup-darwin-x64@4.52.4': + '@rollup/rollup-darwin-x64@4.52.5': optional: true - '@rollup/rollup-freebsd-arm64@4.52.4': + '@rollup/rollup-freebsd-arm64@4.52.5': optional: true - '@rollup/rollup-freebsd-x64@4.52.4': + '@rollup/rollup-freebsd-x64@4.52.5': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.52.4': + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.52.4': + '@rollup/rollup-linux-arm-musleabihf@4.52.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.52.4': + '@rollup/rollup-linux-arm64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-arm64-musl@4.52.4': + '@rollup/rollup-linux-arm64-musl@4.52.5': optional: true - '@rollup/rollup-linux-loong64-gnu@4.52.4': + '@rollup/rollup-linux-loong64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.4': + '@rollup/rollup-linux-ppc64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.4': + '@rollup/rollup-linux-riscv64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.4': + '@rollup/rollup-linux-riscv64-musl@4.52.5': optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.4': + '@rollup/rollup-linux-s390x-gnu@4.52.5': optional: true - '@rollup/rollup-linux-x64-gnu@4.52.4': + '@rollup/rollup-linux-x64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-x64-musl@4.52.4': + '@rollup/rollup-linux-x64-musl@4.52.5': optional: true - '@rollup/rollup-openharmony-arm64@4.52.4': + '@rollup/rollup-openharmony-arm64@4.52.5': optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.4': + '@rollup/rollup-win32-arm64-msvc@4.52.5': optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.4': + '@rollup/rollup-win32-ia32-msvc@4.52.5': optional: true - '@rollup/rollup-win32-x64-gnu@4.52.4': + '@rollup/rollup-win32-x64-gnu@4.52.5': optional: true - '@rollup/rollup-win32-x64-msvc@4.52.4': + '@rollup/rollup-win32-x64-msvc@4.52.5': optional: true - '@shopify/eslint-plugin@50.0.0(@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(prettier@3.6.2)(typescript@5.9.3)': - dependencies: - change-case: 4.1.2 - common-tags: 1.8.2 - doctrine: 2.1.0 - eslint: 9.38.0(jiti@2.6.1) - eslint-config-prettier: 9.1.2(eslint@9.38.0(jiti@2.6.1)) - eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-eslint-comments: 3.2.0(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-jest: 28.14.0(@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - eslint-plugin-jest-formatting: 3.1.0(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-n: 17.23.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - eslint-plugin-prettier: 5.5.4(eslint-config-prettier@9.1.2(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1))(prettier@3.6.2) - eslint-plugin-promise: 7.2.1(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-react: 7.37.5(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-react-hooks: 5.2.0(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-sort-class-members: 1.21.0(eslint@9.38.0(jiti@2.6.1)) - globals: 15.15.0 - jsx-ast-utils: 3.3.5 - pkg-dir: 5.0.0 - pluralize: 8.0.0 - typescript-eslint: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - transitivePeerDependencies: - - '@types/eslint' - - '@typescript-eslint/eslint-plugin' - - '@typescript-eslint/parser' - - '@typescript-eslint/utils' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - eslint-plugin-import - - jest - - prettier - - supports-color - - typescript - - '@shopify/shopify_function@2.0.0(@types/node@22.18.10)(graphql-sock@1.0.1(graphql@16.11.0))': + '@shopify/shopify_function@2.0.0(@types/node@22.18.13)(graphql-sock@1.0.1(graphql@16.11.0))': dependencies: - '@graphql-codegen/cli': 5.0.5(@types/node@22.18.10)(graphql-sock@1.0.1(graphql@16.11.0))(graphql@16.11.0)(typescript@5.8.3) + '@graphql-codegen/cli': 5.0.5(@types/node@22.18.13)(graphql-sock@1.0.1(graphql@16.11.0))(graphql@16.11.0)(typescript@5.8.3) '@graphql-codegen/typescript': 4.1.6(graphql@16.11.0) '@graphql-codegen/typescript-operations': 4.6.0(graphql-sock@1.0.1(graphql@16.11.0))(graphql@16.11.0) - '@graphql-tools/url-loader': 8.0.16(@types/node@22.18.10)(graphql@16.11.0) + '@graphql-tools/url-loader': 8.0.16(@types/node@22.18.13)(graphql@16.11.0) graphql: 16.11.0 - graphql-config: 5.1.3(@types/node@22.18.10)(graphql@16.11.0)(typescript@5.8.3) + graphql-config: 5.1.3(@types/node@22.18.13)(graphql@16.11.0)(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - '@parcel/watcher' @@ -3976,24 +3907,20 @@ snapshots: - supports-color - utf-8-validate - '@theguild/federation-composition@0.20.1(graphql@16.11.0)': + '@theguild/federation-composition@0.20.2(graphql@16.11.0)': dependencies: constant-case: 3.0.4 - debug: 4.4.1 + debug: 4.4.3 graphql: 16.11.0 json5: 2.2.3 lodash.sortby: 4.7.0 transitivePeerDependencies: - supports-color - '@tybys/wasm-util@0.10.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/chai@5.2.2': + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 '@types/deep-eql@4.0.2': {} @@ -4001,15 +3928,13 @@ snapshots: '@types/js-yaml@4.0.9': {} - '@types/json-schema@7.0.15': {} - - '@types/node@22.18.10': + '@types/node@22.18.13': dependencies: undici-types: 6.21.0 '@types/ws@8.18.1': dependencies: - '@types/node': 22.18.10 + '@types/node': 22.18.13 '@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: @@ -4165,19 +4090,19 @@ snapshots: '@vitest/expect@3.2.4': dependencies: - '@types/chai': 5.2.2 + '@types/chai': 5.2.3 '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.10(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(vite@7.1.12(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 - magic-string: 0.30.19 + magic-string: 0.30.21 optionalDependencies: - vite: 7.1.10(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -4192,7 +4117,7 @@ snapshots: '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 - magic-string: 0.30.19 + magic-string: 0.30.21 pathe: 2.0.3 '@vitest/spy@3.2.4': @@ -4347,7 +4272,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.18: {} + baseline-browser-mapping@2.8.21: {} bl@4.1.0: dependencies: @@ -4368,13 +4293,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.26.3: + browserslist@4.27.0: dependencies: - baseline-browser-mapping: 2.8.18 + baseline-browser-mapping: 2.8.21 caniuse-lite: 1.0.30001751 - electron-to-chromium: 1.5.237 - node-releases: 2.0.25 - update-browserslist-db: 1.1.3(browserslist@4.26.3) + electron-to-chromium: 1.5.243 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.27.0) bser@2.1.1: dependencies: @@ -4560,14 +4485,6 @@ snapshots: debounce@1.2.1: {} - debug@3.2.7: - dependencies: - ms: 2.1.3 - - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -4613,13 +4530,7 @@ snapshots: dset@3.1.4: {} - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - electron-to-chromium@1.5.237: {} + electron-to-chromium@1.5.243: {} emoji-regex@8.0.0: {} @@ -4946,15 +4857,11 @@ snapshots: eslint@9.38.0(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.21.1 - '@eslint/config-helpers': 0.4.1 - '@eslint/core': 0.16.0 - '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.38.0 - '@eslint/plugin-kit': 0.4.0 - '@humanfs/node': 0.16.7 + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 @@ -5172,13 +5079,13 @@ snapshots: graphemer@1.4.0: {} - graphql-config@5.1.3(@types/node@22.18.10)(graphql@16.11.0)(typescript@5.8.3): + graphql-config@5.1.3(@types/node@22.18.13)(graphql@16.11.0)(typescript@5.8.3): dependencies: '@graphql-tools/graphql-file-loader': 8.1.2(graphql@16.11.0) '@graphql-tools/json-file-loader': 8.0.20(graphql@16.11.0) '@graphql-tools/load': 8.1.2(graphql@16.11.0) '@graphql-tools/merge': 9.1.1(graphql@16.11.0) - '@graphql-tools/url-loader': 8.0.16(@types/node@22.18.10)(graphql@16.11.0) + '@graphql-tools/url-loader': 8.0.16(@types/node@22.18.13)(graphql@16.11.0) '@graphql-tools/utils': 10.9.1(graphql@16.11.0) cosmiconfig: 8.3.6(typescript@5.8.3) graphql: 16.11.0 @@ -5283,9 +5190,9 @@ snapshots: inherits@2.0.4: {} - inquirer@8.2.7(@types/node@22.18.10): + inquirer@8.2.7(@types/node@22.18.13): dependencies: - '@inquirer/external-editor': 1.0.2(@types/node@22.18.10) + '@inquirer/external-editor': 1.0.2(@types/node@22.18.13) ansi-escapes: 4.3.2 chalk: 4.1.2 cli-cursor: 3.1.0 @@ -5579,7 +5486,7 @@ snapshots: dependencies: yallist: 3.1.1 - magic-string@0.30.19: + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -5589,9 +5496,9 @@ snapshots: merge2@1.4.1: {} - meros@1.3.2(@types/node@22.18.10): + meros@1.3.2(@types/node@22.18.13): optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 22.18.13 micromatch@4.0.8: dependencies: @@ -5637,7 +5544,7 @@ snapshots: node-int64@0.4.0: {} - node-releases@2.0.25: {} + node-releases@2.0.27: {} normalize-path@2.1.1: dependencies: @@ -5881,32 +5788,36 @@ snapshots: rfdc@1.4.1: {} - rollup@4.52.4: + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.52.5: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.4 - '@rollup/rollup-android-arm64': 4.52.4 - '@rollup/rollup-darwin-arm64': 4.52.4 - '@rollup/rollup-darwin-x64': 4.52.4 - '@rollup/rollup-freebsd-arm64': 4.52.4 - '@rollup/rollup-freebsd-x64': 4.52.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.4 - '@rollup/rollup-linux-arm-musleabihf': 4.52.4 - '@rollup/rollup-linux-arm64-gnu': 4.52.4 - '@rollup/rollup-linux-arm64-musl': 4.52.4 - '@rollup/rollup-linux-loong64-gnu': 4.52.4 - '@rollup/rollup-linux-ppc64-gnu': 4.52.4 - '@rollup/rollup-linux-riscv64-gnu': 4.52.4 - '@rollup/rollup-linux-riscv64-musl': 4.52.4 - '@rollup/rollup-linux-s390x-gnu': 4.52.4 - '@rollup/rollup-linux-x64-gnu': 4.52.4 - '@rollup/rollup-linux-x64-musl': 4.52.4 - '@rollup/rollup-openharmony-arm64': 4.52.4 - '@rollup/rollup-win32-arm64-msvc': 4.52.4 - '@rollup/rollup-win32-ia32-msvc': 4.52.4 - '@rollup/rollup-win32-x64-gnu': 4.52.4 - '@rollup/rollup-win32-x64-msvc': 4.52.4 + '@rollup/rollup-android-arm-eabi': 4.52.5 + '@rollup/rollup-android-arm64': 4.52.5 + '@rollup/rollup-darwin-arm64': 4.52.5 + '@rollup/rollup-darwin-x64': 4.52.5 + '@rollup/rollup-freebsd-arm64': 4.52.5 + '@rollup/rollup-freebsd-x64': 4.52.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 + '@rollup/rollup-linux-arm-musleabihf': 4.52.5 + '@rollup/rollup-linux-arm64-gnu': 4.52.5 + '@rollup/rollup-linux-arm64-musl': 4.52.5 + '@rollup/rollup-linux-loong64-gnu': 4.52.5 + '@rollup/rollup-linux-ppc64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-musl': 4.52.5 + '@rollup/rollup-linux-s390x-gnu': 4.52.5 + '@rollup/rollup-linux-x64-gnu': 4.52.5 + '@rollup/rollup-linux-x64-musl': 4.52.5 + '@rollup/rollup-openharmony-arm64': 4.52.5 + '@rollup/rollup-win32-arm64-msvc': 4.52.5 + '@rollup/rollup-win32-ia32-msvc': 4.52.5 + '@rollup/rollup-win32-x64-gnu': 4.52.5 + '@rollup/rollup-win32-x64-msvc': 4.52.5 fsevents: 2.3.3 run-async@2.4.1: {} @@ -6265,33 +6176,9 @@ snapshots: dependencies: normalize-path: 2.1.1 - unrs-resolver@1.11.1: + update-browserslist-db@1.1.4(browserslist@4.27.0): dependencies: - napi-postinstall: 0.3.4 - optionalDependencies: - '@unrs/resolver-binding-android-arm-eabi': 1.11.1 - '@unrs/resolver-binding-android-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-x64': 1.11.1 - '@unrs/resolver-binding-freebsd-x64': 1.11.1 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 - '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-musl': 1.11.1 - '@unrs/resolver-binding-wasm32-wasi': 1.11.1 - '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 - '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 - '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - - update-browserslist-db@1.1.3(browserslist@4.26.3): - dependencies: - browserslist: 4.26.3 + browserslist: 4.27.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -6313,13 +6200,13 @@ snapshots: value-or-promise@1.0.12: {} - vite-node@3.2.4(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1): + vite-node@3.2.4(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.10(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -6334,25 +6221,25 @@ snapshots: - tsx - yaml - vite@7.1.10(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1): + vite@7.1.12(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1): dependencies: esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.4 + rollup: 4.52.5 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 22.18.13 fsevents: 2.3.3 jiti: 2.6.1 yaml: 2.8.1 - vitest@3.2.4(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1): + vitest@3.2.4(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1): dependencies: - '@types/chai': 5.2.2 + '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.10(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.1.12(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -6361,7 +6248,7 @@ snapshots: chai: 5.3.3 debug: 4.4.3 expect-type: 1.2.2 - magic-string: 0.30.19 + magic-string: 0.30.21 pathe: 2.0.3 picomatch: 4.0.3 std-env: 3.10.0 @@ -6370,11 +6257,11 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.10(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@22.18.13)(jiti@2.6.1)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.18.10 + '@types/node': 22.18.13 transitivePeerDependencies: - jiti - less diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index afce704..8e6d098 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -3,12 +3,12 @@ import { DocumentNode, getNamedType, getNullableType, - GraphQLCompositeType, GraphQLList, GraphQLNamedType, GraphQLSchema, isAbstractType, isInputType, + isLeafType, isListType, isNullableType, isObjectType, @@ -16,6 +16,7 @@ import { TypeInfo, visit, visitWithTypeInfo, + BREAK, } from "graphql"; import { inlineNamedFragmentSpreads } from "../utils/inline-named-fragment-spreads.js"; @@ -43,7 +44,10 @@ export function validateFixtureInput( const inlineFragmentSpreadsAst = inlineNamedFragmentSpreads(queryAST); const typeInfo = new TypeInfo(schema); const valueStack: any[][] = [[value]]; + // based on field depth const typeStack: (GraphQLNamedType | undefined)[] = []; + // based on selection set depth + const possibleTypesStack: Set[] = [new Set([schema.getQueryType()!.name])]; const typenameResponseKeyStack: (string | undefined)[] = []; const errors: string[] = []; @@ -51,6 +55,23 @@ export function validateFixtureInput( visit( inlineFragmentSpreadsAst, visitWithTypeInfo(typeInfo, { + InlineFragment: { + enter(node) { + let possibleTypes = new Set(possibleTypesStack[possibleTypesStack.length - 1]); + if (node.typeCondition) { + const namedType = schema.getType(node.typeCondition!.name.value); + if (namedType && isAbstractType(namedType)) { + possibleTypes = possibleTypes.intersection(new Set(schema.getPossibleTypes(namedType).map(type => type.name))); + } else if (namedType && isObjectType(namedType)) { + possibleTypes = new Set([namedType.name]); + } + } + possibleTypesStack.push(possibleTypes); + }, + leave() { + possibleTypesStack.pop(); + }, + }, Field: { enter(node) { const currentValues = valueStack[valueStack.length - 1]; @@ -59,9 +80,11 @@ export function validateFixtureInput( const responseKey = node.alias?.value || node.name.value; const fieldDefinition = typeInfo.getFieldDef(); - const fieldType = fieldDefinition?.type; - - typeStack.push(getNamedType(fieldType)); + if (fieldDefinition === undefined || fieldDefinition === null) { + errors.push(`Cannot validate ${responseKey}: missing field definition`); + return BREAK; + } + const fieldType = fieldDefinition.type; for (const currentValue of currentValues) { const valueForResponseKey = currentValue[responseKey]; @@ -76,8 +99,11 @@ export function validateFixtureInput( errors.push(`Cannot validate ${responseKey}: missing parent type information`); } else { const typenameResponseKey = typenameResponseKeyStack[typenameResponseKeyStack.length - 1]; - const grandparentType = typeStack[typeStack.length - 2]; - if (isValueExpectedForType(currentValue, parentType, grandparentType, schema, typenameResponseKey)) { + // Get the type of the parent field (the field that returned this object) + // This skips inline fragments since they don't push to typeStack + const parentFieldType = typeStack[typeStack.length - 1]!; + const possibleTypes = possibleTypesStack[possibleTypesStack.length - 1]; + if (isValueExpectedForType(currentValue, parentFieldType, possibleTypes, schema, typenameResponseKey)) { errors.push(`Missing expected fixture data for ${responseKey}`); } } @@ -144,11 +170,25 @@ export function validateFixtureInput( } } + const namedType = getNamedType(fieldType); + let possibleTypes: string[] = []; + if (isLeafType(namedType)) { + // do nothing + } else if (isAbstractType(namedType)) { + possibleTypes = schema.getPossibleTypes(namedType).map(type => type.name); + } else if (isObjectType(namedType)) { + possibleTypes = [namedType.name]; + } + + possibleTypesStack.push(new Set(possibleTypes)); + typeStack.push(getNamedType(fieldType)); + valueStack.push(nestedValues); }, leave() { valueStack.pop(); typeStack.pop(); + possibleTypesStack.pop(); }, }, SelectionSet: { @@ -261,48 +301,48 @@ function processNestedArrays( } /** - * Determines if a fixture value is expected for a given parent type based on its __typename. + * Determines if a fixture value is expected * * @param fixtureValue - The fixture value to check - * @param parentType - The parent type from typeInfo - * @param grandparentType - The type returned by the grandparent field (used to detect union/interface contexts) + * @param parentFieldType - The type returned by the parent field (e.g., InterfaceImplementersUnion from `interfaceImplementers` field) + * @param possibleTypes - Set of possible type names after inline fragment narrowing (e.g., {"InterfaceImplementer1"} after `...on HasDescription`) * @param schema - The GraphQL schema to resolve possible types for abstract types * @param typenameKey - The response key for the __typename field (supports aliases like `type: __typename`) - * @returns True if the value is expected for the parent type, false otherwise + * @returns True if the value is expected (field should be present), false otherwise (field can be skipped) * * @remarks - * When the parent type is abstract (union/interface), checks if the value's __typename - * is one of the possible types for that abstract type. - * When the parent type is concrete (e.g., inside `... on ConcreteType`), only values - * whose __typename matches the concrete type are expected. + * When __typename is selected: + * - Checks if value's __typename is in the possibleTypes set * - * Special case: Empty objects {} are valid when __typename is not selected and the inline - * fragment narrows the possible types. + * When __typename is NOT selected: + * - Compares parent field's possible types vs current possibleTypes using symmetricDifference + * - If sets differ (narrowing occurred), empty objects {} are valid (return false) + * - Non-empty objects conservatively expect all fields (return true) + * - Since typeStack only tracks fields (not inline fragments), parentFieldType is the original + * field's type, enabling correct narrowing detection at any nesting depth */ function isValueExpectedForType( fixtureValue: any, - parentType: GraphQLCompositeType, - grandparentType: GraphQLNamedType | undefined, + parentFieldType: GraphQLNamedType, + possibleTypes: Set, schema: GraphQLSchema, typenameKey?: string ): boolean { - // If __typename wasn't selected in the query, we can't discriminate if (!typenameKey) { - // Empty objects {} are valid only if we've narrowed the type through inline fragments - // Compare the set of possible types at grandparent vs parent level - if (grandparentType && isAbstractType(grandparentType) && Object.keys(fixtureValue).length === 0) { - const grandparentPossibleTypes = schema.getPossibleTypes(grandparentType); - const parentPossibleTypes = isAbstractType(parentType) - ? schema.getPossibleTypes(parentType) - : [parentType]; + let parentFieldPossibleTypes: string[] = []; + if (isAbstractType(parentFieldType)) { + parentFieldPossibleTypes = schema.getPossibleTypes(parentFieldType).map(type => type.name); + } else { + parentFieldPossibleTypes = [parentFieldType.name]; + } - // If the sets are different, we've narrowed the type → empty objects are valid (return false) - // If the sets are equal, no narrowing happened → empty objects are invalid (return true) - const setsAreEqual = grandparentPossibleTypes.length === parentPossibleTypes.length && - grandparentPossibleTypes.every(t => parentPossibleTypes.includes(t)); + const parentFieldPossibleTypesSet = new Set(parentFieldPossibleTypes); + const difference = parentFieldPossibleTypesSet.symmetricDifference(possibleTypes); - return setsAreEqual; + if (difference.size > 0 && Object.keys(fixtureValue).length === 0) { + return false; } + return true; // Otherwise, expect all values } @@ -312,12 +352,5 @@ function isValueExpectedForType( return true; } - // If parent type is abstract (union/interface), check if the value's type is one of the possible types - if (isAbstractType(parentType)) { - const possibleTypes = schema.getPossibleTypes(parentType); - return possibleTypes.some(type => type.name === valueTypename); - } - - // Parent is a concrete type - check if fixture value's __typename matches - return valueTypename === parentType.name; + return possibleTypes.has(valueTypename); } diff --git a/test/fixtures/test-schema.graphql b/test/fixtures/test-schema.graphql index 5537917..f6f64f7 100644 --- a/test/fixtures/test-schema.graphql +++ b/test/fixtures/test-schema.graphql @@ -17,6 +17,7 @@ type DataContainer { metadataCube: [[[Metadata]]] products: [Product!]! purchasable: Purchasable + interfaceImplementers: [InterfaceImplementersUnion!]! } union SearchResult = Item | Metadata @@ -28,6 +29,39 @@ interface Purchasable { currency: String! } +interface HasId { + id: ID! +} + +interface HasName { + name: String! +} + +interface HasDescription { + description: String! +} + +type InterfaceImplementer1 implements HasId & HasName & HasDescription { + id: ID! + name: String! + description: String! +} + +type InterfaceImplementer2 implements HasId & HasName { + id: ID! + name: String! +} + +type InterfaceImplementer3 implements HasId { + id: ID! +} + +type InterfaceImplementer4 { + value: String +} + +union InterfaceImplementersUnion = InterfaceImplementer1 | InterfaceImplementer2 | InterfaceImplementer3 | InterfaceImplementer4 + type Item { id: ID count: Int! diff --git a/test/methods/validate-fixture-input.test.ts b/test/methods/validate-fixture-input.test.ts index 7053d15..742ec6a 100644 --- a/test/methods/validate-fixture-input.test.ts +++ b/test/methods/validate-fixture-input.test.ts @@ -355,6 +355,204 @@ describe("validateFixtureInput", () => { expect(result.errors).toHaveLength(0); }); + it("handles deeply nested interface fragments with empty objects", () => { + const queryAST = parse(` + query { + data { + interfaceImplementers { + ...on HasId { + id + ...on HasName { + name + ...on HasDescription { + description + } + } + } + } + } + } + `); + + const fixtureInput = { + data: { + interfaceImplementers: [ + { + id: "1", + name: "Implementer1", + description: "Has all three interfaces" + }, + { + id: "2", + name: "Implementer2", + description: "Also has all three" + }, + {} + // Empty object - InterfaceImplementer4 that doesn't implement any interface + // Valid because InterfaceImplementersUnion {1,2,3,4} was narrowed to HasId {1,2,3} + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // Empty object is valid - demonstrates narrowing through 3 nested interface fragments + // Progressive narrowing: InterfaceImplementersUnion {1,2,3,4} → HasId {1,2,3} → HasName {1,2} → HasDescription {1} + // parentFieldType = InterfaceImplementersUnion (4 types), parentType = HasId (3 types after first fragment) + // Sets are different → type was narrowed → empty object valid + expect(result.errors).toHaveLength(0); + }); + + it("handles deeply nested fragments with field only at innermost level", () => { + const queryAST = parse(` + query { + data { + interfaceImplementers { + ...on HasId { + ...on HasName { + ...on HasDescription { + description + } + } + } + } + } + } + `); + + const fixtureInput = { + data: { + interfaceImplementers: [ + { + description: "Implementer1 - implements all three" + }, + {}, + {}, + {} + // Three empty objects representing Implementer2, 3, 4 that don't implement HasDescription + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // All empty objects are valid - they don't match the HasDescription fragment + // parentFieldType = InterfaceImplementersUnion (4 types) + // parentType = HasDescription (1 type) + // Sets are different → narrowing detected → empty objects valid + expect(result.errors).toHaveLength(0); + }); + + it("handles nested interface fragments with __typename and partial field sets", () => { + const queryAST = parse(` + query { + data { + interfaceImplementers { + __typename + ...on HasId { + id + ...on HasName { + name + ...on HasDescription { + description + } + } + } + } + } + } + `); + + const fixtureInput = { + data: { + interfaceImplementers: [ + { + __typename: "InterfaceImplementer1", + id: "1", + name: "Implementer1", + description: "Implements all three" + }, + { + __typename: "InterfaceImplementer2", + id: "2", + name: "Implementer2" + // Implements HasId & HasName, but not HasDescription + }, + { + __typename: "InterfaceImplementer3", + id: "3" + // Implements HasId only + }, + { + __typename: "InterfaceImplementer4" + // Doesn't implement any interface + } + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // With __typename, validator correctly discriminates which fields are expected for each type + expect(result.errors).toHaveLength(0); + }); + + it("requires __typename for nested interface fragments with partial field sets", () => { + const queryAST = parse(` + query { + data { + interfaceImplementers { + ...on HasId { + id + ...on HasName { + name + ...on HasDescription { + description + } + } + } + } + } + } + `); + + const fixtureInput = { + data: { + interfaceImplementers: [ + { + id: "1", + name: "Implementer1", + description: "Implements all three" + }, + { + id: "2", + name: "Implementer2" + // InterfaceImplementer2: implements HasId & HasName, but not HasDescription + // This is a valid response - nested fragment doesn't match + }, + { + id: "3" + // InterfaceImplementer3: implements HasId only + // This is a valid response - nested fragments don't match + }, + {} + // InterfaceImplementer4: doesn't implement any interface + // Empty object is valid - handled by empty object logic + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // Without __typename, validator cannot determine if missing fields are valid + // (due to type not implementing nested interfaces) or invalid (incomplete data) + // So it conservatively expects all selected fields on non-empty objects + expect(result.errors).toHaveLength(3); + expect(result.errors[0]).toBe("Missing expected fixture data for name"); + expect(result.errors[1]).toBe("Missing expected fixture data for description"); + expect(result.errors[2]).toBe("Missing expected fixture data for description"); + }); + it("handles objects with only __typename when inline fragment doesn't match", () => { const queryAST = parse(` query { @@ -1153,7 +1351,7 @@ describe("validateFixtureInput", () => { // Should detect missing type information for the invalid field expect(result.errors).toHaveLength(1); - expect(result.errors[0]).toBe('Cannot validate nonExistentField: missing type information'); + expect(result.errors[0]).toBe('Cannot validate nonExistentField: missing field definition'); }); it("detects empty objects in non-union context", () => { diff --git a/tsconfig.json b/tsconfig.json index dec559e..e5184c3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { - "target": "ES2020", + "target": "ES2024", + "lib": ["ESNext"], "module": "ESNext", "moduleResolution": "Node", "outDir": "./dist", From 23538e3d65530100a7e20334abcee301ecd8d0a8 Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Wed, 29 Oct 2025 16:04:10 -0400 Subject: [PATCH 08/13] use core-js to support node 20 --- package.json | 3 ++- pnpm-lock.yaml | 8 ++++++++ src/methods/validate-fixture-input.ts | 4 +++- tsconfig.json | 4 ++-- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 76591e4..2691edc 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "prepublishOnly": "pnpm run build && pnpm run test && pnpm run lint" }, "dependencies": { + "core-js": "^3.46.0", "graphql": "^16.11.0" }, "devDependencies": { @@ -38,7 +39,7 @@ "vitest": "^3.2.4" }, "engines": { - "node": ">=22.0.0" + "node": ">=20.0.0" }, "author": "lopert", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43482b5..d881985 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + core-js: + specifier: ^3.46.0 + version: 3.46.0 graphql: specifier: ^16.11.0 version: 16.11.0 @@ -1274,6 +1277,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + core-js@3.46.0: + resolution: {integrity: sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==} + cosmiconfig@8.3.6: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} @@ -4434,6 +4440,8 @@ snapshots: convert-source-map@2.0.0: {} + core-js@3.46.0: {} + cosmiconfig@8.3.6(typescript@5.8.3): dependencies: import-fresh: 3.3.1 diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index 8e6d098..7ff262c 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -1,3 +1,5 @@ +import "core-js/actual/set/intersection.js"; +import "core-js/actual/set/symmetric-difference.js"; import { coerceInputValue, DocumentNode, @@ -315,7 +317,7 @@ function processNestedArrays( * - Checks if value's __typename is in the possibleTypes set * * When __typename is NOT selected: - * - Compares parent field's possible types vs current possibleTypes using symmetricDifference + * - Compares parent field's possible types vs current possibleTypes * - If sets differ (narrowing occurred), empty objects {} are valid (return false) * - Non-empty objects conservatively expect all fields (return true) * - Since typeStack only tracks fields (not inline fragments), parentFieldType is the original diff --git a/tsconfig.json b/tsconfig.json index e5184c3..7905eb2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "target": "ES2024", - "lib": ["ESNext"], + "target": "ES2020", + "lib": ["ES2020", "ESNext.Collection"], "module": "ESNext", "moduleResolution": "Node", "outDir": "./dist", From 5fc9c578508d9d3a038c9c6ea473b4ba8759d06e Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Thu, 30 Oct 2025 11:35:03 -0400 Subject: [PATCH 09/13] clean up leftover parentType usage --- src/methods/validate-fixture-input.ts | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index 7ff262c..5354e7c 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -60,8 +60,8 @@ export function validateFixtureInput( InlineFragment: { enter(node) { let possibleTypes = new Set(possibleTypesStack[possibleTypesStack.length - 1]); - if (node.typeCondition) { - const namedType = schema.getType(node.typeCondition!.name.value); + if (node.typeCondition !== null && node.typeCondition !== undefined) { + const namedType = schema.getType(node.typeCondition.name.value); if (namedType && isAbstractType(namedType)) { possibleTypes = possibleTypes.intersection(new Set(schema.getPossibleTypes(namedType).map(type => type.name))); } else if (namedType && isObjectType(namedType)) { @@ -93,22 +93,13 @@ export function validateFixtureInput( // Field is missing from fixture if (valueForResponseKey === undefined) { - const parentType = typeInfo.getParentType(); - if (!parentType) { - // This shouldn't happen with a valid query and schema - TypeInfo should always - // provide parent type information when traversing fields. This check is here to - // satisfy TypeScript's type requirements (getParentType() can return null). - errors.push(`Cannot validate ${responseKey}: missing parent type information`); - } else { - const typenameResponseKey = typenameResponseKeyStack[typenameResponseKeyStack.length - 1]; - // Get the type of the parent field (the field that returned this object) - // This skips inline fragments since they don't push to typeStack - const parentFieldType = typeStack[typeStack.length - 1]!; - const possibleTypes = possibleTypesStack[possibleTypesStack.length - 1]; - if (isValueExpectedForType(currentValue, parentFieldType, possibleTypes, schema, typenameResponseKey)) { - errors.push(`Missing expected fixture data for ${responseKey}`); - } + const typenameResponseKey = typenameResponseKeyStack[typenameResponseKeyStack.length - 1]; + const parentFieldType = typeStack[typeStack.length - 1]!; + const possibleTypes = possibleTypesStack[possibleTypesStack.length - 1]; + if (isValueExpectedForType(currentValue, parentFieldType, possibleTypes, schema, typenameResponseKey)) { + errors.push(`Missing expected fixture data for ${responseKey}`); } + } // Scalars and Enums (including wrapped types) else if (isInputType(fieldType)) { From 944ea12152fe176104e98c17afd36596553651c3 Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Thu, 30 Oct 2025 13:10:58 -0400 Subject: [PATCH 10/13] only inherit typenameResponseKey when in an inlineFragment --- src/methods/validate-fixture-input.ts | 17 ++-- test/fixtures/test-schema.graphql | 22 +++++ test/methods/validate-fixture-input.test.ts | 99 +++++++++++++++++++++ 3 files changed, 132 insertions(+), 6 deletions(-) diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index 5354e7c..528b925 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -185,7 +185,7 @@ export function validateFixtureInput( }, }, SelectionSet: { - enter(node) { + enter(node, _key, parent) { // Look ahead to find __typename field and track its response key const typenameField = node.selections.find( (selection) => @@ -193,11 +193,16 @@ export function validateFixtureInput( selection.name.value === "__typename" ); - // If this SelectionSet has __typename, use its response key. - // Otherwise, inherit from parent. - const typenameResponseKey = typenameField && typenameField.kind === Kind.FIELD - ? typenameField.alias?.value || "__typename" - : typenameResponseKeyStack[typenameResponseKeyStack.length - 1]; + let typenameResponseKey: string | undefined; + if (typenameField && typenameField.kind === Kind.FIELD) { + typenameResponseKey = typenameField.alias?.value || "__typename"; + } else if (parent && 'kind' in parent && parent.kind === Kind.INLINE_FRAGMENT) { + // Inside an inline fragment without __typename - inherit from parent SelectionSet + typenameResponseKey = typenameResponseKeyStack[typenameResponseKeyStack.length - 1]; + } else { + // Field SelectionSet or root level - don't inherit (new object context) + typenameResponseKey = undefined; + } typenameResponseKeyStack.push(typenameResponseKey); diff --git a/test/fixtures/test-schema.graphql b/test/fixtures/test-schema.graphql index f6f64f7..b8565c2 100644 --- a/test/fixtures/test-schema.graphql +++ b/test/fixtures/test-schema.graphql @@ -18,6 +18,7 @@ type DataContainer { products: [Product!]! purchasable: Purchasable interfaceImplementers: [InterfaceImplementersUnion!]! + nested: [NestedOuter!]! } union SearchResult = Item | Metadata @@ -62,6 +63,27 @@ type InterfaceImplementer4 { union InterfaceImplementersUnion = InterfaceImplementer1 | InterfaceImplementer2 | InterfaceImplementer3 | InterfaceImplementer4 +union NestedOuter = NestedOuterA | NestedOuterB + +type NestedOuterA { + id: ID + inner: [NestedInner!]! +} + +type NestedOuterB { + email: String +} + +union NestedInner = NestedInnerA | NestedInnerB + +type NestedInnerA { + name: String! +} + +type NestedInnerB { + value: String! +} + type Item { id: ID count: Int! diff --git a/test/methods/validate-fixture-input.test.ts b/test/methods/validate-fixture-input.test.ts index 742ec6a..1fd720b 100644 --- a/test/methods/validate-fixture-input.test.ts +++ b/test/methods/validate-fixture-input.test.ts @@ -211,6 +211,105 @@ describe("validateFixtureInput", () => { expect(result.errors).toHaveLength(0); }); + it("should not inherit typename key across field boundaries", () => { + const queryAST = parse(` + query { + data { + nested { + outerType: __typename + ... on NestedOuterA { + id + inner { + ... on NestedInnerA { + name + } + ... on NestedInnerB { + value + } + } + } + } + } + } + `); + + const fixtureInput = { + data: { + nested: [ + { + outerType: "NestedOuterA", + id: "1", + inner: [ + { + name: "Inner name" + // No __typename - query doesn't select it for inner + } + ] + } + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // - inner SelectionSet has typenameResponseKey = undefined (doesn't inherit "outerType") + // - Error 1: Missing __typename (inner has 2+ fragments without it) + // - Error 2: Missing value field (without typename, can't discriminate, expects all fields) + expect(result.errors).toHaveLength(2); + expect(result.errors[0]).toBe("Missing __typename field for abstract type NestedInner"); + expect(result.errors[1]).toBe("Missing expected fixture data for value"); + }); + + it("handles nested unions with typename at each level", () => { + // Same structure as previous test, but WITH __typename at inner level + const queryAST = parse(` + query { + data { + nested { + outerType: __typename + ... on NestedOuterA { + id + inner { + __typename + ... on NestedInnerA { + name + } + ... on NestedInnerB { + value + } + } + } + } + } + } + `); + + const fixtureInput = { + data: { + nested: [ + { + outerType: "NestedOuterA", + id: "1", + inner: [ + { + __typename: "NestedInnerA", + name: "Inner name" + } + ] + } + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // With __typename selected at inner level, validation works correctly + // - outer uses "outerType" alias + // - inner uses "__typename" (not inherited) + // - Each level properly scoped to its field + expect(result.errors).toHaveLength(0); + }); + it("handles inline fragment on interface type", () => { const queryAST = parse(` query { From 421ec407283ae0ea952602382f0a48cd0b606dac Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Thu, 30 Oct 2025 13:44:29 -0400 Subject: [PATCH 11/13] early break for missing typename on abstract type --- src/methods/validate-fixture-input.ts | 1 + test/fixtures/test-schema.graphql | 4 +- test/methods/validate-fixture-input.test.ts | 59 +++++++++++++++------ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index 528b925..3116f28 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -223,6 +223,7 @@ export function validateFixtureInput( errors.push( `Missing __typename field for abstract type ${getNamedType(typeInfo.getType())?.name}` ); + return BREAK; } } }, diff --git a/test/fixtures/test-schema.graphql b/test/fixtures/test-schema.graphql index b8565c2..7f1c6e3 100644 --- a/test/fixtures/test-schema.graphql +++ b/test/fixtures/test-schema.graphql @@ -57,11 +57,11 @@ type InterfaceImplementer3 implements HasId { id: ID! } -type InterfaceImplementer4 { +type NoInterfacesImplemented { value: String } -union InterfaceImplementersUnion = InterfaceImplementer1 | InterfaceImplementer2 | InterfaceImplementer3 | InterfaceImplementer4 +union InterfaceImplementersUnion = InterfaceImplementer1 | InterfaceImplementer2 | InterfaceImplementer3 | NoInterfacesImplemented union NestedOuter = NestedOuterA | NestedOuterB diff --git a/test/methods/validate-fixture-input.test.ts b/test/methods/validate-fixture-input.test.ts index 1fd720b..df6bf24 100644 --- a/test/methods/validate-fixture-input.test.ts +++ b/test/methods/validate-fixture-input.test.ts @@ -253,11 +253,9 @@ describe("validateFixtureInput", () => { const result = validateFixtureInput(queryAST, schema, fixtureInput); // - inner SelectionSet has typenameResponseKey = undefined (doesn't inherit "outerType") - // - Error 1: Missing __typename (inner has 2+ fragments without it) - // - Error 2: Missing value field (without typename, can't discriminate, expects all fields) - expect(result.errors).toHaveLength(2); + // - Detects missing __typename and BREAKs early + expect(result.errors).toHaveLength(1); expect(result.errors[0]).toBe("Missing __typename field for abstract type NestedInner"); - expect(result.errors[1]).toBe("Missing expected fixture data for value"); }); it("handles nested unions with typename at each level", () => { @@ -487,7 +485,7 @@ describe("validateFixtureInput", () => { description: "Also has all three" }, {} - // Empty object - InterfaceImplementer4 that doesn't implement any interface + // Empty object - NoInterfacesImplemented that doesn't implement any interface // Valid because InterfaceImplementersUnion {1,2,3,4} was narrowed to HasId {1,2,3} ] } @@ -583,7 +581,7 @@ describe("validateFixtureInput", () => { // Implements HasId only }, { - __typename: "InterfaceImplementer4" + __typename: "NoInterfacesImplemented" // Doesn't implement any interface } ] @@ -635,7 +633,7 @@ describe("validateFixtureInput", () => { // This is a valid response - nested fragments don't match }, {} - // InterfaceImplementer4: doesn't implement any interface + // NoInterfacesImplemented: doesn't implement any interface // Empty object is valid - handled by empty object logic ] } @@ -1512,6 +1510,42 @@ describe("validateFixtureInput", () => { expect(result.errors[0]).toBe("Missing expected fixture data for price"); }); + it("handles multiple inline fragments on same type without typename", () => { + const queryAST = parse(` + query { + data { + searchResults { + ... on Item { + id + } + ... on Item { + count + } + } + } + } + `); + + const fixtureInput = { + data: { + searchResults: [ + { + id: "gid://test/Item/1", + count: 5 + } + ] + } + }; + + const result = validateFixtureInput(queryAST, schema, fixtureInput); + + // Multiple fragments but all on the same type (Item) + // Still errors on missing __typename because fragmentSpreadCount > 1 + // However, NO cascading field errors because all fragments select on same type + expect(result.errors).toHaveLength(1); + expect(result.errors[0]).toBe("Missing __typename field for abstract type SearchResult"); + }); + it("detects missing fields when __typename is not selected in union with inline fragments", () => { const queryAST = parse(` query { @@ -1548,16 +1582,9 @@ describe("validateFixtureInput", () => { const result = validateFixtureInput(queryAST, schema, fixtureInput); // Without __typename, we can't discriminate which fields are expected for each object - // So the validator conservatively expects all fields from all inline fragments - // First error: Missing __typename field for abstract type (required for discrimination) - // First object is missing email and phone (from Metadata fragment) - // Second object is missing id and count (from Item fragment) - expect(result.errors).toHaveLength(5); + // Validator detects missing __typename for abstract type with 2+ fragments and BREAKs early + expect(result.errors).toHaveLength(1); expect(result.errors[0]).toBe("Missing __typename field for abstract type SearchResult"); - expect(result.errors[1]).toBe("Missing expected fixture data for id"); - expect(result.errors[2]).toBe("Missing expected fixture data for count"); - expect(result.errors[3]).toBe("Missing expected fixture data for email"); - expect(result.errors[4]).toBe("Missing expected fixture data for phone"); }); }); }); From dc111df300bc4d299de24da01beb3961f027a1b5 Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Thu, 30 Oct 2025 15:19:21 -0400 Subject: [PATCH 12/13] shortform typenameField check --- src/methods/validate-fixture-input.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/methods/validate-fixture-input.ts b/src/methods/validate-fixture-input.ts index 3116f28..c5a2921 100644 --- a/src/methods/validate-fixture-input.ts +++ b/src/methods/validate-fixture-input.ts @@ -194,7 +194,7 @@ export function validateFixtureInput( ); let typenameResponseKey: string | undefined; - if (typenameField && typenameField.kind === Kind.FIELD) { + if (typenameField?.kind === Kind.FIELD) { typenameResponseKey = typenameField.alias?.value || "__typename"; } else if (parent && 'kind' in parent && parent.kind === Kind.INLINE_FRAGMENT) { // Inside an inline fragment without __typename - inherit from parent SelectionSet From 84542dd049524d9411e34749f830ccd16efe388b Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Thu, 30 Oct 2025 15:45:18 -0400 Subject: [PATCH 13/13] update pnpm-lock after rebase --- pnpm-lock.yaml | 131 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 15 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d881985..d4de8fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,6 +21,12 @@ importers: '@types/node': specifier: ^22.18.6 version: 22.18.13 + '@typescript-eslint/eslint-plugin': + specifier: ^8.46.2 + version: 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8.46.2 + version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) eslint: specifier: ^9.38.0 version: 9.38.0(jiti@2.6.1) @@ -811,6 +817,9 @@ packages: peerDependencies: graphql: ^16.0.0 + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -823,6 +832,9 @@ packages: '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@22.18.13': resolution: {integrity: sha512-Bo45YKIjnmFtv6I1TuC8AaHBbqXtIo+Om5fE4QiU1Tj8QR/qt+8O3BAtOimG5IFmwaWiPmB3Mv3jtYzBA4Us2A==} @@ -1325,6 +1337,14 @@ packages: debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1379,6 +1399,10 @@ packages: resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} engines: {node: '>=4'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + electron-to-chromium@1.5.243: resolution: {integrity: sha512-ZCphxFW3Q1TVhcgS9blfut1PX8lusVi2SvXQgmEEnK4TCmE1JhH2JkjJN+DNt0pJJwfBri5AROBnz2b/C+YU9g==} @@ -2498,11 +2522,6 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - rollup@4.52.5: resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2825,6 +2844,9 @@ packages: resolution: {integrity: sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==} engines: {node: '>=0.10.0'} + unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + update-browserslist-db@1.1.4: resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true @@ -3893,6 +3915,44 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.52.5': optional: true + '@shopify/eslint-plugin@50.0.0(@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(prettier@3.6.2)(typescript@5.9.3)': + dependencies: + change-case: 4.1.2 + common-tags: 1.8.2 + doctrine: 2.1.0 + eslint: 9.38.0(jiti@2.6.1) + eslint-config-prettier: 9.1.2(eslint@9.38.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-eslint-comments: 3.2.0(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-jest: 28.14.0(@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-jest-formatting: 3.1.0(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-n: 17.23.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-prettier: 5.5.4(eslint-config-prettier@9.1.2(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1))(prettier@3.6.2) + eslint-plugin-promise: 7.2.1(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-react: 7.37.5(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-sort-class-members: 1.21.0(eslint@9.38.0(jiti@2.6.1)) + globals: 15.15.0 + jsx-ast-utils: 3.3.5 + pkg-dir: 5.0.0 + pluralize: 8.0.0 + typescript-eslint: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + transitivePeerDependencies: + - '@types/eslint' + - '@typescript-eslint/eslint-plugin' + - '@typescript-eslint/parser' + - '@typescript-eslint/utils' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - eslint-plugin-import + - jest + - prettier + - supports-color + - typescript + '@shopify/shopify_function@2.0.0(@types/node@22.18.13)(graphql-sock@1.0.1(graphql@16.11.0))': dependencies: '@graphql-codegen/cli': 5.0.5(@types/node@22.18.13)(graphql-sock@1.0.1(graphql@16.11.0))(graphql@16.11.0)(typescript@5.8.3) @@ -3923,6 +3983,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -3934,6 +3999,8 @@ snapshots: '@types/js-yaml@4.0.9': {} + '@types/json-schema@7.0.15': {} + '@types/node@22.18.13': dependencies: undici-types: 6.21.0 @@ -3944,7 +4011,7 @@ snapshots: '@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/regexpp': 4.12.1 + '@eslint-community/regexpp': 4.12.2 '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.46.2 '@typescript-eslint/type-utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) @@ -4493,6 +4560,10 @@ snapshots: debounce@1.2.1: {} + debug@3.2.7: + dependencies: + ms: 2.1.3 + debug@4.4.3: dependencies: ms: 2.1.3 @@ -4538,6 +4609,12 @@ snapshots: dset@3.1.4: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + electron-to-chromium@1.5.243: {} emoji-regex@8.0.0: {} @@ -4735,7 +4812,7 @@ snapshots: eslint-plugin-es-x@7.8.0(eslint@9.38.0(jiti@2.6.1)): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) - '@eslint-community/regexpp': 4.12.1 + '@eslint-community/regexpp': 4.12.2 eslint: 9.38.0(jiti@2.6.1) eslint-compat-utils: 0.5.1(eslint@9.38.0(jiti@2.6.1)) @@ -4865,11 +4942,15 @@ snapshots: eslint@9.38.0(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.1 + '@eslint/core': 0.16.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.38.0 + '@eslint/plugin-kit': 0.4.0 + '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 @@ -5796,10 +5877,6 @@ snapshots: rfdc@1.4.1: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - rollup@4.52.5: dependencies: '@types/estree': 1.0.8 @@ -6184,6 +6261,30 @@ snapshots: dependencies: normalize-path: 2.1.1 + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + update-browserslist-db@1.1.4(browserslist@4.27.0): dependencies: browserslist: 4.27.0