import type { RawTypesConfig } from "@graphql-codegen/visitor-plugin-common"; /** * Mock data generation options (user input - all fields optional). * * Mock generation uses two complementary depth controls: * - `maxDepth`: Limits total nesting depth across ALL types (A → B → C → D stops at depth limit) * - `maxTypeRecursion`: Limits how many times the SAME type can appear in a chain (User → User → User) * * Both conditions must pass for generation to continue: * `depth < maxDepth && typeVisitCount[type] < maxTypeRecursion` */ export type RawMockConfig = { /** * Maximum total nesting depth across all types. * Prevents deep chains like: Query → Book → Author → Publisher → Address → Country → ... * @default 9 * @example * // With maxDepth: 3, generation stops at depth 3 regardless of type * // Query(0) → Book(1) → Author(2) → Publisher(3) → stops */ maxDepth?: number | undefined; /** * Maximum times a specific type can be visited in a single path. * Prevents same-type recursion like: User → User → User → ... * @default 2 * @example * // With maxTypeRecursion: 2 * // User(visit:1) → friends: User(visit:2) → friends: undefined (stops) */ maxTypeRecursion?: number | undefined; /** * Number of elements to generate for array/list fields. * @default 3 */ listLength?: number | undefined; /** * Default values for GraphQL scalar types. */ defaultValues?: | { String?: string | undefined; Int?: number | undefined; Float?: number | undefined; Boolean?: boolean | undefined; ID?: string | undefined; CustomScalar?: { /** * CustomScalar default expression * The value must be an JavaScript expression. * If you want to put a string value, you must put it in quotes * @example * ```js * CustomScalar: { * Digit: "1", * DateYYYYMMDD: "'2022-02-03'", * ISODateTime: "new Date().toISOString()" * } * ``` */ [key: string]: string; }; } | undefined; }; /** * Code generation config (user input - all fields optional). * Used by graphql-codegen plugin to generate mock factory functions. */ export type RawConfig = { /** * Path to type definitions file generated by graphql-codegen client preset. * Required for TypeScript output. * @example "typeFile: "./graphql" */ typesFile?: string | undefined; /** * Whether to skip __typename field in generated types. * @see https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#skiptypename */ skipTypename?: RawTypesConfig["skipTypename"] | undefined; /** * Naming convention for generated types. * @see https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#namingconvention */ namingConvention?: RawTypesConfig["namingConvention"] | undefined; /** * Prefix for generated type names. */ typesPrefix?: RawTypesConfig["typesPrefix"] | undefined; /** * Suffix for generated type names. */ typesSuffix?: RawTypesConfig["typesSuffix"] | undefined; /** * Mock data generation options. * Controls depth limits, list lengths, and default scalar values. */ mock?: RawMockConfig | undefined; }; /** * Default values for mock generation options. */ export const MockDefaults = { /** @see RawMockConfig.maxDepth */ maxDepth: 9, /** @see RawMockConfig.maxTypeRecursion */ maxTypeRecursion: 2, /** @see RawMockConfig.listLength */ listLength: 3, } as const; /** * Default values for GraphQL scalar types. */ export const DefaultValues = { String: "string", Int: 12, Float: 12.3, Boolean: true, ID: "xxxx-xxxx-xxxx-xxxx", } as const; /** * Mock data generation options (normalized - all fields required). * This is the internal config type with defaults applied. * * @see RawMockConfig for user-facing config with optional fields */ export type MockConfig = { /** * Maximum total nesting depth across all types. * @see RawMockConfig.maxDepth */ maxDepth: number; /** * Maximum times a specific type can be visited in a single path. * @see RawMockConfig.maxTypeRecursion */ maxTypeRecursion: number; /** * Number of elements to generate for array/list fields. */ listLength: number; /** * Default values for GraphQL scalar types. */ defaultValues: { /** Default value for String scalar */ String: string; /** Default value for Int scalar */ Int: number; /** Default value for Float scalar */ Float: number; /** Default value for Boolean scalar */ Boolean: boolean; /** Default value for ID scalar */ ID: string; /** * Default values for custom scalars. * Key is the scalar name, value is a JavaScript expression. */ CustomScalar?: { [key: string]: string; }; }; }; /** * Code generation config (normalized - all fields required). * This is the internal config type with defaults applied. * * @see RawConfig for user-facing config with optional fields */ export type Config = { /** Path to type definitions file */ typesFile: string; /** Whether to skip __typename field */ skipTypename: Exclude; /** Prefix for generated type names */ typesPrefix: Exclude; /** Suffix for generated type names */ typesSuffix: Exclude; /** Naming convention for generated types */ namingConvention: Exclude; /** Mock data generation options (normalized) */ mock: MockConfig; }; export function validateConfig( rawConfig: unknown, outputType: "typescript" | "javascript" = "javascript", ): asserts rawConfig is RawConfig { if (rawConfig === null || rawConfig === undefined) { throw new Error("config must be an object"); } if (typeof rawConfig !== "object") { throw new Error("config must be an object"); } if (outputType === "typescript") { if (!("typesFile" in rawConfig)) { throw new Error("config.typesFile is required"); } } if ("mock" in rawConfig && rawConfig.mock !== undefined) { if (typeof rawConfig.mock !== "object") { throw new Error("config.mock must be an object"); } const mock = rawConfig.mock as RawMockConfig; if (mock.maxDepth !== undefined && typeof mock.maxDepth !== "number") { throw new Error("config.mock.maxDepth must be a number"); } if (mock.maxDepth !== undefined && mock.maxDepth < 1) { throw new Error("config.mock.maxDepth must be at least 1"); } if (mock.maxTypeRecursion !== undefined && typeof mock.maxTypeRecursion !== "number") { throw new Error("config.mock.maxTypeRecursion must be a number"); } if (mock.maxTypeRecursion !== undefined && mock.maxTypeRecursion < 1) { throw new Error("config.mock.maxTypeRecursion must be at least 1"); } if (mock.listLength !== undefined && typeof mock.listLength !== "number") { throw new Error("config.mock.listLength must be a number"); } if (mock.defaultValues !== undefined) { if (typeof mock.defaultValues !== "object") { throw new Error("config.mock.defaultValues must be an object"); } const defaultValues = mock.defaultValues; if (defaultValues.String !== undefined && typeof defaultValues.String !== "string") { throw new Error("config.mock.defaultValues.String must be a string"); } if (defaultValues.Int !== undefined && typeof defaultValues.Int !== "number") { throw new Error("config.mock.defaultValues.Int must be a number"); } if (defaultValues.Float !== undefined && typeof defaultValues.Float !== "number") { throw new Error("config.mock.defaultValues.Float must be a number"); } if (defaultValues.Boolean !== undefined && typeof defaultValues.Boolean !== "boolean") { throw new Error("config.mock.defaultValues.Boolean must be a boolean"); } if (defaultValues.ID !== undefined && typeof defaultValues.ID !== "string") { throw new Error("config.mock.defaultValues.ID must be a string"); } if ( defaultValues.CustomScalar !== undefined && typeof defaultValues.CustomScalar !== "object" ) { throw new Error("config.mock.defaultValues.CustomScalar must be an object"); } } } } export function normalizeConfig(rawConfig: RawConfig): Config { return { typesFile: rawConfig.typesFile ?? "", skipTypename: rawConfig.skipTypename ?? false, typesPrefix: rawConfig.typesPrefix ?? "", typesSuffix: rawConfig.typesSuffix ?? "", namingConvention: rawConfig.namingConvention ?? "", mock: { maxDepth: rawConfig.mock?.maxDepth ?? MockDefaults.maxDepth, maxTypeRecursion: rawConfig.mock?.maxTypeRecursion ?? MockDefaults.maxTypeRecursion, listLength: rawConfig.mock?.listLength ?? MockDefaults.listLength, defaultValues: { String: rawConfig.mock?.defaultValues?.String ?? DefaultValues.String, Int: rawConfig.mock?.defaultValues?.Int ?? DefaultValues.Int, Float: rawConfig.mock?.defaultValues?.Float ?? DefaultValues.Float, Boolean: rawConfig.mock?.defaultValues?.Boolean ?? DefaultValues.Boolean, ID: rawConfig.mock?.defaultValues?.ID ?? DefaultValues.ID, CustomScalar: rawConfig.mock?.defaultValues?.CustomScalar ?? {}, }, }, }; }