UNPKG

prisma-nestjs-graphql

Version:

Generate object types, inputs, args, etc. from prisma schema file for usage with @nestjs/graphql module

581 lines (578 loc) 19.3 kB
import { DMMF, GeneratorOptions } from '@prisma/generator-helper'; import AwaitEventEmitter from 'await-event-emitter'; import { Project, SourceFile } from 'ts-morph'; import { ValueOf, WritableDeep } from 'type-fest'; export type ConfigurationCreateArgs = { config: { [k: string]: GeneratorConfigLine; }; output: string; sourceFilePath: string; }; declare class Configuration { #private; readonly warnings: Set<string>; schemaConfig: Record<string, unknown>; externalConfig?: ExternalConfig; externalConfigFile?: string; constructor(args: Pick<Configuration, "schemaConfig" | "externalConfig" | "externalConfigFile"> & { sourceFilePath: string; output: string; warnings?: string[]; }); static create(args: ConfigurationCreateArgs): Promise<Configuration>; private initializeOutputFilePattern; get output(): string; get outputFilePattern(): string; get importExtension(): string; get emitCompiled(): boolean; get tsConfigFilePath(): string | undefined; get combineScalarFilters(): boolean; get noTypeId(): boolean; get noAtomicOperations(): boolean; get reExport(): ReExportType; get purgeOutput(): boolean; get emitSingle(): boolean; get requireSingleFieldsInWhereUniqueInput(): boolean; get omitModelsCount(): boolean; get typeListNullable(): boolean; get unsafeCompatibleWhereUniqueInput(): boolean; get prismaClientImport(): string; get emitBlocksModels(): boolean; get emitBlocksPrismaEnums(): boolean; get emitBlocksSchemaEnums(): boolean; get emitBlocksOutputs(): boolean; get emitBlocksInputs(): boolean; get emitBlocksArgs(): boolean; getGraphqlScalar(type: string): ImportNameSpec | undefined; getField(namespace?: string): ConfigFieldRecord | undefined; /** * Get graphql for input type */ getInputType(args: GetInputTypeFunctionArgs): InputTypeRef | undefined; get customImports(): CustomImportItem[]; shouldHideField(args: { objectName: string; propertyName: string; propertyType: string; location: FieldLocation; typeName: string; /** * @deprecated Should not use */ settings?: ObjectSettings; /** * @deprecated Legacy1 */ output?: boolean; /** * @deprecated Legacy */ input?: boolean; }): boolean; getDecorators(): Generator<DecoratorItem>; /** * Get field override arguments for a specific field. * Returns merged fieldArguments from all matching overrides. */ getFieldOverride(args: { objectName: string; propertyName: string; propertyType: string; location: FieldLocation; typeName: string; }): Record<string, unknown> | undefined; /** * @deprecated Should be replaced by decorators */ get decorate(): LegacyDecorateElement[]; } declare const ReExport: { readonly All: "All"; readonly Directories: "Directories"; readonly None: "None"; readonly Single: "Single"; }; export type ReExportType = ValueOf<typeof ReExport>; export type ObjectSetting = { /** * Act as named import or namespaceImport or defaultImport */ name: string; kind: "Decorator" | "Field" | "FieldType" | "PropertyType" | "ObjectType"; arguments?: string[] | Record<string, unknown>; input: boolean; output: boolean; model: boolean; match?: (test: string) => boolean; from: string; namespace?: string; defaultImport?: string | true; namespaceImport?: string; namedImport?: boolean; }; export interface ObjectSettingsFilterArgs { name: string; input?: boolean; output?: boolean; } declare class ObjectSettings extends Array<ObjectSetting> { shouldHideField({ input, name, output, }: ObjectSettingsFilterArgs): boolean; getFieldType({ input, name, output, }: ObjectSettingsFilterArgs): ObjectSetting | undefined; getPropertyType({ input, name, output, }: ObjectSettingsFilterArgs): ObjectSetting | undefined; getObjectTypeArguments(options: Record<string, any>): string[]; fieldArguments(): Record<string, unknown> | undefined; } export type TAwaitEventEmitter = AwaitEventEmitter.default; export type FieldLocation = DMMF.FieldLocation; export type Model = WritableDeep<DMMF.Model>; export type Schema = WritableDeep<DMMF.Schema>; export type Field = DMMF.Field; export type InputTypeRef = DMMF.InputTypeRef; /** * @deprecated */ export type LegacyDecorateElement = { isMatchField: (s: string) => boolean; isMatchType: (s: string) => boolean; from: string; name: string; arguments?: string[]; namedImport: boolean; defaultImport?: string | true; namespaceImport?: string; type: string; field: string; }; export type FieldInfo = { /** * Prisma DMMF field location type * Can be: 'scalar', 'inputObjectTypes', 'outputObjectTypes', 'enumTypes', 'fieldRefTypes' */ location: FieldLocation; /** * Class name */ objectName: string; /** * Property name */ propertyName: string; /** * Property type (may contain TypeScript elements, like parameters for generics, etc.) */ propertyType: string; /** * GraphQL/Prisma type name */ typeName: string; }; /** * Configuration lines defined in schema */ export type GeneratorConfigLine = string | string[] | undefined | null; export type GetInputTypeFunctionArgs = { inputTypeName: string; fieldName: string; fieldInputTypes: InputTypeRef[]; }; export type EventArguments = { schema: Schema; models: Map<string, Model>; modelNames: string[]; modelFields: Map<string, Map<string, Field>>; fieldSettings: Map<string, Map<string, ObjectSettings>>; config: Configuration; project: Project; output: string; getSourceFile(args: { type: string; name: string; }): SourceFile; eventEmitter: TAwaitEventEmitter; typeNames: Set<string>; removeTypes: Set<string>; enums: Record<string, DMMF.DatamodelEnum | undefined>; getModelName(name: string): string | undefined; /** * Input types for this models should be decorated @Type(() => Self) */ classTransformerTypeModels: Set<string>; }; export type ImportNameSpec = { name: string; specifier?: string; }; export type EmitBlocksOption = "enums" | "models" | "inputs" | "args" | "outputs"; export type ConfigFieldRecord = { /** Arguments passed to the decorator. * @default [] */ arguments?: string[]; /** Import as default export. * Use `true` to import by the field namespace name. */ defaultImport?: string | true; /** Module to import from (e.g. 'class-validator') */ from?: string; /** Apply decorator on InputType classes. * @default false */ input?: boolean; /** Apply decorator only on model ObjectType classes. * @default false */ model?: boolean; /** Import entire module under this namespace. * @default equals field namespace name */ namespaceImport?: string; /** Apply decorator on ObjectType/output classes. * @default false */ output?: boolean; }; /** * @example * WhereInput: { '*': 'WhereInput' }, * PostCreateInput: { author: 'UserCreateNestedOneWithoutPostsInput' }, */ export type ConfigInputTypeMap = Record<string, Record<string, string>>; export type GetInputTypeRefFunction = (args: GetInputTypeFunctionArgs) => InputTypeRef | undefined; export type GetInputTypePatternFunction = (args: GetInputTypeFunctionArgs) => string | undefined; export type GetInputTypeFunction = GetInputTypeRefFunction | GetInputTypePatternFunction; export type CustomImportItem = { /** Import as default export. * Use `true` to import by name. */ defaultImport?: string | true; /** Module specifier to import from (e.g. 'class-validator') */ from: string; /** Name to import */ name: string; /** Import as a named export. * @default false */ namedImport?: boolean; /** Import entire module under this namespace */ namespaceImport?: string; }; /** * Return `true` to generate `@HideField()` instead of `@Field()` for a field. */ export type ShouldHideFieldFunction = (args: FieldInfo) => boolean; /** * Modern decorator rule for `ExternalConfig.decorators`. * Prefer this over legacy `decorate_*` schema keys. */ export type DecoratorItem = { /** Return `true` to apply this decorator to the current field. */ match: (args: FieldInfo) => boolean; /** Arguments passed to the decorator call. * Supports templates like `{propertyType.0}`. */ arguments?: string[]; /** Module specifier to import from (e.g. 'class-validator') */ from: string; /** Decorator name. Can include namespace, e.g. `Transform.Type`. */ name: string; /** Import as a named export. */ namedImport?: boolean; /** Import as default export. * Use `true` to import by decorator name. */ defaultImport?: string | true; /** Import entire module under this namespace. */ namespaceImport?: string; }; /** * Arguments passed to the `@Field()` decorator. * Follows NestJS GraphQL's FieldOptions structure to avoid conflicts. * These are merged into the generated `@Field()` options object. * * @see https://docs.nestjs.com/graphql/resolvers-map#field-decorator */ export type FieldDecoratorArguments = { /** * Custom name for the field in GraphQL schema (different from TypeScript property name). * * **Important:** When you rename a field (e.g., 'take' → 'first'), you must map the GraphQL * argument names back to Prisma field names in your resolver, since Prisma expects the * original field names. * * @example * // Option 1: Manual mapping in resolver * async findMany(args: FindManyArgs): Promise<Item[]> { * const { first, ...restArgs } = args as any; * return this.prisma.item.findMany({ * ...restArgs, * ...(first !== undefined && { take: first }), * }); * } * * @example * // Option 2: Use a helper function * export function mapGraphQLArgsToPrisma(args: any): any { * const { first, ...restArgs } = args; * return { * ...restArgs, * ...(first !== undefined && { take: first }), * }; * } * * async findMany(args: FindManyArgs): Promise<Item[]> { * const prismaArgs = mapGraphQLArgsToPrisma(args); * return this.prisma.item.findMany(prismaArgs); * } * export function mapGraphQLArgsToPrisma(args: any): any { * const { first, ...restArgs } = args; * return { * ...restArgs, * ...(first !== undefined && { take: first }), * }; * } */ name?: string; /** Description shown in GraphQL schema. */ description?: string; /** Mark field as deprecated with optional reason. */ deprecationReason?: string; /** Complexity for query complexity analysis. */ complexity?: unknown; /** * Middleware function name(s) to apply to the field. * These are emitted as identifier references, not strings. * Make sure to add corresponding customImports for the middleware. * * @example * // Single middleware * middleware: 'loggerMiddleware' * * @example * // Multiple middleware * middleware: ['loggerMiddleware', 'authMiddleware'] */ middleware?: string | string[]; /** Mark field as nullable in GraphQL schema. */ nullable?: boolean; /** Default value for the field. */ defaultValue?: unknown; }; /** * Rule for overriding `@Field()` decorator arguments on generated fields. * Use this to customize pagination fields (take, skip) or other generated Args fields. */ export type FieldDecoratorRule = { /** Return `true` to apply this override to the current field. */ match: (args: FieldInfo) => boolean; /** * Arguments to merge into the `@Field()` decorator options. * These are merged with existing arguments (nullable, etc.). */ decoratorArguments: FieldDecoratorArguments; }; export type ExternalConfig = Partial<{ /** * Output folder for generated files. * If path relative and defined in schema it will be relative to schema, * if defined in config file it will be relative to this config file. */ output: string; /** * File path and name pattern for generated files. * @type {string} * Available tokens: * - `{model}` — Model name in dashed-case, or 'prisma' if unknown * - `{name}` — Dashed-case name of model/input/arg without suffix * - `{type}` — Short type name (model, input, args, output) * - `{plural.type}` — Plural short type name (models, inputs, enums) * @default '{model}/{name}.{type}.ts' */ outputFilePattern: string; /** * Append an extension to relative import and export module specifiers. * Useful when your project uses ESM or a custom module resolution. * @example 'js', 'ts', 'mjs' */ importExtension: string; /** * Combine nested/nullable scalar filters into a single filter type. * When enabled, reduces the number of generated filter classes by merging * e.g. `StringNullableFilter` and `StringFilter` into one. * @default true */ combineScalarFilters: boolean; /** * Remove input types for atomic operations. * When enabled, types like `IntFieldUpdateOperationsInput` are not generated. * @default true */ noAtomicOperations: boolean; /** * Emit only selected blocks. Some blocks depend on others * (e.g. models requires schemaEnums, inputs requires prismaEnums). * Valid block names: 'args', 'inputs', 'outputs', 'models', 'enums' * @default All blocks enabled */ emitBlocks: EmitBlocksOption[]; /** * Omit the `_count` field from model output types. * @default false */ omitModelsCount: boolean; /** * Emit compiled JavaScript and definition files instead of TypeScript sources. * @default false */ emitCompiled: boolean; /** * Path to tsconfig.json (absolute or relative to CWD). * If not specified, auto-detects `tsconfig.json` if it exists. */ tsConfigFilePath: string; /** * Re-export strategy for generated files. * - None — No re-export index files (default) * - Directories — Index file in each root directory * - Single — Single index file in the output directory * - All — All of the above * @default None */ reExport: ReExportType; /** * Delete all files in the output folder before each generation. * @default false */ purgeOutput: boolean; /** * Mark single-field WhereUniqueInput fields as required (TypeScript) * and non-nullable (GraphQL). * NOTE: This will break compatibility between Prisma types and generated classes. * @default false */ requireSingleFieldsInWhereUniqueInput: boolean; /** * Generate a single merged file with all classes and enums * instead of one file per model/input/arg. * @default false */ emitSingle: boolean; /** * Make all fields in `*WhereUniqueInput` classes non-optional TypeScript properties. * @default false */ unsafeCompatibleWhereUniqueInput: boolean; /** * Add `nullable: true` to relation list properties on output types. * Changes `[Type!]!` → `[Type!]` in the GraphQL schema. * @default false */ typeListNullable: boolean; /** * Import path used for Prisma Client imports in generated files. * @default '@prisma/client' */ prismaClientImport: string; /** * Disable GraphQL ID type usage, using Int/Float for @id fields instead. * @default false */ noTypeId: boolean; /** * Custom GraphQL scalar type mappings for Prisma scalar types. * Keyed by Prisma scalar type name (e.g. 'BigInt', 'DateTime'). * @example * // Override BigInt with graphql-scalars' GraphQLBigInt: * { BigInt: { name: 'GraphQLBigInt', specifier: 'graphql-scalars' } } */ graphqlScalars: Record<string, ImportNameSpec | undefined>; /** * Per-field custom decorator configuration, keyed by namespace. * Enables automatic decorator application from external modules * on fields annotated with `@{namespace}.XXX` in the Prisma schema. * Each namespace entry defines: * - `from` — Module specifier to import (e.g. 'class-validator') * - `input` — Apply to InputType classes * - `output` — Apply to ObjectType/output classes * - `model` — Apply only to model ObjectType classes * - `defaultImport` — Import as default export (true = use namespace as name) * - `namespaceImport` — Import entire module under this namespace * - `namedImport` — Import as named export * * @example * // Schema: /// @Validator.MinLength(3) * // Config: { from: 'class-validator', input: true } * // Result: import * as Validator from 'class-validator'; @Validator.MinLength(3) */ fields: Record<string, ConfigFieldRecord | undefined>; /** * Input type mapping. * Select which input type should be exposed when multiple candidates exist. * Since GraphQL does not support input unions, this setting can resolve * ambiguous fields (e.g. `UserRelationFilter` vs `UserWhereInput`). * * Supports two variants: * - object map: `{ [inputTypeName]: { [fieldName|'*']: pattern } }` * - function: return either an `InputTypeRef` or a string pattern * (same matching behavior as map patterns, including `match:` syntax) * @example * // Force all WhereInput relation properties to use the plain WhereInput type: * { WhereInput: { '*': 'WhereInput' } } * // Or for a specific property in a specific type: * { PostCreateInput: { author: 'UserCreateNestedOneWithoutPostsInput' } } * // Function variant returning a pattern: * ({ inputTypeName, fieldName }) => * inputTypeName.includes('CreateOne') && fieldName === 'data' * ? 'UncheckedCreate' * : undefined */ inputType: GetInputTypeFunction | ConfigInputTypeMap; /** * Custom import statements injected into generated files. * Each element specifies: * - `from` — Module specifier to import from * - `name` — Name to import * - `namedImport` — Import as named export * - `defaultImport` — Import as default export * - `namespaceImport` — Import entire module under this namespace */ customImports: CustomImportItem[]; /** * Hook for deciding whether a generated field should be hidden in GraphQL schema. * Called for each generated input/output/model field. * Returning `true` adds `@HideField()` and skips `@Field()`. * When set, this hook overrides hide settings from field comments and legacy `decorate`. */ shouldHideField: ShouldHideFieldFunction; /** * Modern way to attach decorators to generated fields. * Each rule is evaluated against generated field metadata (`FieldInfo`) and * applied when `match` returns `true`. * Prefer this over legacy `decorate`/`decorate_*` schema configuration. */ decorators: DecoratorItem[]; /** * Override `@Field()` decorator arguments for specific fields. * Use this to customize pagination fields (take, skip, cursor) or other * generated Args fields that don't come from your Prisma schema. * * Each rule is evaluated against generated field metadata (`FieldInfo`) and * applied when `match` returns `true`. * * @example * fieldDecoratorArguments: [ * { * match: ({ objectName, propertyName }) => * objectName.endsWith('Args') && propertyName === 'take', * decoratorArguments: { * name: 'first', * defaultValue: 10, * description: 'Number of records to return', * }, * }, * ] */ fieldDecoratorArguments: FieldDecoratorRule[]; }>; export declare function generate(args: GeneratorOptions & { skipAddOutputSourceFiles?: boolean; connectCallback?: (emitter: TAwaitEventEmitter, eventArguments: EventArguments) => void | Promise<void>; }): Promise<void>; export declare const generatorHandlerConfig: { onGenerate(options: GeneratorOptions): Promise<void>; onManifest(): { defaultOutput: string; prettyName: string; }; }; export {};