Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/graphql/src/lib/scalar-mappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,25 @@ export function isStdScalar(tk: Typekit, scalar: Scalar): boolean {
return tk.scalar.getStdBase(scalar) === scalar;
}

/**
* TypeSpec std scalar names that map directly to GraphQL built-in scalar types:
* string → String, boolean → Boolean, int32 → Int, float32/float64 → Float.
*
* @see https://spec.graphql.org/September2025/#sec-Scalars.Built-in-Scalars
*/
const GRAPHQL_BUILTIN_SCALAR_NAMES = new Set([
"string", "boolean", "int32", "float32", "float64",
]);

/**
* Check whether a scalar IS a GraphQL built-in type (String, Boolean, Int, Float).
* Uses identity-based check via typekit to avoid false positives from user-defined
* scalars that happen to share the same name in a different namespace.
*/
export function isGraphQLBuiltinScalar(tk: Typekit, scalar: Scalar): boolean {
return isStdScalar(tk, scalar) && GRAPHQL_BUILTIN_SCALAR_NAMES.has(scalar.name);
}

/**
* Get the GraphQL custom scalar mapping for a scalar via its standard library ancestor.
*
Expand Down
4 changes: 2 additions & 2 deletions packages/graphql/src/mutation-engine/mutations/scalar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
type SimpleMutations,
} from "@typespec/mutator-framework";
import { reportDiagnostic } from "../../lib.js";
import { getScalarMapping, isStdScalar } from "../../lib/scalar-mappings.js";
import { getScalarMapping, isGraphQLBuiltinScalar, isStdScalar } from "../../lib/scalar-mappings.js";
import { getSpecifiedBy, setSpecifiedByUrl } from "../../lib/specified-by.js";
import { sanitizeNameForGraphQL } from "../../lib/type-utils.js";

Expand Down Expand Up @@ -61,7 +61,7 @@ export class GraphQLScalarMutation extends SimpleScalarMutation<SimpleMutationOp
scalar.name = "ID";
scalar.baseScalar = undefined;
});
} else if (mapping && isDirectStd) {
} else if (mapping && isDirectStd && !isGraphQLBuiltinScalar(tk, this.sourceType)) {
// Std library scalar that maps to a custom GraphQL scalar (e.g. int64 → Long)
this.mutationNode.mutate((scalar) => {
scalar.name = mapping.graphqlName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,60 @@ describe("GraphQL Mutation Engine - Scalars", () => {
expect(mutation.mutatedType.name).toBe("ID");
});

it("does not rename builtin std scalars even when they inherit a mapping", async () => {
// float32 inherits a mapping via float → numeric → "Numeric", but it's a
// GraphQL builtin (maps to Float) and must never be renamed.
const { M } = await tester.compile(
t.code`model ${t.model("M")} { value: float32; }`,
);

const engine = createTestEngine(tester.program);
const float32Scalar = M.properties.get("value")!.type;
expect(float32Scalar.kind).toBe("Scalar");
const mutation = engine.mutateScalar(float32Scalar as any);

expect(mutation.mutatedType.name).toBe("float32");
});

it("does not rename float64 builtin scalar", async () => {
const { M } = await tester.compile(
t.code`model ${t.model("M")} { value: float64; }`,
);

const engine = createTestEngine(tester.program);
const float64Scalar = M.properties.get("value")!.type;
expect(float64Scalar.kind).toBe("Scalar");
const mutation = engine.mutateScalar(float64Scalar as any);

expect(mutation.mutatedType.name).toBe("float64");
});

it("does not rename int32 builtin scalar", async () => {
const { M } = await tester.compile(
t.code`model ${t.model("M")} { count: int32; }`,
);

const engine = createTestEngine(tester.program);
const int32Scalar = M.properties.get("count")!.type;
expect(int32Scalar.kind).toBe("Scalar");
const mutation = engine.mutateScalar(int32Scalar as any);

expect(mutation.mutatedType.name).toBe("int32");
});

it("still renames mapped non-builtin std scalars like int64", async () => {
const { M } = await tester.compile(
t.code`model ${t.model("M")} { big: int64; }`,
);

const engine = createTestEngine(tester.program);
const int64Scalar = M.properties.get("big")!.type;
expect(int64Scalar.kind).toBe("Scalar");
const mutation = engine.mutateScalar(int64Scalar as any);

expect(mutation.mutatedType.name).toBe("Long");
});

it("warns when user-defined scalar collides with GraphQL built-in name", async () => {
const { Float } = await tester.compile(
t.code`scalar ${t.scalar("Float")} extends string;`,
Expand Down