From 6f09ca080015105fc7b9f73d01721705a6f28473 Mon Sep 17 00:00:00 2001 From: Pedro Guzman Date: Mon, 9 Jun 2025 10:19:03 +0200 Subject: [PATCH 1/9] fix: keep enum values in types generation --- packages/core/modules-sdk/src/medusa-app.ts | 1 + .../src/dml/__tests__/create-graphql.spec.ts | 22 ++++-- .../helpers/graphql-builder/get-attribute.ts | 2 +- .../utils/src/graphql/__tests__/.gitignore | 1 + .../__tests__/gql-schema-to-types.spec.ts | 68 +++++++++++++++++++ .../utils/src/graphql/graphql-to-ts-types.ts | 43 +++++++++++- 6 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 packages/core/utils/src/graphql/__tests__/.gitignore create mode 100644 packages/core/utils/src/graphql/__tests__/gql-schema-to-types.spec.ts diff --git a/packages/core/modules-sdk/src/medusa-app.ts b/packages/core/modules-sdk/src/medusa-app.ts index 02ef90eb3b..77131903d6 100644 --- a/packages/core/modules-sdk/src/medusa-app.ts +++ b/packages/core/modules-sdk/src/medusa-app.ts @@ -234,6 +234,7 @@ function cleanAndMergeSchema(loadedSchema) { const defaultMedusaSchema = ` scalar DateTime scalar JSON + directive @enumValue(value: String) on ENUM_VALUE ` const { schema: cleanedSchema, notFound } = GraphQLUtils.cleanGraphQLSchema( defaultMedusaSchema + loadedSchema diff --git a/packages/core/utils/src/dml/__tests__/create-graphql.spec.ts b/packages/core/utils/src/dml/__tests__/create-graphql.spec.ts index b6710df767..3d95905188 100644 --- a/packages/core/utils/src/dml/__tests__/create-graphql.spec.ts +++ b/packages/core/utils/src/dml/__tests__/create-graphql.spec.ts @@ -13,6 +13,11 @@ describe("GraphQL builder", () => { isVerified: model.boolean(), }) + enum DepartmentEnum { + FinanceDept = "finance", + MarketingDept = "marketing", + } + const user = model.define("user", { id: model.id(), username: model.text(), @@ -21,8 +26,9 @@ describe("GraphQL builder", () => { phones: model.array(), group: model.belongsTo(() => group, { mappedBy: "users" }), role: model - .enum(["moderator", "admin", "guest", "new_user"]) + .enum(["moderator", "admin", "guest", "new-user"]) .default("guest"), + department: model.enum(DepartmentEnum), tags: model.manyToMany(() => tag, { pivotTable: "custom_user_tags", }), @@ -59,10 +65,15 @@ describe("GraphQL builder", () => { } enum UserRoleEnum { - MODERATOR - ADMIN - GUEST - NEW_USER + MODERATOR @enumValue(value: "moderator") + ADMIN @enumValue(value: "admin") + GUEST @enumValue(value: "guest") + NEW_USER @enumValue(value: "new-user") + } + + enum UserDepartmentEnum { + FINANCE @enumValue(value: "finance") + MARKETING @enumValue(value: "marketing") } type User { @@ -74,6 +85,7 @@ describe("GraphQL builder", () => { group_id:String! group: Group! role: UserRoleEnum! + department: UserDepartmentEnum! tags: [Tag]! raw_spend_limit: JSON! created_at: DateTime! diff --git a/packages/core/utils/src/dml/helpers/graphql-builder/get-attribute.ts b/packages/core/utils/src/dml/helpers/graphql-builder/get-attribute.ts index 5a26d9e8c8..d4e9e10c41 100644 --- a/packages/core/utils/src/dml/helpers/graphql-builder/get-attribute.ts +++ b/packages/core/utils/src/dml/helpers/graphql-builder/get-attribute.ts @@ -44,7 +44,7 @@ export function getGraphQLAttributeFromDMLPropety( const enumValues = field.dataType .options!.choices.map((value) => { const enumValue = value.replace(/[^a-z0-9_]/gi, "_").toUpperCase() - return ` ${enumValue}` + return ` ${enumValue} @enumValue(value: "${value}")` }) .join("\n") diff --git a/packages/core/utils/src/graphql/__tests__/.gitignore b/packages/core/utils/src/graphql/__tests__/.gitignore new file mode 100644 index 0000000000..64d6339018 --- /dev/null +++ b/packages/core/utils/src/graphql/__tests__/.gitignore @@ -0,0 +1 @@ +.test-output \ No newline at end of file diff --git a/packages/core/utils/src/graphql/__tests__/gql-schema-to-types.spec.ts b/packages/core/utils/src/graphql/__tests__/gql-schema-to-types.spec.ts new file mode 100644 index 0000000000..bd436aed26 --- /dev/null +++ b/packages/core/utils/src/graphql/__tests__/gql-schema-to-types.spec.ts @@ -0,0 +1,68 @@ +import { makeExecutableSchema } from "@graphql-tools/schema" +import fs from "fs" +import path from "path" +import { gqlSchemaToTypes } from "../graphql-to-ts-types" + +describe("gqlSchemaToTypes", () => { + it("should use enumValue directive for enum values", async () => { + const schema = ` + directive @enumValue(value: String) on ENUM_VALUE + + enum Test { + Test_A @enumValue(value: "test-a") + Test_B + } + ` + + const executableSchema = makeExecutableSchema({ + typeDefs: schema, + }) + + await gqlSchemaToTypes({ + schema: executableSchema, + outputDir: path.resolve(__dirname, ".test-output/enum-values"), + filename: "query-entry-points", + joinerConfigs: [], + interfaceName: "RemoteQueryEntryPoints", + }) + + const expectedTypes = ` + import "@medusajs/framework/types" + export type Maybe = T | null; + export type InputMaybe = Maybe; + export type Exact = { [K in keyof T]: T[K] }; + export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; + export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; + export type MakeEmpty = { [_ in K]?: never }; + export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; + /** All built-in and custom scalars, mapped to their actual values */ + export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } + }; + + export enum Test { + TestA = 'test-a', + TestB = 'Test_B' + } + + declare module '@medusajs/framework/types' { + interface RemoteQueryEntryPoints { + + } + }` + + const fileName = ".test-output/enum-values/query-entry-points.d.ts" + const generatedTypes = fs + .readFileSync(path.resolve(__dirname, fileName)) + .toString() + expect(normalizeFile(generatedTypes)).toEqual(normalizeFile(expectedTypes)) + }) +}) + +const normalizeFile = (file: string) => { + return file.replace(/^\s+/g, "").replace(/\s+/g, " ").trim() +} diff --git a/packages/core/utils/src/graphql/graphql-to-ts-types.ts b/packages/core/utils/src/graphql/graphql-to-ts-types.ts index 7082ae0277..3645b9bca7 100644 --- a/packages/core/utils/src/graphql/graphql-to-ts-types.ts +++ b/packages/core/utils/src/graphql/graphql-to-ts-types.ts @@ -1,7 +1,14 @@ import { codegen } from "@graphql-codegen/core" import * as typescriptPlugin from "@graphql-codegen/typescript" import { ModuleJoinerConfig } from "@medusajs/types" -import { type GraphQLSchema, parse, printSchema } from "graphql" +import { + EnumTypeDefinitionNode, + EnumValueDefinitionNode, + type GraphQLSchema, + Kind, + parse, + printSchema, +} from "graphql" import { FileSystem } from "../common" function buildEntryPointsTypeMap({ @@ -87,6 +94,35 @@ ${entryPoints } } +const getEnumValues = (schema: GraphQLSchema) => { + const enumTypes = Object.values(schema.getTypeMap()).filter( + (type) => type.astNode?.kind === Kind.ENUM_TYPE_DEFINITION + ) + + const enumValues = {} + enumTypes.forEach((type) => { + const enumName = type.name + enumValues[enumName] = {} + + const nodes = (type.astNode as EnumTypeDefinitionNode).values || [] + nodes.forEach((node: EnumValueDefinitionNode) => { + const directive = node.directives?.find( + (d) => d.name.value === "enumValue" + ) + if (directive) { + const valueArg = directive.arguments?.find( + (a) => a.name.value === "value" + ) + if (valueArg && valueArg.value.kind === Kind.STRING) { + enumValues[enumName][node.name.value] = valueArg.value.value + } + } + }) + }) + + return enumValues +} + // TODO: rename from gqlSchemaToTypes to grapthqlToTsTypes export async function gqlSchemaToTypes({ schema, @@ -118,9 +154,10 @@ export async function gqlSchemaToTypes({ filename: "", schema: parse(printSchema(schema as any)), plugins: [ - // Each plugin should be an object { - typescript: {}, // Here you can pass configuration to the plugin + typescript: { + enumValues: getEnumValues(schema), + }, }, ], pluginMap: { From c202c819e5f3e0802ebb34003b1a330137bc3663 Mon Sep 17 00:00:00 2001 From: Pedro Guzman Date: Mon, 9 Jun 2025 10:53:08 +0200 Subject: [PATCH 2/9] add enumValue directive to GraphQL schema --- packages/modules/index/src/utils/base-graphql-schema.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/modules/index/src/utils/base-graphql-schema.ts b/packages/modules/index/src/utils/base-graphql-schema.ts index 2a29024b99..c07e804ec1 100644 --- a/packages/modules/index/src/utils/base-graphql-schema.ts +++ b/packages/modules/index/src/utils/base-graphql-schema.ts @@ -3,4 +3,5 @@ export const baseGraphqlSchema = ` scalar Date scalar Time scalar JSON + directive @enumValue(value: String) on ENUM_VALUE ` From 0409ef48869e882b7c6e50ff4b2b6f4ac8e6fab9 Mon Sep 17 00:00:00 2001 From: Pedro Guzman Date: Mon, 9 Jun 2025 14:06:31 +0200 Subject: [PATCH 3/9] add enumSchema directive to module graphQL schema --- .../core/utils/src/dml/helpers/create-graphql.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/core/utils/src/dml/helpers/create-graphql.ts b/packages/core/utils/src/dml/helpers/create-graphql.ts index 226ffb4b8f..bbe89174e7 100644 --- a/packages/core/utils/src/dml/helpers/create-graphql.ts +++ b/packages/core/utils/src/dml/helpers/create-graphql.ts @@ -1,9 +1,9 @@ import type { PropertyType } from "@medusajs/types" import { DmlEntity } from "../entity" -import { parseEntityName } from "./entity-builder/parse-entity-name" -import { setGraphQLRelationship } from "./graphql-builder/set-relationship" -import { getGraphQLAttributeFromDMLPropety } from "./graphql-builder/get-attribute" import { getForeignKey } from "./entity-builder" +import { parseEntityName } from "./entity-builder/parse-entity-name" +import { getGraphQLAttributeFromDMLPropety } from "./graphql-builder/get-attribute" +import { setGraphQLRelationship } from "./graphql-builder/set-relationship" export function generateGraphQLFromEntity>( entity: T @@ -82,5 +82,9 @@ export const toGraphQLSchema = (entities: T): string => { return entity }) - return gqlSchemas.join("\n") + const defaultMedusaSchema = ` + directive @enumValue(value: String) on ENUM_VALUE + ` + + return defaultMedusaSchema + gqlSchemas.join("\n") } From 0fd0a0892b368064f4b579865f73af4472b8068b Mon Sep 17 00:00:00 2001 From: Pedro Guzman Date: Mon, 9 Jun 2025 14:20:21 +0200 Subject: [PATCH 4/9] add full defaultMedusaSchema --- packages/core/utils/src/dml/helpers/create-graphql.ts | 2 ++ packages/modules/index/src/utils/build-config.ts | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/utils/src/dml/helpers/create-graphql.ts b/packages/core/utils/src/dml/helpers/create-graphql.ts index bbe89174e7..0894c413a7 100644 --- a/packages/core/utils/src/dml/helpers/create-graphql.ts +++ b/packages/core/utils/src/dml/helpers/create-graphql.ts @@ -83,6 +83,8 @@ export const toGraphQLSchema = (entities: T): string => { }) const defaultMedusaSchema = ` + scalar DateTime + scalar JSON directive @enumValue(value: String) on ENUM_VALUE ` diff --git a/packages/modules/index/src/utils/build-config.ts b/packages/modules/index/src/utils/build-config.ts index b8f99c9dde..dd924210bd 100644 --- a/packages/modules/index/src/utils/build-config.ts +++ b/packages/modules/index/src/utils/build-config.ts @@ -1253,7 +1253,10 @@ export function buildSchemaObjectRepresentation(schema: string): { } as IndexTypes.SchemaObjectRepresentation Object.entries(entitiesMap).forEach(([entityName, entityMapValue]) => { - if (!entityMapValue.astNode) { + if ( + !entityMapValue.astNode || + entityMapValue.astNode.kind === GraphQLUtils.Kind.SCALAR_TYPE_DEFINITION + ) { return } From 9e3b3667f880b43a5e1e267e9836a7de177173f8 Mon Sep 17 00:00:00 2001 From: Pedro Guzman Date: Mon, 9 Jun 2025 15:04:18 +0200 Subject: [PATCH 5/9] fix joiner-config-builder test --- packages/core/utils/src/dml/helpers/create-graphql.ts | 5 ++++- .../src/modules-sdk/__tests__/joiner-config-builder.spec.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/core/utils/src/dml/helpers/create-graphql.ts b/packages/core/utils/src/dml/helpers/create-graphql.ts index 0894c413a7..264bf33518 100644 --- a/packages/core/utils/src/dml/helpers/create-graphql.ts +++ b/packages/core/utils/src/dml/helpers/create-graphql.ts @@ -82,11 +82,14 @@ export const toGraphQLSchema = (entities: T): string => { return entity }) - const defaultMedusaSchema = ` + const defaultMedusaSchema = + gqlSchemas.length > 0 + ? ` scalar DateTime scalar JSON directive @enumValue(value: String) on ENUM_VALUE ` + : "" return defaultMedusaSchema + gqlSchemas.join("\n") } diff --git a/packages/core/utils/src/modules-sdk/__tests__/joiner-config-builder.spec.ts b/packages/core/utils/src/modules-sdk/__tests__/joiner-config-builder.spec.ts index 8c78424a2a..ed0f50c1f3 100644 --- a/packages/core/utils/src/modules-sdk/__tests__/joiner-config-builder.spec.ts +++ b/packages/core/utils/src/modules-sdk/__tests__/joiner-config-builder.spec.ts @@ -405,7 +405,10 @@ describe("joiner-config-builder", () => { ], }) - const schemaExpected = `type FulfillmentSet { + const schemaExpected = `scalar DateTime + scalar JSON + directive @enumValue(value: String) on ENUM_VALUE + type FulfillmentSet { id: ID! created_at: DateTime! updated_at: DateTime! From 66f27d5770a9635168a440beb3f3caaf495d8706 Mon Sep 17 00:00:00 2001 From: Pedro Guzman Date: Mon, 9 Jun 2025 15:19:13 +0200 Subject: [PATCH 6/9] leave base medusa schema only in modules --- packages/core/modules-sdk/src/medusa-app.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/core/modules-sdk/src/medusa-app.ts b/packages/core/modules-sdk/src/medusa-app.ts index 77131903d6..e626f851d3 100644 --- a/packages/core/modules-sdk/src/medusa-app.ts +++ b/packages/core/modules-sdk/src/medusa-app.ts @@ -231,14 +231,8 @@ function isMedusaModule(mod) { } function cleanAndMergeSchema(loadedSchema) { - const defaultMedusaSchema = ` - scalar DateTime - scalar JSON - directive @enumValue(value: String) on ENUM_VALUE - ` - const { schema: cleanedSchema, notFound } = GraphQLUtils.cleanGraphQLSchema( - defaultMedusaSchema + loadedSchema - ) + const { schema: cleanedSchema, notFound } = + GraphQLUtils.cleanGraphQLSchema(loadedSchema) const mergedSchema = GraphQLUtils.mergeTypeDefs(cleanedSchema) return { schema: GraphQLUtils.makeExecutableSchema({ typeDefs: mergedSchema }), From 827c49e87e1129b7ed521be8c6e431728a7aadec Mon Sep 17 00:00:00 2001 From: Pedro Guzman Date: Mon, 9 Jun 2025 15:21:12 +0200 Subject: [PATCH 7/9] fix graphql test --- packages/core/utils/src/dml/__tests__/create-graphql.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/core/utils/src/dml/__tests__/create-graphql.spec.ts b/packages/core/utils/src/dml/__tests__/create-graphql.spec.ts index 3d95905188..c0cf7b3262 100644 --- a/packages/core/utils/src/dml/__tests__/create-graphql.spec.ts +++ b/packages/core/utils/src/dml/__tests__/create-graphql.spec.ts @@ -44,6 +44,9 @@ describe("GraphQL builder", () => { const toGql = toGraphQLSchema([tag, email, user, group]) const expected = ` + scalar DateTime + scalar JSON + directive @enumValue(value: String) on ENUM_VALUE type Tag { id: ID! value: String! From 4e9d2352f80711a8da90a67b296b02471c9f2adf Mon Sep 17 00:00:00 2001 From: Pedro Guzman Date: Mon, 9 Jun 2025 15:24:44 +0200 Subject: [PATCH 8/9] restore base schema in cleanAndMergeSchema in case modules define their own schemas --- packages/core/modules-sdk/src/medusa-app.ts | 10 ++++++++-- .../modules/index/src/utils/base-graphql-schema.ts | 1 - 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/core/modules-sdk/src/medusa-app.ts b/packages/core/modules-sdk/src/medusa-app.ts index e626f851d3..77131903d6 100644 --- a/packages/core/modules-sdk/src/medusa-app.ts +++ b/packages/core/modules-sdk/src/medusa-app.ts @@ -231,8 +231,14 @@ function isMedusaModule(mod) { } function cleanAndMergeSchema(loadedSchema) { - const { schema: cleanedSchema, notFound } = - GraphQLUtils.cleanGraphQLSchema(loadedSchema) + const defaultMedusaSchema = ` + scalar DateTime + scalar JSON + directive @enumValue(value: String) on ENUM_VALUE + ` + const { schema: cleanedSchema, notFound } = GraphQLUtils.cleanGraphQLSchema( + defaultMedusaSchema + loadedSchema + ) const mergedSchema = GraphQLUtils.mergeTypeDefs(cleanedSchema) return { schema: GraphQLUtils.makeExecutableSchema({ typeDefs: mergedSchema }), diff --git a/packages/modules/index/src/utils/base-graphql-schema.ts b/packages/modules/index/src/utils/base-graphql-schema.ts index c07e804ec1..2a29024b99 100644 --- a/packages/modules/index/src/utils/base-graphql-schema.ts +++ b/packages/modules/index/src/utils/base-graphql-schema.ts @@ -3,5 +3,4 @@ export const baseGraphqlSchema = ` scalar Date scalar Time scalar JSON - directive @enumValue(value: String) on ENUM_VALUE ` From 8e3490d7482de099ba8eb3a93f0177e8c6ce911c Mon Sep 17 00:00:00 2001 From: Pedro Guzman Date: Thu, 12 Jun 2025 17:03:25 +0200 Subject: [PATCH 9/9] generate union types instead of enums --- .../utils/src/graphql/__tests__/gql-schema-to-types.spec.ts | 5 +---- packages/core/utils/src/graphql/graphql-to-ts-types.ts | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/utils/src/graphql/__tests__/gql-schema-to-types.spec.ts b/packages/core/utils/src/graphql/__tests__/gql-schema-to-types.spec.ts index bd436aed26..cc4b7c849a 100644 --- a/packages/core/utils/src/graphql/__tests__/gql-schema-to-types.spec.ts +++ b/packages/core/utils/src/graphql/__tests__/gql-schema-to-types.spec.ts @@ -44,10 +44,7 @@ describe("gqlSchemaToTypes", () => { Float: { input: number; output: number; } }; - export enum Test { - TestA = 'test-a', - TestB = 'Test_B' - } + export type Test = | 'test-a' | 'Test_B'; declare module '@medusajs/framework/types' { interface RemoteQueryEntryPoints { diff --git a/packages/core/utils/src/graphql/graphql-to-ts-types.ts b/packages/core/utils/src/graphql/graphql-to-ts-types.ts index 3645b9bca7..552b47f86e 100644 --- a/packages/core/utils/src/graphql/graphql-to-ts-types.ts +++ b/packages/core/utils/src/graphql/graphql-to-ts-types.ts @@ -157,6 +157,7 @@ export async function gqlSchemaToTypes({ { typescript: { enumValues: getEnumValues(schema), + enumsAsTypes: true, }, }, ],