diff --git a/src/masking/__benches__/types.bench.ts b/src/masking/__benches__/types.bench.ts index 819ba17194..48428c7c83 100644 --- a/src/masking/__benches__/types.bench.ts +++ b/src/masking/__benches__/types.bench.ts @@ -2,6 +2,7 @@ import type { MaybeMasked, Unmasked } from "../index.js"; import { attest, bench } from "@ark/attest"; import { expectTypeOf } from "expect-type"; import type { DeepPartial } from "../../utilities/index.js"; +import type { ContainsFragmentsRefs } from "../internal/types.js"; import { setup } from "@ark/attest"; @@ -499,3 +500,81 @@ test("leaves tuples alone", (prefix) => { }>(); }); }); + +test("does not detect `$fragmentRefs` if type is a record type", (prefix) => { + interface MetadataItem { + foo: string; + } + + interface Source { + metadata: Record; + " $fragmentName": "Source"; + } + + bench(prefix + "instantiations", () => { + return {} as MaybeMasked; + }).types([6, "instantiations"]); + + bench(prefix + "functionality", () => { + const x = {} as MaybeMasked; + + expectTypeOf(x).branded.toEqualTypeOf(); + }); +}); + +test("does not detect `$fragmentRefs` on types with index signatures", (prefix) => { + interface Source { + foo: string; + " $fragmentName": "Source"; + [key: string]: string; + } + + bench(prefix + "instantiations", () => { + return {} as MaybeMasked; + }).types([6, "instantiations"]); + + bench(prefix + "functionality", () => { + const x = {} as MaybeMasked; + + expectTypeOf(x).branded.toEqualTypeOf(); + }); +}); + +test("detects `$fragmentRefs` on types with index signatures", (prefix) => { + type Source = { + __typename: "Foo"; + id: number; + metadata: Record; + structuredMetadata: StructuredMetadata; + } & { " $fragmentName"?: "UserFieldsFragment" } & { + " $fragmentRefs"?: { + FooFragment: FooFragment; + }; + }; + + interface StructuredMetadata { + bar: number; + [index: string]: number; + } + + type FooFragment = { + __typename: "Foo"; + foo: string; + } & { " $fragmentName"?: "FooFragment" }; + + bench(prefix + "instantiations", () => { + return {} as MaybeMasked; + }).types([6, "instantiations"]); + + bench(prefix + "functionality", () => { + const x = {} as MaybeMasked; + + expectTypeOf(x).branded.toEqualTypeOf<{ + __typename: "Foo"; + id: number; + metadata: Record; + foo: string; + structuredMetadata: StructuredMetadata; + }>(); + }); +}); diff --git a/src/masking/internal/types.ts b/src/masking/internal/types.ts index 2eba4a1af6..4c769de426 100644 --- a/src/masking/internal/types.ts +++ b/src/masking/internal/types.ts @@ -1,4 +1,4 @@ -import type { Prettify } from "../../utilities/index.js"; +import type { Prettify, RemoveIndexSignature } from "../../utilities/index.js"; export type IsAny = 0 extends 1 & T ? true : false; @@ -185,7 +185,7 @@ export type RemoveFragmentName = export type ContainsFragmentsRefs = true extends IsAny ? false : TData extends object ? - " $fragmentRefs" extends keyof TData ? + " $fragmentRefs" extends keyof RemoveIndexSignature ? true : ContainsFragmentsRefs : false; diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 300cafb0d5..f4e7705deb 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -139,6 +139,7 @@ export type { OnlyRequiredProperties } from "./types/OnlyRequiredProperties.js"; export type { Prettify } from "./types/Prettify.js"; export type { UnionToIntersection } from "./types/UnionToIntersection.js"; export type { NoInfer } from "./types/NoInfer.js"; +export type { RemoveIndexSignature } from "./types/RemoveIndexSignature.js"; export { AutoCleanedStrongCache,