UNPKG

prisma-nestjs-graphql

Version:

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

1,589 lines (1,559 loc) 67.5 kB
'use strict'; var require$$0 = require('await-event-emitter'); var assert = require('assert'); var lodash = require('lodash'); var tsMorph = require('ts-morph'); var JSON5 = require('json5'); var pupa = require('pupa'); var getRelativePath = require('get-relative-path'); var outmatch = require('outmatch'); var gracefulFs = require('graceful-fs'); var filenamify = require('filenamify'); var flat = require('flat'); var pluralize = require('pluralize'); function isManyAndReturnOutputType(name) { const lowerName = name.toLowerCase(); if ((lowerName.startsWith("createmany") || lowerName.startsWith("updatemany")) && (lowerName.endsWith("andreturnoutputtype") || lowerName.endsWith("andreturn"))) { return true; } return false; } function pascalCase(string) { return lodash.startCase(lodash.camelCase(string)).replaceAll(" ", ""); } function argsType(field, args) { if (["queryRaw", "executeRaw"].includes(field.name)) { return; } if (isManyAndReturnOutputType(field.name)) return; const { eventEmitter, typeNames, getModelName } = args; let className = pascalCase(`${field.name}Args`); const modelName = getModelName(className) || ""; switch (className) { case `Aggregate${modelName}Args`: { className = `${modelName}AggregateArgs`; break; } case `GroupBy${modelName}Args`: { className = `${modelName}GroupByArgs`; break; } } const inputType = { // eslint-disable-next-line unicorn/no-null constraints: { maxNumFields: null, minNumFields: null }, name: className, fields: [...field.args] }; if (!field.args.some((x) => x.name === "_count") && [`${modelName}AggregateArgs`, `${modelName}GroupByArgs`].includes(className)) { const names = ["Count", "Avg", "Sum", "Min", "Max"]; if (`${modelName}GroupByArgs` === inputType.name) { const byField = inputType.fields.find((f) => f.name === "by"); if (byField?.inputTypes) { byField.inputTypes = byField.inputTypes.filter((inputType2) => inputType2.isList); } } for (const name of names) { if (!typeNames.has(`${modelName}${name}AggregateInput`)) { continue; } inputType.fields.push({ name: `_${name.toLowerCase()}`, isRequired: false, isNullable: true, inputTypes: [ { location: "inputObjectTypes", type: `${modelName}${name}AggregateInput`, isList: false } ] }); } } eventEmitter.emitSync("InputType", { ...args, inputType, fileType: "args", classDecoratorName: "ArgsType" }); } const BeforeGenerateField = "BeforeGenerateField"; function combineScalarFilters(eventEmitter) { eventEmitter.on("BeforeInputType", beforeInputType$2); eventEmitter.on(BeforeGenerateField, beforeGenerateField); eventEmitter.on("PostBegin", postBegin); } function beforeInputType$2(args) { const { inputType, removeTypes } = args; if (isContainBogus(inputType.name) && isScalarFilter(inputType)) { removeTypes.add(inputType.name); inputType.name = replaceBogus(inputType.name); } } function beforeGenerateField(field) { for (const fieldInput of field.inputTypes) { if (fieldInput.location !== "inputObjectTypes") { continue; } const fieldInputType = String(fieldInput.type); if (isContainBogus(fieldInputType)) { fieldInput.type = replaceBogus(fieldInputType); } } } function replaceBogus(name) { return name.replaceAll(/Nullable|Nested/g, ""); } function isContainBogus(name) { return name.startsWith("Nested") || name.includes("Nullable") && name.endsWith("Filter") || name.endsWith("NullableFilter"); } function isScalarFilter(inputType) { if (!inputType.name.endsWith("Filter")) { return false; } let result = false; const equals = inputType.fields.find((f) => f.name === "equals"); if (equals) { result = equals.inputTypes.every((x) => { return ["enumTypes", "scalar"].includes(x.location); }); } return result; } function postBegin(args) { const { modelNames, schema } = args; const inputTypes = schema.inputObjectTypes.prisma; const enumTypes = schema.enumTypes.model || []; const types = [ "Bool", "Int", "String", "DateTime", "Decimal", "Float", "Json", "Bytes", "BigInt" ]; for (const enumType of enumTypes) { const { name } = enumType; types.push(`Enum${name}`); } const inputTypeByName = lodash.keyBy(inputTypes, (inputType) => inputType.name); const replaceBogusFilters = (filterName, filterNameCandidates) => { for (const filterNameCandidate of filterNameCandidates) { const candidate = inputTypeByName[filterNameCandidate]; if (candidate) { const inputType = lodash.cloneDeep({ ...candidate, name: filterName }); inputTypes.push(inputType); inputTypeByName[filterName] = inputType; break; } } }; for (const type of types) { replaceBogusFilters(`${type}Filter`, [ `${type}NullableFilter`, `Nested${type}NullableFilter` ]); replaceBogusFilters(`${type}WithAggregatesFilter`, [ `${type}NullableWithAggregatesFilter`, `Nested${type}NullableWithAggregatesFilter` ]); replaceBogusFilters(`${type}ListFilter`, [ `${type}NullableListFilter`, `Nested${type}NullableListFilter` ]); } for (const modelName of modelNames) { replaceBogusFilters(`${modelName}RelationFilter`, [ `${modelName}NullableRelationFilter` ]); } for (const modelName of modelNames) { replaceBogusFilters(`${modelName}ScalarRelationFilter`, [ `${modelName}NullableScalarRelationFilter` ]); } lodash.remove(inputTypes, (inputType) => { return isContainBogus(inputType.name); }); } function createAggregateInput(args) { const { eventEmitter, outputType } = args; const className = `${outputType.name}Input`; const inputType = { // eslint-disable-next-line unicorn/no-null constraints: { maxNumFields: null, minNumFields: null }, name: className, fields: outputType.fields.map((x) => ({ name: x.name, isNullable: x.isNullable ?? true, isRequired: false, inputTypes: [ { isList: false, type: "true", location: "scalar" } ] })) }; eventEmitter.emitSync("InputType", { ...args, inputType, fileType: "input", classDecoratorName: "InputType" }); } function emitSingle(emitter) { emitter.on("ClassProperty", classProperty); } function classProperty(property, eventArguments) { const { location, isList, propertyType } = eventArguments; if (["inputObjectTypes", "outputObjectTypes"].includes(location) && !isList) { const [safeTypes, instanceofTypes] = lodash.partition( propertyType, (t) => t === "null" || t.startsWith("Prisma.") ); const mappedInstanceofTypes = instanceofTypes.map((t) => `InstanceType<typeof ${t}>`); property.type = [...mappedInstanceofTypes, ...safeTypes].join(" | "); } } class ImportDeclarationMap extends Map { add(name, value) { if (!this.has(name)) { const structure = typeof value === "string" ? { moduleSpecifier: value, namedImports: [{ name }] } : value; this.set(name, structure); } } create(args) { const { from, defaultImport, namespaceImport, namedImport } = args; let name = args.name; const value = { moduleSpecifier: from, namedImports: [], defaultImport: void 0, namespaceImport: void 0 }; if (namedImport === true && namespaceImport) { value.namedImports = [{ name: namespaceImport }]; name = namespaceImport; } else if (defaultImport) { value.defaultImport = defaultImport === true ? name : defaultImport; name = value.defaultImport; } else if (namespaceImport) { value.namespaceImport = namespaceImport; name = namespaceImport; } else { value.namedImports = [{ name }]; } this.add(name, value); } *toStatements() { const iterator = this.values(); let result = iterator.next(); while (result.value) { yield { ...result.value, kind: tsMorph.StructureKind.ImportDeclaration }; result = iterator.next(); } } } async function generateFiles(args) { const { config, eventEmitter, output, project } = args; if (config.emitSingle) { const rootDirectory = project.getDirectory(output) || project.createDirectory(output); const sourceFile = rootDirectory.getSourceFile("index.ts") || rootDirectory.createSourceFile("index.ts", void 0, { overwrite: true }); const statements = project.getSourceFiles().flatMap((s) => { if (s === sourceFile) { return []; } const classDeclaration = s.getClass(() => true); const statements2 = s.getStructure().statements; if (Array.isArray(statements2)) { for (const statement of statements2) { if (!(typeof statement === "object" && statement.kind === tsMorph.StructureKind.Class)) { continue; } for (const property of statement.properties || []) { for (const decorator of property.decorators || []) { const fullName = classDeclaration?.getProperty(property.name)?.getDecorator(decorator.name)?.getFullName(); assert.ok( fullName, `Cannot get full name of decorator of class ${statement.name}` ); decorator.name = fullName; } } } } project.removeSourceFile(s); return statements2; }); const imports = new ImportDeclarationMap(); const enums = []; const classes = []; for (const statement of statements) { if (typeof statement === "string") { if (statement.startsWith("registerEnumType")) { enums.push(statement); } continue; } switch (statement.kind) { case tsMorph.StructureKind.ImportDeclaration: { if (statement.moduleSpecifier.startsWith("./") || statement.moduleSpecifier.startsWith("..")) { continue; } for (const namedImport of statement.namedImports) { const name = namedImport.alias || namedImport.name; imports.add(name, statement.moduleSpecifier); } if (statement.defaultImport) { imports.create({ defaultImport: statement.defaultImport, from: statement.moduleSpecifier, name: statement.defaultImport }); } if (statement.namespaceImport) { imports.create({ from: statement.moduleSpecifier, name: statement.namespaceImport, namespaceImport: statement.namespaceImport }); } break; } case tsMorph.StructureKind.Enum: { enums.unshift(statement); break; } case tsMorph.StructureKind.Class: { classes.push(statement); break; } } } for (const customImport of config.customImport) { imports.create(customImport); } sourceFile.set({ kind: tsMorph.StructureKind.SourceFile, statements: [...imports.toStatements(), ...enums, ...classes] }); } if (config.emitCompiled) { project.compilerOptions.set({ declaration: true, declarationDir: output, emitDecoratorMetadata: false, outDir: output, rootDir: output, skipLibCheck: true }); const emitResult = await project.emit(); const errors = emitResult.getDiagnostics().map((d) => String(d.getMessageText())); if (errors.length > 0) { eventEmitter.emitSync("Warning", errors); } } else { await project.save(); } } function fileTypeByLocation(fieldLocation) { switch (fieldLocation) { case "inputObjectTypes": { return "input"; } case "outputObjectTypes": { return "output"; } case "enumTypes": { return "enum"; } } return "object"; } function relativePath(from, to) { if (!from.startsWith("/")) { from = `/${from}`; } if (!to.startsWith("/")) { to = `/${to}`; } let result = getRelativePath(from, to); if (!result.startsWith(".")) { result = `./${result}`; } if (result.endsWith(".ts")) { result = result.slice(0, -3); } return result; } function getGraphqlImport(args) { const { config, fileType, getSourceFile, isId, location, noTypeId, sourceFile, typeName } = args; if (location === "scalar") { if (isId && !noTypeId) { return { name: "ID", specifier: "@nestjs/graphql" }; } const graphqlType = config.graphqlScalars[typeName]; if (graphqlType) { return { name: graphqlType.name, specifier: graphqlType.specifier }; } switch (typeName) { case "Float": case "Int": { return { name: typeName, specifier: "@nestjs/graphql" }; } case "DateTime": { return { name: "Date", specifier: void 0 }; } case "true": case "Boolean": { return { name: "Boolean", specifier: void 0 }; } case "Decimal": { return { name: "GraphQLDecimal", specifier: "prisma-graphql-type-decimal" }; } case "Json": { return { name: "GraphQLJSON", specifier: "graphql-type-json" }; } } return { name: "String", specifier: void 0 }; } let sourceFileType = fileTypeByLocation(location); if (sourceFileType === "output" && fileType === "model") { sourceFileType = "model"; } const specifier = relativePath( sourceFile.getFilePath(), getSourceFile({ name: typeName, type: sourceFileType }).getFilePath() ); return { name: typeName, specifier }; } function getGraphqlInputType(inputTypes, pattern) { let result; inputTypes = inputTypes.filter((t) => !["null", "Null"].includes(String(t.type))); inputTypes = lodash.uniqWith(inputTypes, lodash.isEqual); if (inputTypes.length === 1) { return inputTypes[0]; } const countTypes = lodash.countBy(inputTypes, (x) => x.location); const isOneType = Object.keys(countTypes).length === 1; if (isOneType) { result = inputTypes.find((x) => x.isList); if (result) { return result; } } if (pattern) { if (pattern.startsWith("matcher:") || pattern.startsWith("match:")) { const { 1: patternValue } = pattern.split(":", 2); const isMatch = outmatch(patternValue, { separator: false }); result = inputTypes.find((x) => isMatch(String(x.type))); if (result) { return result; } } result = inputTypes.find((x) => String(x.type).includes(pattern)); if (result) { return result; } } result = inputTypes.find((x) => x.location === "inputObjectTypes"); if (result) { return result; } if (countTypes.enumTypes && countTypes.scalar && inputTypes.some((x) => x.type === "Json" && x.location === "scalar")) { result = inputTypes.find((x) => x.type === "Json" && x.location === "scalar"); if (result) { return result; } } if ((countTypes.scalar >= 1 || countTypes.enumTypes >= 1) && countTypes.fieldRefTypes === 1) { result = inputTypes.find( (x) => (x.location === "scalar" || x.location === "enumTypes") && x.isList ); if (result) { return result; } result = inputTypes.find( (x) => x.location === "scalar" || x.location === "enumTypes" ); if (result) { return result; } } throw new TypeError( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Cannot get matching input type from ${inputTypes.map((x) => x.type).join(", ") || "zero length inputTypes"}` ); } function getPropertyType(args) { const { location, type } = args; switch (type) { case "Float": case "Int": { return ["number"]; } case "String": { return ["string"]; } case "Boolean": { return ["boolean"]; } case "DateTime": { return ["Date", "string"]; } case "Decimal": { return ["Decimal"]; } case "Json": { return ["any"]; } case "Null": { return ["null"]; } case "Bytes": { return ["Uint8Array"]; } case "BigInt": { return ["bigint", "number"]; } } if (["inputObjectTypes", "outputObjectTypes"].includes(location)) { return [type]; } if (location === "enumTypes") { const enumType = "`${" + type + "}`"; return [enumType]; } if (location === "scalar") { return [type]; } return ["unknown"]; } function getWhereUniqueAtLeastKeys(model) { const names = model.fields.filter((field) => field.isUnique || field.isId).map((field) => field.name); if (model.primaryKey) { names.push(createFieldName(model.primaryKey)); } for (const uniqueIndex of model.uniqueIndexes) { names.push(createFieldName(uniqueIndex)); } return names; } function createFieldName(args) { const { name, fields } = args; return name || fields.join("_"); } function isWhereUniqueInputType(name) { return name.endsWith("WhereUniqueInput"); } function propertyStructure(args) { const { isNullable, propertyType, name, isList, hasQuestionToken, hasExclamationToken } = args; const type = propertyType.map((type2) => isList ? `Array<${type2}>` : type2).join(" | "); return { kind: tsMorph.StructureKind.Property, name, type, hasQuestionToken: hasQuestionToken ?? isNullable, hasExclamationToken: hasExclamationToken ?? !isNullable, decorators: [], leadingTrivia: "\n" }; } function inputType(args) { const { classDecoratorName, classTransformerTypeModels, config, eventEmitter, fieldSettings, fileType, getModelName, getSourceFile, inputType: inputType2, models, removeTypes, typeNames } = args; typeNames.add(inputType2.name); const importDeclarations = new ImportDeclarationMap(); const sourceFile = getSourceFile({ name: inputType2.name, type: fileType }); const classStructure = { kind: tsMorph.StructureKind.Class, isExported: true, name: inputType2.name, decorators: [ { name: classDecoratorName, arguments: [] } ], properties: [] }; const modelName = getModelName(inputType2.name) || ""; const model = models.get(modelName); const modelFieldSettings = model && fieldSettings.get(model.name); const moduleSpecifier = "@nestjs/graphql"; importDeclarations.set("Field", { namedImports: [{ name: "Field" }], moduleSpecifier }).set(classDecoratorName, { namedImports: [{ name: classDecoratorName }], moduleSpecifier }); const useInputType = config.useInputType.find( (x) => inputType2.name.includes(x.typeName) ); const isWhereUnique = isWhereUniqueInputType(inputType2.name); for (const field of inputType2.fields) { field.inputTypes = field.inputTypes.filter((t) => !removeTypes.has(String(t.type))); eventEmitter.emitSync(BeforeGenerateField, field, args); const { inputTypes, isRequired, name } = field; if (inputTypes.length === 0) { continue; } const usePattern = useInputType?.ALL || useInputType?.[name]; const graphqlInputType = getGraphqlInputType(inputTypes, usePattern); const { isList, location, type } = graphqlInputType; const typeName = String(type); const settings = modelFieldSettings?.get(name); const propertySettings = settings?.getPropertyType({ name: inputType2.name, input: true }); const modelField = model?.fields.find((f) => f.name === name); const isCustomsApplicable = typeName === modelField?.type; const atLeastKeys = model && getWhereUniqueAtLeastKeys(model); const whereUniqueInputType = isWhereUniqueInputType(typeName) && atLeastKeys && `Prisma.AtLeast<${typeName}, ${atLeastKeys.map((name2) => `'${name2}'`).join(" | ")}>`; const propertyType = lodash.castArray( propertySettings?.name || whereUniqueInputType || getPropertyType({ location, type: typeName }) ); const hasExclamationToken = Boolean( isWhereUnique && config.unsafeCompatibleWhereUniqueInput && atLeastKeys?.includes(name) ); const property = propertyStructure({ name, isNullable: !isRequired, hasExclamationToken: hasExclamationToken || void 0, hasQuestionToken: hasExclamationToken ? false : void 0, propertyType, isList }); classStructure.properties.push(property); if (propertySettings) { importDeclarations.create({ ...propertySettings }); } else if (propertyType.includes("Decimal")) { importDeclarations.add("Decimal", `${config.prismaClientImport}/runtime/library`); } else if (propertyType.some((p) => p.startsWith("Prisma."))) { importDeclarations.add("Prisma", config.prismaClientImport); } let graphqlType; const shouldHideField = settings?.shouldHideField({ name: inputType2.name, input: true }) || config.decorate.some( (d) => d.name === "HideField" && d.from === moduleSpecifier && d.isMatchField(name) && d.isMatchType(inputType2.name) ); const fieldType = settings?.getFieldType({ name: inputType2.name, input: true }); if (fieldType && isCustomsApplicable && !shouldHideField) { graphqlType = fieldType.name; importDeclarations.create({ ...fieldType }); } else { const graphqlImport = getGraphqlImport({ config, sourceFile, location, typeName, getSourceFile }); graphqlType = graphqlImport.name; let referenceName = propertyType[0]; if (location === "enumTypes") { referenceName = lodash.last(referenceName.split(" ")); } if (graphqlImport.specifier && !importDeclarations.has(graphqlImport.name) && graphqlImport.name !== inputType2.name) { importDeclarations.set(graphqlImport.name, { namedImports: [{ name: graphqlImport.name }], moduleSpecifier: graphqlImport.specifier }); } } assert.ok(property.decorators, "property.decorators is undefined"); if (shouldHideField) { importDeclarations.add("HideField", moduleSpecifier); property.decorators.push({ name: "HideField", arguments: [] }); } else { property.decorators.push({ name: "Field", arguments: [ isList ? `() => [${graphqlType}]` : `() => ${graphqlType}`, JSON5.stringify({ ...settings?.fieldArguments(), nullable: !isRequired }) ] }); if (graphqlType === "GraphQLDecimal") { importDeclarations.add("transformToDecimal", "prisma-graphql-type-decimal"); importDeclarations.add("Transform", "class-transformer"); importDeclarations.add("Type", "class-transformer"); property.decorators.push( { name: "Type", arguments: ["() => Object"] }, { name: "Transform", arguments: ["transformToDecimal"] } ); } else if (location === "inputObjectTypes" && (modelField?.type === "Decimal" || [ "connect", "connectOrCreate", "create", "createMany", "data", "delete", "deleteMany", "disconnect", "set", "update", "updateMany", "upsert", "where" ].includes(name) || classTransformerTypeModels.has(getModelName(graphqlType) || "") || modelField?.kind === "object" && models.get(modelField.type) && models.get(modelField.type)?.fields.some( (field2) => field2.kind === "object" && classTransformerTypeModels.has(field2.type) ))) { importDeclarations.add("Type", "class-transformer"); property.decorators.push({ name: "Type", arguments: [`() => ${graphqlType}`] }); } if (isCustomsApplicable) { for (const options of settings || []) { if ((options.kind === "Decorator" && options.input && options.match?.(name)) ?? true) { property.decorators.push({ name: options.name, arguments: options.arguments }); assert.ok(options.from, "Missed 'from' part in configuration or field setting"); importDeclarations.create(options); } } } for (const decorate of config.decorate) { if (decorate.isMatchField(name) && decorate.isMatchType(inputType2.name)) { property.decorators.push({ name: decorate.name, arguments: decorate.arguments?.map((x) => pupa(x, { propertyType })) }); importDeclarations.create(decorate); } } } eventEmitter.emitSync("ClassProperty", property, { location, isList, propertyType }); } sourceFile.set({ statements: [...importDeclarations.toStatements(), classStructure] }); } class ObjectSettings extends Array { shouldHideField({ name, input = false, output = false }) { const hideField = this.find((s) => s.name === "HideField"); return Boolean( hideField?.input && input || hideField?.output && output || hideField?.match?.(name) ); } getFieldType({ name, input, output }) { const fieldType = this.find((s) => s.kind === "FieldType"); if (!fieldType) { return void 0; } if (fieldType.match) { return fieldType.match(name) ? fieldType : void 0; } if (input && !fieldType.input) { return void 0; } if (output && !fieldType.output) { return void 0; } return fieldType; } getPropertyType({ name, input, output }) { const propertyType = this.find((s) => s.kind === "PropertyType"); if (!propertyType) { return void 0; } if (propertyType.match) { return propertyType.match(name) ? propertyType : void 0; } if (input && !propertyType.input) { return void 0; } if (output && !propertyType.output) { return void 0; } return propertyType; } getObjectTypeArguments(options) { const objectTypeOptions = lodash.merge({}, options); const resultArguments = [objectTypeOptions]; const objectType = this.find((s) => s.kind === "ObjectType"); if (objectType && lodash.isObject(objectType.arguments)) { const name = objectType.arguments.name; lodash.merge(objectTypeOptions, lodash.omit(objectType.arguments, "name")); if (name) { resultArguments.unshift(name); } } return resultArguments.map((x) => JSON5.stringify(x)); } fieldArguments() { const item = this.find((item2) => item2.kind === "Field"); if (item) { return item.arguments; } } } function createObjectSettings(args) { const { config, text } = args; const result = new ObjectSettings(); const textLines = text.split("\n"); const documentationLines = []; let fieldElement = result.find((item) => item.kind === "Field"); if (!fieldElement) { fieldElement = { name: "", kind: "Field", arguments: {} }; } for (const line of textLines) { const match = /^@(?<name>\w+(\.(\w+))?)\((?<args>.*)\)/.exec(line); const { element, documentLine } = createSettingElement({ line, config, fieldElement, match }); if (element) { result.push(element); } if (documentLine) { documentationLines.push(line); } } return { settings: result, documentation: documentationLines.filter(Boolean).join("\n") || void 0 }; } function createSettingElement({ line, config, fieldElement, match }) { const result = { documentLine: "", element: void 0 }; if (line.startsWith("@deprecated")) { fieldElement.arguments["deprecationReason"] = lodash.trim(line.slice(11)); result.element = fieldElement; return result; } if (line.startsWith("@complexity")) { let n = Number.parseInt(lodash.trim(line.slice(11))); if (n !== n || n < 1) n = 1; fieldElement.arguments["complexity"] = n; result.element = fieldElement; return result; } const name = match?.groups?.name; if (!(match && name)) { result.documentLine = line; return result; } const element = { kind: "Decorator", name: "", arguments: [], input: false, output: false, model: false, from: "" }; result.element = element; if (name === "TypeGraphQL.omit" || name === "HideField") { Object.assign(element, hideFieldDecorator(match)); return result; } if (["FieldType", "PropertyType"].includes(name) && match.groups?.args) { const options2 = customType(match.groups.args); lodash.merge(element, options2.namespace && config.fields[options2.namespace], options2, { kind: name }); return result; } if (name === "ObjectType" && match.groups?.args) { element.kind = "ObjectType"; const options2 = customType(match.groups.args); if (typeof options2[0] === "string" && options2[0]) { options2.name = options2[0]; } if (lodash.isObject(options2[1])) { lodash.merge(options2, options2[1]); } element.arguments = { name: options2.name, isAbstract: options2.isAbstract }; return result; } if (name === "Directive" && match.groups?.args) { const options2 = customType(match.groups.args); lodash.merge(element, { model: true, from: "@nestjs/graphql" }, options2, { name, namespace: false, kind: "Decorator", arguments: Array.isArray(options2.arguments) ? options2.arguments.map((s) => JSON5.stringify(s)) : options2.arguments }); return result; } const namespace = getNamespace(name); element.namespaceImport = namespace; const options = { name, arguments: (match.groups?.args || "").split(",").map((s) => lodash.trim(s)).filter(Boolean) }; lodash.merge(element, namespace && config.fields[namespace], options); return result; } function customType(args) { const result = {}; let options = parseArgs(args); if (typeof options === "string") { options = { name: options }; } Object.assign(result, options); const namespace = getNamespace(options.name); result.namespace = namespace; if (options.name?.includes(".")) { result.namespaceImport = namespace; } if (typeof options.match === "string" || Array.isArray(options.match)) { result.match = outmatch(options.match, { separator: false }); } return result; } function hideFieldDecorator(match) { const result = { name: "HideField", arguments: [], from: "@nestjs/graphql", defaultImport: void 0, namespaceImport: void 0, match: void 0 }; if (!match.groups?.args) { result.output = true; return result; } if (match.groups.args.includes("{") && match.groups.args.includes("}")) { const options = parseArgs(match.groups.args); result.output = Boolean(options.output); result.input = Boolean(options.input); if (typeof options.match === "string" || Array.isArray(options.match)) { result.match = outmatch(options.match, { separator: false }); } } else { if (/output:\s*true/.test(match.groups.args)) { result.output = true; } if (/input:\s*true/.test(match.groups.args)) { result.input = true; } } return result; } function parseArgs(string) { try { return JSON5.parse(string); } catch { try { return JSON5.parse(`[${string}]`); } catch { throw new Error(`Failed to parse: ${string}`); } } } function getNamespace(name) { if (name === void 0) { return void 0; } let result = String(name); if (result.includes(".")) { [result] = result.split("."); } return result; } function modelData(model, args) { const { config, modelNames, models, modelFields, fieldSettings, classTransformerTypeModels } = args; modelNames.push(model.name); models.set(model.name, model); const modelFieldsValue = /* @__PURE__ */ new Map(); modelFields.set(model.name, modelFieldsValue); const fieldSettingsValue = /* @__PURE__ */ new Map(); fieldSettings.set(model.name, fieldSettingsValue); for (const field of model.fields) { if (field.documentation) { const { documentation, settings } = createObjectSettings({ text: field.documentation, config }); field.documentation = documentation; fieldSettingsValue.set(field.name, settings); } modelFieldsValue.set(field.name, field); } if (model.fields.some((field) => field.type === "Decimal")) { classTransformerTypeModels.add(model.name); } } function createComment(documentation, settings) { const documentationLines = documentation.split("\n"); const commentLines = ["/**"]; for (const line of documentationLines) { commentLines.push(` * ${line}`); } const deprecationReason = settings?.fieldArguments()?.deprecationReason; if (deprecationReason) { commentLines.push(` * @deprecated ${deprecationReason}`); } commentLines.push(" */\n"); return commentLines.join("\n"); } function getOutputTypeName(name) { return name.replace(/(?:OutputType|Output)$/, ""); } const nestjsGraphql$1 = "@nestjs/graphql"; function modelOutputType(outputType, args) { const { config, eventEmitter, fieldSettings, getSourceFile, modelFields, models } = args; if (isManyAndReturnOutputType(outputType.name)) return; const model = models.get(outputType.name); assert.ok(model, `Cannot find model by name ${outputType.name}`); const sourceFile = getSourceFile({ name: outputType.name, type: "model" }); const sourceFileStructure = sourceFile.getStructure(); const exportDeclaration = getExportDeclaration$1( model.name, sourceFileStructure.statements ); const importDeclarations = new ImportDeclarationMap(); const classStructure = { decorators: [ { arguments: [], name: "ObjectType" } ], isExported: true, kind: tsMorph.StructureKind.Class, name: outputType.name, properties: [] }; sourceFileStructure.statements.push(classStructure); assert.ok(classStructure.decorators, "classStructure.decorators is undefined"); const decorator = classStructure.decorators.find((d) => d.name === "ObjectType"); assert.ok(decorator, "ObjectType decorator not found"); let modelSettings; if (model.documentation) { const objectTypeOptions = {}; const { documentation, settings } = createObjectSettings({ config, text: model.documentation }); if (documentation) { if (!classStructure.leadingTrivia) { classStructure.leadingTrivia = createComment(documentation); } objectTypeOptions.description = documentation; } decorator.arguments = settings.getObjectTypeArguments(objectTypeOptions); modelSettings = settings; } importDeclarations.add("Field", nestjsGraphql$1); importDeclarations.add("ObjectType", nestjsGraphql$1); for (const field of outputType.fields) { if (config.omitModelsCount && field.name === "_count") continue; let fileType = "model"; const { isList, location, namespace, type } = field.outputType; let outputTypeName = String(type); if (namespace !== "model") { fileType = "output"; outputTypeName = getOutputTypeName(outputTypeName); } const modelField = modelFields.get(model.name)?.get(field.name); const settings = fieldSettings.get(model.name)?.get(field.name); const fieldType = settings?.getFieldType({ name: outputType.name, output: true }); const propertySettings = settings?.getPropertyType({ name: outputType.name, output: true }); const propertyType = lodash.castArray( propertySettings?.name || getPropertyType({ location, type: outputTypeName }) ); propertyType.splice(1, propertyType.length); if (field.isNullable && !isList) { propertyType.push("null"); } let graphqlType; if (fieldType) { graphqlType = fieldType.name; importDeclarations.create({ ...fieldType }); } else { const graphqlImport = getGraphqlImport({ config, fileType, getSourceFile, isId: modelField?.isId, location, noTypeId: config.noTypeId, sourceFile, typeName: outputTypeName }); graphqlType = graphqlImport.name; if (graphqlImport.name !== outputType.name && graphqlImport.specifier) { importDeclarations.add(graphqlImport.name, graphqlImport.specifier); } } const property = propertyStructure({ hasExclamationToken: true, hasQuestionToken: location === "outputObjectTypes", isList, isNullable: field.isNullable, name: field.name, propertyType }); if (typeof property.leadingTrivia === "string" && modelField?.documentation) { property.leadingTrivia += createComment(modelField.documentation, settings); } classStructure.properties?.push(property); if (propertySettings) { importDeclarations.create({ ...propertySettings }); } else if (propertyType.includes("Decimal")) { importDeclarations.add("Decimal", `${config.prismaClientImport}/runtime/library`); } assert.ok(property.decorators, "property.decorators is undefined"); const shouldHideField = settings?.shouldHideField({ name: outputType.name, output: true }) || config.decorate.some( (d) => d.name === "HideField" && d.from === "@nestjs/graphql" && d.isMatchField(field.name) && d.isMatchType(outputTypeName) ); if (shouldHideField) { importDeclarations.add("HideField", nestjsGraphql$1); property.decorators.push({ arguments: [], name: "HideField" }); } else { property.decorators.push({ arguments: [ isList ? `() => [${graphqlType}]` : `() => ${graphqlType}`, JSON5.stringify({ ...settings?.fieldArguments(), defaultValue: ["number", "string", "boolean"].includes( typeof modelField?.default ) ? modelField?.default : void 0, description: modelField?.documentation, nullable: Boolean(field.isNullable) }) ], name: "Field" }); for (const setting of settings || []) { if (shouldBeDecorated(setting) && (setting.match?.(field.name) ?? true)) { property.decorators.push({ arguments: setting.arguments, name: setting.name }); assert.ok(setting.from, "Missed 'from' part in configuration or field setting"); importDeclarations.create(setting); } } for (const decorate of config.decorate) { if (decorate.isMatchField(field.name) && decorate.isMatchType(outputTypeName)) { property.decorators.push({ arguments: decorate.arguments?.map((x) => pupa(x, { propertyType })), name: decorate.name }); importDeclarations.create(decorate); } } } eventEmitter.emitSync("ClassProperty", property, { isList, location, propertyType }); } for (const setting of modelSettings || []) { if (shouldBeDecorated(setting)) { classStructure.decorators.push({ arguments: setting.arguments, name: setting.name }); importDeclarations.create(setting); } } if (exportDeclaration) { sourceFile.set({ statements: [exportDeclaration, "\n", classStructure] }); const classDeclaration = sourceFile.getClassOrThrow(model.name); const commentedText = classDeclaration.getText().split("\n").map((x) => `// ${x}`); classDeclaration.remove(); sourceFile.addStatements(["\n", ...commentedText]); } else { sourceFile.set({ statements: [...importDeclarations.toStatements(), classStructure] }); } } function shouldBeDecorated(setting) { return setting.kind === "Decorator" && (setting.output || setting.model) && !(setting.output && setting.model); } function getExportDeclaration$1(name, statements) { return statements.find((structure) => { return structure.kind === tsMorph.StructureKind.ExportDeclaration && structure.namedExports.some( (o) => (o.alias || o.name) === name ); }); } function noAtomicOperations(eventEmitter) { eventEmitter.on("BeforeInputType", beforeInputType$1); eventEmitter.on("BeforeGenerateFiles", beforeGenerateFiles$1); } function beforeInputType$1(args) { const { inputType, getModelName } = args; for (const field of inputType.fields) { const fieldName = field.name; field.inputTypes = field.inputTypes.filter((inputType2) => { const inputTypeName = String(inputType2.type); const modelName = getModelName(inputTypeName); if (isAtomicOperation(inputTypeName) || modelName && isListInput(inputTypeName, modelName, fieldName)) { return false; } return true; }); } } function beforeGenerateFiles$1(args) { const { project } = args; for (const sourceFile of project.getSourceFiles()) { const className = sourceFile.getClass(() => true)?.getName(); if (className && isAtomicOperation(className)) { project.removeSourceFile(sourceFile); } } } function isAtomicOperation(typeName) { if (typeName.endsWith("FieldUpdateOperationsInput")) { return true; } return false; } function isListInput(typeName, model, field) { return typeName === `${model}Create${field}Input` || typeName === `${model}Update${field}Input`; } function getEnumName(referenceName) { return referenceName.slice(3, -2); } const nestjsGraphql = "@nestjs/graphql"; function outputType(outputType2, args) { const { config, eventEmitter, fieldSettings, getModelName, getSourceFile, models } = args; const importDeclarations = new ImportDeclarationMap(); const fileType = "output"; const modelName = getModelName(outputType2.name) || ""; const model = models.get(modelName); const isAggregateOutput = model && /(?:Count|Avg|Sum|Min|Max)AggregateOutputType$/.test(outputType2.name) && String(outputType2.name).startsWith(model.name); const isCountOutput = model?.name && outputType2.name === `${model.name}CountOutputType`; if (!config.emitBlocks.outputs && !isCountOutput) return; outputType2.name = getOutputTypeName(outputType2.name); if (isAggregateOutput) { eventEmitter.emitSync("AggregateOutput", { ...args, outputType: outputType2 }); } const sourceFile = getSourceFile({ name: outputType2.name, type: fileType }); const classStructure = { decorators: [ { arguments: [], name: "ObjectType" } ], isExported: true, kind: tsMorph.StructureKind.Class, name: outputType2.name, properties: [] }; importDeclarations.add("Field", nestjsGraphql); importDeclarations.add("ObjectType", nestjsGraphql); for (const field of outputType2.fields) { const { isList, location, type } = field.outputType; const outputTypeName = getOutputTypeName(String(type)); const settings = isCountOutput ? void 0 : model && fieldSettings.get(model.name)?.get(field.name); const propertySettings = settings?.getPropertyType({ name: outputType2.name, output: true }); const isCustomsApplicable = outputTypeName === model?.fields.find((f) => f.name === field.name)?.type; field.outputType.type = outputTypeName; const propertyType = lodash.castArray( propertySettings?.name || getPropertyType({ location, type: outputTypeName }) ); const property = propertyStructure({ hasQuestionToken: isCountOutput ? true : void 0, isList, isNullable: field.isNullable, name: field.name, propertyType }); classStructure.properties?.push(property); if (propertySettings) { importDeclarations.create({ ...propertySettings }); } else if (propertyType.includes("Decimal")) { importDeclarations.add("Decimal", `${config.prismaClientImport}/runtime/library`); } let graphqlType; const shouldHideField = settings?.shouldHideField({ name: outputType2.name, output: true }) || config.decorate.some( (d) => d.name === "HideField" && d.from === "@nestjs/graphql" && d.isMatchField(field.name) && d.isMatchType(outputTypeName) ); const fieldType = settings?.getFieldType({ name: outputType2.name, output: true }); if (fieldType && isCustomsApplicable && !shouldHideField) { graphqlType = fieldType.name; importDeclarations.create({ ...fieldType }); } else { const graphqlImport = getGraphqlImport({ config, fileType, getSourceFile, isId: false, location, sourceFile, typeName: outputTypeName }); const referenceName = location === "enumTypes" ? getEnumName(propertyType[0]) : propertyType[0]; graphqlType = graphqlImport.name; if (graphqlImport.specifier && !importDeclarations.has(graphqlImport.name) && (graphqlImport.name !== outputType2.name && !shouldHideField || shouldHideField && referenceName === graphqlImport.name)) { importDeclarations.set(graphqlImport.name, { moduleSpecifier: graphqlImport.specifier, namedImports: [{ name: graphqlImport.name }] }); } } assert.ok(property.decorators, "property.decorators is undefined"); if (shouldHideField) { importDeclarations.add("HideField", nestjsGraphql); property.decorators.push({ arguments: [], name: "HideField" }); } else { property.decorators.push({ arguments: [ isList ? `() => [${graphqlType}]` : `() => ${graphqlType}`, JSON5.stringify({ ...settings?.fieldArguments(), nullable: Boolean(field.isNullable) }) ], name: "Field" }); if (isCustomsApplicable) { for (const options of settings || []) { if ((options.kind === "Decorator" && options.output && options.match?.(field.name)) ?? true) { property.decorators.push({ arguments: options.arguments, name: options.name }); assert.ok(options.from, "Missed 'from' part in configuration or field setting"); importDeclarations.create(options); } } } } eventEmitter.emitSync("ClassProperty", property, { isList, location, propertyType }); } sourceFile.set({ statements: [...importDeclarations.toStatements(), classStructure] }); } function purgeOutput(emitter) { emitter.on("Begin", begin); emitter.on("End", end); } function begin({ project, output }) { const sourceFiles = project.getDirectory(output)?.getDescendantSourceFiles(); if (sourceFiles) { for (const sourceFile of sourceFiles) { sourceFile.delete(); } } } function end({ project, output }) { const directories = project.getDirectory(output)?.getDescendantDirectories().filter((directory) => directory.getSourceFiles().length === 0).map((directory) => directory.getPath()); for (const directory of directories || []) { try { gracefulFs.rmdirSync(directory); } catch { } } } var ReExport = /* @__PURE__ */ ((ReExport2) => { ReExport2["None"] = "None"; ReExport2["Directories"] = "Directories"; ReExport2["Single"] = "Single"; ReExport2["All"] = "All"; return ReExport2; })(ReExport || {}); function reExport(emitter) { emitter.on("BeforeGenerateFiles", beforeGenerateFiles); } function beforeGenerateFiles(args) { const { project, output, config } = args; const rootDirectory = project.getDirectoryOrThrow(output); if (["Directories" /* Directories */, "All" /* All */].includes(config.reExport)) { for (const directory of rootDirectory.getDescendantDirectories()) { let indexSourceFile; const exportDeclarations = directory.getSourceFiles().filter((sourceFile) => { return sourceFile.getBaseName() !== "index.ts"; }).map((sourcesFile) => getExportDeclaration(directory, sourcesFile)); if (exportDeclarations.length > 0) { indexSourceFile = directory.createSourceFile( "index.ts", { statements: exportDeclarations }, { overwrite: true } ); } if (indexSourceFile) { continue; } const namespaceExportDeclarations = directory.getDirectories().map( (sourceDirectory) => getNamespaceExportDeclaration(directory, sourceDirectory) ); project.createSourceFile( `${directory.getPath()}/index.ts`, { statements: namespaceExportDeclarations }, { overwrite: true } ); } } if (config.reExport === "Single" /* Single */) { const exportDeclarations = project.getSourceFiles().filter((sourceFile) => { return sourceFile.getBaseName() !== "index.ts"; }).map((sourceFile) => getExportDeclaration(rootDirectory, sourceFile)); rootDirectory.createSourceFile( "index.ts", { statements: exportDeclarations }, { overwrite: true } ); } if (config.reExport === "All" /* All */) { const exportDeclarations = []; for (const directory of rootDirectory.getDirectories()) { if (directory.getBaseName() === "node_modules") continue; const sourceFile = directory.getSourceFileOrThrow("index.ts"); exportDeclarations.push(getExportDeclaration(rootDirectory, sourceFile)); } rootDirectory.createSourceFile( "index.ts",