kapix-typegraphql-prisma
Version:

500 lines (479 loc) • 24.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const ts_morph_1 = require("ts-morph");
const path_1 = tslib_1.__importDefault(require("path"));
const node_child_process_1 = require("node:child_process");
const node_util_1 = require("node:util");
const execa = (0, node_util_1.promisify)(node_child_process_1.exec);
const helpers_1 = require("./helpers");
const enum_1 = tslib_1.__importDefault(require("./enum"));
const model_type_class_1 = tslib_1.__importDefault(require("./model-type-class"));
const relations_1 = tslib_1.__importDefault(require("./resolvers/relations"));
const type_class_1 = require("./type-class");
const full_crud_1 = tslib_1.__importDefault(require("./resolvers/full-crud"));
const config_1 = require("./config");
const imports_1 = require("./imports");
const dmmf_document_1 = require("./dmmf/dmmf-document");
const args_class_1 = tslib_1.__importDefault(require("./args-class"));
const separate_action_1 = tslib_1.__importDefault(require("./resolvers/separate-action"));
const prisma_version_1 = require("../utils/prisma-version");
const generate_enhance_1 = require("./generate-enhance");
const generate_scalars_1 = require("./generate-scalars");
const generate_helpers_1 = require("./generate-helpers");
const emit_block_1 = require("./emit-block");
const baseCompilerOptions = {
target: ts_morph_1.ScriptTarget.ES2019,
module: ts_morph_1.ModuleKind.CommonJS,
emitDecoratorMetadata: true,
experimentalDecorators: true,
esModuleInterop: true,
};
async function generateCode(dmmf, baseOptions, log = helpers_1.noop) {
var _a, _b, _c, _d, _e;
(0, prisma_version_1.ensureInstalledCorrectPrismaPackage)();
const options = {
...baseOptions,
blocksToEmit: (0, emit_block_1.getBlocksToEmit)(baseOptions.emitOnly),
contextPrismaKey: (_a = baseOptions.contextPrismaKey) !== null && _a !== void 0 ? _a : "prisma",
relativePrismaOutputPath: (0, helpers_1.toUnixPath)(path_1.default.relative(baseOptions.outputDirPath, baseOptions.prismaClientPath)),
absolutePrismaOutputPath: !baseOptions.customPrismaImportPath &&
baseOptions.prismaClientPath.includes("node_modules")
? "@prisma/client"
: undefined,
formatGeneratedCode: (_b = baseOptions.formatGeneratedCode) !== null && _b !== void 0 ? _b : "tsc",
queryActions: (_c = baseOptions.queryActions) !== null && _c !== void 0 ? _c : [
"findUnique",
"findFirst",
"findMany",
// "groupBy",
],
mutationActions: (_d = baseOptions.mutationActions) !== null && _d !== void 0 ? _d : [
"createOne",
"createMany",
"updateOne",
"deleteMany",
"updateMany",
"upsertOne",
],
};
const baseDirPath = options.outputDirPath;
const emitTranspiledCode = (_e = options.emitTranspiledCode) !== null && _e !== void 0 ? _e : options.outputDirPath.includes("node_modules");
const project = new ts_morph_1.Project({
compilerOptions: {
...baseCompilerOptions,
...(emitTranspiledCode && {
declaration: true,
importHelpers: true,
}),
},
});
log("Transforming dmmfDocument...");
const dmmfDocument = new dmmf_document_1.DmmfDocument(dmmf, options);
const hasAggregate = options.queryActions.includes("aggregate");
const hasNoMutations = options.mutationActions.length === 0;
// const hasCreateOne = options.mutationActions.includes('createOne')
// const hasCreateMany = options.mutationActions.includes('createMany')
// const hasUpdateOne = options.mutationActions.includes('updateOne')
// const hasUpdateMany = options.mutationActions.includes('updateMany')
const inputTypes = dmmfDocument.schema.inputTypes.filter(t => {
if (!hasAggregate &&
!t.typeName.includes("OrderByRelationAggregateInput") &&
(t.typeName.includes("WithAggreg") ||
t.typeName.includes("AggregateInput"))) {
return false;
}
if (hasNoMutations) {
if (t.typeName.includes('CreateInput')) {
return false;
}
if (t.typeName.includes('CreateMany')) {
return false;
}
if (t.typeName.includes('UpdateInput')) {
return false;
}
if (t.typeName.includes('UpdateMany')) {
return false;
}
}
// if (!hasCreateOne && t.typeName.includes('CreateInput')) {
// return false
// }
// if (!hasCreateMany && t.typeName.includes('CreateMany')) {
// return false
// }
// if (!hasUpdateOne && t.typeName.includes('UpdateInput')) {
// return false
// }
// if (!hasUpdateMany && t.typeName.includes('UpdateMany')) {
// return false
// }
return true;
});
// debugger
if (dmmfDocument.shouldGenerateBlock("enums")) {
log("Generating enums...");
const datamodelEnumNames = dmmfDocument.datamodel.enums.map(enumDef => enumDef.typeName);
dmmfDocument.datamodel.enums.forEach(enumDef => (0, enum_1.default)(project, baseDirPath, enumDef));
dmmfDocument.schema.enums
// skip enums from datamodel
.filter(enumDef => !datamodelEnumNames.includes(enumDef.typeName))
.forEach(enumDef => (0, enum_1.default)(project, baseDirPath, enumDef));
const emittedEnumNames = [
...new Set([
...dmmfDocument.schema.enums.map(it => it.typeName),
...dmmfDocument.datamodel.enums.map(it => it.typeName),
]),
];
const enumsBarrelExportSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.enumsFolderName, "index.ts"), undefined, { overwrite: true });
(0, imports_1.generateEnumsBarrelFile)(enumsBarrelExportSourceFile, emittedEnumNames);
}
if (dmmfDocument.shouldGenerateBlock("models")) {
log("Generating models...");
dmmfDocument.datamodel.models.forEach(model => {
const modelOutputType = dmmfDocument.schema.outputTypes.find(type => type.name === model.name);
return (0, model_type_class_1.default)(project, baseDirPath, model, modelOutputType, dmmfDocument);
});
const modelsBarrelExportSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.modelsFolderName, "index.ts"), undefined, { overwrite: true });
(0, imports_1.generateModelsBarrelFile)(modelsBarrelExportSourceFile, dmmfDocument.datamodel.models.map(it => it.typeName));
}
const resolversDirPath = path_1.default.resolve(baseDirPath, config_1.resolversFolderName);
let outputTypesToGenerate = [];
if (dmmfDocument.shouldGenerateBlock("outputs")) {
log("Generating output types...");
const rootTypes = dmmfDocument.schema.outputTypes.filter(type => ["Query", "Mutation"].includes(type.name));
const modelNames = dmmfDocument.datamodel.models.map(model => model.name);
outputTypesToGenerate = dmmfDocument.schema.outputTypes.filter(
// skip generating models and root resolvers
type => !modelNames.includes(type.name) && !rootTypes.includes(type));
const outputTypesFieldsArgsToGenerate = outputTypesToGenerate
.map(it => it.fields)
.reduce((a, b) => a.concat(b), [])
.filter(it => it.argsTypeName);
outputTypesToGenerate.forEach(type => (0, type_class_1.generateOutputTypeClassFromType)(project, resolversDirPath, type, dmmfDocument, options));
const outputsBarrelExportSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.outputsFolderName, "index.ts"), undefined, { overwrite: true });
(0, imports_1.generateOutputsBarrelFile)(outputsBarrelExportSourceFile, outputTypesToGenerate.map(it => it.typeName), outputTypesFieldsArgsToGenerate.length > 0);
if (outputTypesFieldsArgsToGenerate.length > 0) {
log("Generating output types args...");
outputTypesFieldsArgsToGenerate.forEach(async (field) => {
(0, args_class_1.default)(project, path_1.default.resolve(resolversDirPath, config_1.outputsFolderName), field.args, field.argsTypeName, dmmfDocument, 2);
});
const outputsArgsBarrelExportSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.outputsFolderName, config_1.argsFolderName, "index.ts"), undefined, { overwrite: true });
(0, imports_1.generateArgsBarrelFile)(outputsArgsBarrelExportSourceFile, outputTypesFieldsArgsToGenerate.map(it => it.argsTypeName));
}
}
if (dmmfDocument.shouldGenerateBlock("inputs")) {
log("Generating input types...");
// if (dmmfDocument.schema.inputTypes.some(type => type.name.toLowerCase().includes("enum"))) {
// debugger
// }
const geometryInputContent = `import * as TypeGraphQL from "type-graphql";
import * as GraphQLScalars from "graphql-scalars";
import { Prisma } from "@prisma/client";
@TypeGraphQL.InputType("Coordinate", {})
export class Coordinate {
@TypeGraphQL.Field(_type => Number)
lat: number
@TypeGraphQL.Field(_type => Number)
lng: number
}
@TypeGraphQL.InputType("GeometryInput", {})
export class GeometryInput {
@TypeGraphQL.Field(_type => String)
type: string
@TypeGraphQL.Field(_type => Coordinate)
coord: Coordinate
}
@TypeGraphQL.InputType("GeometryListInput", {})
export class GeometryListInput {
@TypeGraphQL.Field(_type => String)
type: string
@TypeGraphQL.Field(_type => [Coordinate])
coords: Coordinate[]
}
@TypeGraphQL.InputType("DistanceFilter", {})
export class DistanceFilter {
@TypeGraphQL.Field(_type => Coordinate, {
nullable:true
})
centerPoint?: Coordinate;
@TypeGraphQL.Field(_type => TypeGraphQL.Float, {
nullable: true
})
lt?: number | undefined;
@TypeGraphQL.Field(_type => [GeometryListInput], {
nullable: true
})
polygons?: GeometryListInput[];
}
`;
project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.inputsFolderName, "GeometryInput.ts"), geometryInputContent, {
overwrite: true,
});
inputTypes.forEach(type => {
(0, type_class_1.generateInputTypeClassFromType)(project, resolversDirPath, type, options);
});
const inputsBarrelExportSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.inputsFolderName, "index.ts"), undefined, { overwrite: true });
(0, imports_1.generateInputsBarrelFile)(inputsBarrelExportSourceFile, inputTypes.map(it => it.typeName));
}
if (dmmfDocument.relationModels.length > 0 &&
dmmfDocument.shouldGenerateBlock("relationResolvers")) {
log("Generating relation resolvers...");
dmmfDocument.relationModels.forEach(relationModel => (0, relations_1.default)(project, baseDirPath, dmmfDocument, relationModel, options));
const relationResolversBarrelExportSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.relationsResolversFolderName, "resolvers.index.ts"), undefined, { overwrite: true });
(0, imports_1.generateResolversBarrelFile)(relationResolversBarrelExportSourceFile, dmmfDocument.relationModels.map(relationModel => ({
resolverName: relationModel.resolverName,
modelName: relationModel.model.typeName,
})));
log("Generating relation resolver args...");
dmmfDocument.relationModels.forEach(async (relationModelData) => {
const resolverDirPath = path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.relationsResolversFolderName, relationModelData.model.typeName);
relationModelData.relationFields
.filter(field => field.argsTypeName)
.forEach(async (field) => {
(0, args_class_1.default)(project, resolverDirPath, field.outputTypeField.args, field.argsTypeName, dmmfDocument);
});
const argTypeNames = relationModelData.relationFields
.filter(it => it.argsTypeName !== undefined)
.map(it => it.argsTypeName);
if (argTypeNames.length) {
const barrelExportSourceFile = project.createSourceFile(path_1.default.resolve(resolverDirPath, config_1.argsFolderName, "index.ts"), undefined, { overwrite: true });
(0, imports_1.generateArgsBarrelFile)(barrelExportSourceFile, argTypeNames);
}
});
const relationModelsWithArgs = dmmfDocument.relationModels.filter(relationModelData => relationModelData.relationFields.some(it => it.argsTypeName !== undefined));
if (relationModelsWithArgs.length > 0) {
const relationResolversArgsIndexSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.relationsResolversFolderName, "args.index.ts"), undefined, { overwrite: true });
(0, imports_1.generateArgsIndexFile)(relationResolversArgsIndexSourceFile, relationModelsWithArgs.map(relationModelData => relationModelData.model.typeName));
}
const relationResolversIndexSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.relationsResolversFolderName, "index.ts"), undefined, { overwrite: true });
(0, imports_1.generateResolversIndexFile)(relationResolversIndexSourceFile, "relations", relationModelsWithArgs.length > 0);
}
if (dmmfDocument.shouldGenerateBlock("crudResolvers")) {
log("Generating crud resolvers...");
dmmfDocument.modelMappings.forEach(async (mapping) => {
const model = dmmfDocument.datamodel.models.find(model => model.name === mapping.modelName);
(0, full_crud_1.default)(project, baseDirPath, mapping, model, dmmfDocument, options);
mapping.actions.forEach(async (action) => {
const model = dmmfDocument.datamodel.models.find(model => model.name === mapping.modelName);
(0, separate_action_1.default)(project, baseDirPath, model, action, mapping, dmmfDocument, options);
});
});
const generateMappingData = dmmfDocument.modelMappings.map(mapping => {
const model = dmmfDocument.datamodel.models.find(model => model.name === mapping.modelName);
return {
modelName: model.typeName,
resolverName: mapping.resolverName,
actionResolverNames: mapping.actions.map(it => it.actionResolverName),
};
});
const crudResolversBarrelExportSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.crudResolversFolderName, "resolvers-crud.index.ts"), undefined, { overwrite: true });
(0, imports_1.generateResolversBarrelFile)(crudResolversBarrelExportSourceFile, generateMappingData);
const crudResolversActionsBarrelExportSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.crudResolversFolderName, "resolvers-actions.index.ts"), undefined, { overwrite: true });
generatePostgisQuery(project, baseDirPath);
(0, imports_1.generateResolversActionsBarrelFile)(crudResolversActionsBarrelExportSourceFile, generateMappingData);
const crudResolversIndexSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.crudResolversFolderName, "index.ts"), undefined, { overwrite: true });
(0, imports_1.generateResolversIndexFile)(crudResolversIndexSourceFile, "crud", true);
log("Generating crud resolvers args...");
dmmfDocument.modelMappings.forEach(async (mapping) => {
const actionsWithArgs = mapping.actions.filter(it => it.argsTypeName !== undefined);
if (actionsWithArgs.length) {
const model = dmmfDocument.datamodel.models.find(model => model.name === mapping.modelName);
const resolverDirPath = path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.crudResolversFolderName, model.typeName);
actionsWithArgs.forEach(async (action) => {
(0, args_class_1.default)(project, resolverDirPath, action.method.args, action.argsTypeName, dmmfDocument);
});
const barrelExportSourceFile = project.createSourceFile(path_1.default.resolve(resolverDirPath, config_1.argsFolderName, "index.ts"), undefined, { overwrite: true });
(0, imports_1.generateArgsBarrelFile)(barrelExportSourceFile, actionsWithArgs.map(it => it.argsTypeName));
}
});
const crudResolversArgsIndexSourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.crudResolversFolderName, "args.index.ts"), undefined, { overwrite: true });
(0, imports_1.generateArgsIndexFile)(crudResolversArgsIndexSourceFile, dmmfDocument.modelMappings
.filter(mapping => mapping.actions.some(it => it.argsTypeName !== undefined))
.map(mapping => mapping.modelTypeName));
}
log("Generate enhance map");
const enhanceSourceFile = project.createSourceFile(baseDirPath + "/enhance.ts", undefined, { overwrite: true });
(0, generate_enhance_1.generateEnhanceMap)(enhanceSourceFile, dmmfDocument, dmmfDocument.modelMappings, dmmfDocument.relationModels, dmmfDocument.datamodel.models, dmmfDocument.schema.inputTypes, outputTypesToGenerate, options);
log("Generate custom scalars");
const scalarsSourceFile = project.createSourceFile(baseDirPath + "/scalars.ts", undefined, { overwrite: true });
(0, generate_scalars_1.generateCustomScalars)(scalarsSourceFile, dmmfDocument.options);
log("Generate custom helpers");
const helpersSourceFile = project.createSourceFile(baseDirPath + "/helpers.ts", undefined, { overwrite: true });
(0, generate_helpers_1.generateHelpersFile)(helpersSourceFile, dmmfDocument.options);
log("Generating index file");
const indexSourceFile = project.createSourceFile(baseDirPath + "/index.ts", undefined, { overwrite: true });
(0, imports_1.generateIndexFile)(indexSourceFile, dmmfDocument.relationModels.length > 0, dmmfDocument.options.blocksToEmit);
log("Emitting generated code files");
if (emitTranspiledCode) {
await project.emit();
}
else {
if (options.formatGeneratedCode === "tsc") {
for (const file of project.getSourceFiles()) {
file.formatText({ indentSize: 2 });
}
}
await project.save();
if (options.formatGeneratedCode === "prettier") {
await execa(`npx prettier --write ${baseDirPath}`);
}
}
}
exports.default = generateCode;
function generatePostgisQuery(project, baseDirPath) {
const sourceFile = project.createSourceFile(path_1.default.resolve(baseDirPath, config_1.resolversFolderName, config_1.crudResolversFolderName, "PostgisQuery.ts"), undefined, { overwrite: true });
sourceFile.addFunction({
name: "updateGeomFields",
isExported: true,
isAsync: true,
parameters: [
{
name: "id",
type: "string",
},
{
name: "args",
type: "any",
},
{
name: "prisma",
type: "any",
},
{
name: "tabName",
type: "string",
},
{
name: "geomFieldsList",
type: "string[]",
},
],
statements: 'const updates: string[] = [] \n\
geomFieldsList.forEach((field) => { \n\
const geometry = args.data[field] as any \n\
if(!geometry) return \n\
const { coord } = geometry \n\
const point = `ST_GeomFromText(\'POINT(${coord.lng} ${coord.lat})\',4326)` \n\
updates.push(`"${field}Geom" = ${point}`) \n\
}) \n\
if(updates.length > 0) \n\
await prisma.$executeRawUnsafe(`UPDATE "${tabName}" \n\
SET ${updates.join(", ")} \n\
WHERE id = \'${id}\'`)',
});
sourceFile.addFunction({
name: "filterGeomByIds",
isExported: true,
isAsync: false,
parameters: [
{
name: "idWithDistance",
type: "undefined | { id: string }[]",
},
{
name: "result",
type: "any",
},
{
name: "args",
type: "any",
},
{
name: "take",
type: "undefined | number",
},
],
statements: `
if (idWithDistance) {
const idToKeep = idWithDistance
.filter(idWithDistanceItem => result.some(obj => obj.id === idWithDistanceItem.id))
.slice((args.skip ?? 0) * (take ?? 0), take);
result = result.filter(obj => idToKeep.some(idWithDistanceItem => idWithDistanceItem.id === obj.id));
}
return result;`,
});
sourceFile.addFunction({
name: "applyGeomPointFilter",
isExported: true,
isAsync: true,
returnType: `Promise<[
undefined | Array<{
id: string;
}>,
undefined | number
]>`,
parameters: [
{
name: "geomFieldsList",
type: "string[]",
},
{
name: "args",
type: "any",
},
{
name: "tableName",
type: "string",
},
{
name: "pkName",
type: "string",
},
{
name: "prisma",
type: "any",
},
],
statements: `
const geomPointToFilter = geomFieldsList.filter((field) => {
const geom = args.where && args.where[field]
if (geom && geom.centerPoint) return true
})
if (geomPointToFilter.length > 0) {
const whereGeomPoint = Array<{
fieldName: string,
centerPoint: { lat: number, lng: number },
lt: number
}>()
for (const field of geomPointToFilter) {
const { centerPoint, lt } = args.where[field]
whereGeomPoint.push({ fieldName: field, centerPoint, lt })
delete args.where[field]
}
const stringGeomPoint = whereGeomPoint.map((point) => {
return {
postgisString: \`ST_SetSRID(ST_MakePoint(\${point.centerPoint.lng}, \${point.centerPoint.lat}), 4326)::geography\`,
fieldName: point.fieldName,
lt: point.lt
}
})
let query= \`SELECT r.\${pkName}, RANK() OVER (
ORDER BY r.distanceFromCenter ASC
) as distanceOrder
FROM (
SELECT \${pkName}, ST_Distance("\${stringGeomPoint[0].fieldName}Geom",
\${stringGeomPoint[0].postgisString}) as distanceFromCenter
FROM "\${tableName}"
WHERE \${stringGeomPoint.map((point) => {
return \`ST_DWithin("\${point.fieldName}Geom", \${point.postgisString}, \${point.lt * 1000})\` // for km; 1000m = 1km
}).join(' AND ')}
) r;\`
const rawResult = (await prisma.$queryRawUnsafe(query))
let take = args.take
if (take) {
delete args.take
}
const bigIntList: any[] = []
const tempList = rawResult.values()
for (const current of tempList) {
bigIntList.push(current[pkName])
}
args.where[pkName] = { in: bigIntList }
return [rawResult, take]
}
return [undefined, undefined]`,
});
}
//# sourceMappingURL=generate-code.js.map