UNPKG

zenstack

Version:

FullStack enhancement for Prisma ORM: seamless integration from database to UI

689 lines (681 loc) 36.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EnhancerGenerator = void 0; const runtime_1 = require("@zenstackhq/runtime"); const sdk_1 = require("@zenstackhq/sdk"); const ast_1 = require("@zenstackhq/sdk/ast"); const prisma_1 = require("@zenstackhq/sdk/prisma"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const semver_1 = __importDefault(require("semver")); const ts_morph_1 = require("ts-morph"); const upper_case_first_1 = require("upper-case-first"); const __1 = require(".."); const ast_utils_1 = require("../../../utils/ast-utils"); const exec_utils_1 = require("../../../utils/exec-utils"); const plugin_utils_1 = require("../../plugin-utils"); const prisma_2 = require("../../prisma"); const schema_generator_1 = require("../../prisma/schema-generator"); const enhancer_utils_1 = require("../enhancer-utils"); const auth_type_generator_1 = require("./auth-type-generator"); const checker_type_generator_1 = require("./checker-type-generator"); const model_typedef_generator_1 = require("./model-typedef-generator"); const LOGICAL_CLIENT_GENERATION_PATH = './.logical-prisma-client'; class EnhancerGenerator { constructor(model, options, project, outDir) { this.model = model; this.options = options; this.project = project; this.outDir = outDir; // a mapping from shortened names to full names this.reversedShortNameMap = new Map(); this.CreateUpdateWithoutDelegateRelationRegex = new RegExp(`(.+)(Create|Update)Without${(0, upper_case_first_1.upperCaseFirst)(runtime_1.DELEGATE_AUX_RELATION_PREFIX)}_(.+)Input`); const modelsWithAuthInDefault = this.model.declarations.filter((d) => (0, ast_1.isDataModel)(d) && d.fields.some((f) => f.attributes.some(enhancer_utils_1.isDefaultWithAuth))); this.modelsWithAuthInDefaultCreateInputPattern = new RegExp(`^(${modelsWithAuthInDefault.map((m) => m.name).join('|')})(Unchecked)?Create.*?Input$`); this.modelsWithJsonTypeFields = this.model.declarations.filter((d) => (0, ast_1.isDataModel)(d) && d.fields.some((f) => { var _a; return (0, ast_1.isTypeDef)((_a = f.type.reference) === null || _a === void 0 ? void 0 : _a.ref); })); // input/output patterns for models with json type fields const relevantTypePatterns = [ 'GroupByOutputType', '(Unchecked)?Create(\\S+?)?Input', '(Unchecked)?Update(\\S+?)?Input', 'CreateManyInput', '(Unchecked)?UpdateMany(Mutation)?Input', ]; // build combination regex with all models with JSON types and the above suffixes this.modelsWithJsonTypeFieldsInputOutputPattern = this.modelsWithJsonTypeFields.map((m) => new RegExp(`^(${m.name})(${relevantTypePatterns.join('|')})$`)); } generate() { return __awaiter(this, void 0, void 0, function* () { let dmmf; const prismaImport = (0, prisma_1.getPrismaClientImportSpec)(this.outDir, this.options); let prismaTypesFixed = false; let resultPrismaTypeImport = prismaImport; if (this.needsLogicalClient) { prismaTypesFixed = true; resultPrismaTypeImport = `${LOGICAL_CLIENT_GENERATION_PATH}/index-fixed`; const result = yield this.generateLogicalPrisma(); dmmf = result.dmmf; } // reexport PrismaClient types (original or fixed) const modelsDts = this.project.createSourceFile(path_1.default.join(this.outDir, 'models.d.ts'), `export * from '${resultPrismaTypeImport}';`, { overwrite: true }); yield modelsDts.save(); // reexport values from the original PrismaClient (enums, etc.) fs_1.default.writeFileSync(path_1.default.join(this.outDir, 'models.js'), `module.exports = require('${prismaImport}');`); const authDecl = (0, sdk_1.getAuthDecl)((0, sdk_1.getDataModelAndTypeDefs)(this.model)); const authTypes = authDecl ? (0, auth_type_generator_1.generateAuthType)(this.model, authDecl) : ''; const authTypeParam = authDecl ? `auth.${authDecl.name}` : 'AuthUser'; const checkerTypes = this.generatePermissionChecker ? (0, checker_type_generator_1.generateCheckerType)(this.model) : ''; for (const target of ['node', 'edge']) { // generate separate `enhance()` for node and edge runtime const outFile = target === 'node' ? 'enhance.ts' : 'enhance-edge.ts'; const enhanceTs = this.project.createSourceFile(path_1.default.join(this.outDir, outFile), `/* eslint-disable */ import { type EnhancementContext, type EnhancementOptions, type ZodSchemas, type AuthUser } from '@zenstackhq/runtime'; import { createEnhancement } from '@zenstackhq/runtime/enhancements/${target}'; import modelMeta from './model-meta'; import policy from './policy'; ${this.options.withZodSchemas ? `import * as zodSchemas from '${this.getZodImport()}';` : 'const zodSchemas = undefined;'} ${prismaTypesFixed ? this.createLogicalPrismaImports(prismaImport, resultPrismaTypeImport, target) : this.createSimplePrismaImports(prismaImport, target)} ${authTypes} ${checkerTypes} ${prismaTypesFixed ? this.createLogicalPrismaEnhanceFunction(authTypeParam) : this.createSimplePrismaEnhanceFunction(authTypeParam)} `, { overwrite: true }); this.saveSourceFile(enhanceTs); } return { dmmf, newPrismaClientDtsPath: prismaTypesFixed ? path_1.default.resolve(this.outDir, LOGICAL_CLIENT_GENERATION_PATH, 'index-fixed.d.ts') : undefined, }; }); } getZodImport() { const zodCustomOutput = (0, plugin_utils_1.getPluginCustomOutputFolder)(this.model, plugin_utils_1.CorePlugins.Zod); if (!this.options.output && !zodCustomOutput) { // neither zod or me (enhancer) have custom output, use the default return './zod'; } if (!zodCustomOutput) { // I have a custom output, but zod doesn't, import from runtime return '@zenstackhq/runtime/zod'; } if (!this.options.output) { // I don't have a custom output, but zod has, CLI will still generate // a copy into the default output, so we can still import from there return './zod'; } // both zod and me have custom output, resolve to relative path and import const schemaDir = path_1.default.dirname(this.options.schemaPath); const zodAbsPath = path_1.default.isAbsolute(zodCustomOutput) ? zodCustomOutput : path_1.default.resolve(schemaDir, zodCustomOutput); return (0, sdk_1.normalizedRelative)(this.outDir, zodAbsPath); } createSimplePrismaImports(prismaImport, target) { const prismaTargetImport = target === 'edge' ? `${prismaImport}/edge` : prismaImport; return `import { Prisma, type PrismaClient } from '${prismaTargetImport}'; import type * as _P from '${prismaImport}'; export type { PrismaClient }; /** * Infers the type of PrismaClient with ZenStack's enhancements. * @example * type EnhancedPrismaClient = Enhanced<typeof prisma>; */ export type Enhanced<Client> = Client; `; } createSimplePrismaEnhanceFunction(authTypeParam) { const returnType = `DbClient${this.generatePermissionChecker ? ' & ModelCheckers' : ''}`; return ` export function enhance<DbClient extends object>(prisma: DbClient, context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): ${returnType} { return createEnhancement(prisma, { modelMeta, policy, zodSchemas: zodSchemas as unknown as (ZodSchemas | undefined), prismaModule: Prisma, ...options }, context) as ${returnType}; } `; } createLogicalPrismaImports(prismaImport, prismaClientImport, target) { const prismaTargetImport = target === 'edge' ? `${prismaImport}/edge` : prismaImport; return `import { Prisma as _Prisma, PrismaClient as _PrismaClient } from '${prismaTargetImport}'; import type { InternalArgs, DynamicClientExtensionThis } from '${prismaImport}/runtime/library'; import type * as _P from '${prismaClientImport}'; import type { Prisma, PrismaClient } from '${prismaClientImport}'; export type { PrismaClient }; `; } createLogicalPrismaEnhanceFunction(authTypeParam) { const prismaVersion = (0, prisma_1.getPrismaVersion)(); // Prisma 5.16.0...6.5.0 introduced a new generic parameter to `DynamicClientExtensionThis` const hasClientOptions = prismaVersion && semver_1.default.gte(prismaVersion, '5.16.0') && semver_1.default.lt(prismaVersion, '6.5.0'); return ` // overload for plain PrismaClient export function enhance<ExtArgs extends Record<string, any> & InternalArgs>( prisma: _PrismaClient<any, any, ExtArgs>, context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): PrismaClient${this.generatePermissionChecker ? ' & ModelCheckers' : ''}; // overload for extended PrismaClient export function enhance<ExtArgs extends Record<string, any> & InternalArgs${hasClientOptions ? ', ClientOptions' : ''}>( prisma: DynamicClientExtensionThis<_Prisma.TypeMap<ExtArgs>, _Prisma.TypeMapCb, ExtArgs${hasClientOptions ? ', ClientOptions' : ''}>, context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): DynamicClientExtensionThis<Prisma.TypeMap<ExtArgs>, Prisma.TypeMapCb, ExtArgs${hasClientOptions ? ', ClientOptions' : ''}>${this.generatePermissionChecker ? ' & ModelCheckers' : ''}; export function enhance(prisma: any, context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): any { return createEnhancement(prisma, { modelMeta, policy, zodSchemas: zodSchemas as unknown as (ZodSchemas | undefined), prismaModule: _Prisma, ...options }, context); } /** * Infers the type of PrismaClient with ZenStack's enhancements. * @example * type EnhancedPrismaClient = Enhanced<typeof prisma>; */ export type Enhanced<Client> = Client extends _PrismaClient<infer _ClientOptions, infer _U, infer ExtArgs> ? PrismaClient : Client extends DynamicClientExtensionThis<infer _TypeMap, infer _TypeMapCb, infer ExtArgs${hasClientOptions ? ', infer ClientOptions' : ''}> ? DynamicClientExtensionThis<Prisma.TypeMap<ExtArgs>, Prisma.TypeMapCb, ExtArgs${hasClientOptions ? ', ClientOptions' : ''}> : Client; `; } get needsLogicalClient() { return this.hasDelegateModel(this.model) || this.hasAuthInDefault(this.model) || this.hasTypeDef(this.model); } hasDelegateModel(model) { const dataModels = (0, sdk_1.getDataModels)(model); return dataModels.some((dm) => (0, sdk_1.isDelegateModel)(dm) && dataModels.some((sub) => sub.superTypes.some((base) => base.ref === dm))); } hasAuthInDefault(model) { return (0, sdk_1.getDataModels)(model).some((dm) => dm.fields.some((f) => f.attributes.some((attr) => (0, enhancer_utils_1.isDefaultWithAuth)(attr)))); } hasTypeDef(model) { return model.declarations.some(ast_1.isTypeDef); } generateLogicalPrisma() { return __awaiter(this, void 0, void 0, function* () { const prismaGenerator = new schema_generator_1.PrismaSchemaGenerator(this.model); // dir of the zmodel file const zmodelDir = path_1.default.dirname(this.options.schemaPath); // generate a temp logical prisma schema in zmodel's dir const logicalPrismaFile = path_1.default.join(zmodelDir, `logical-${Date.now()}.prisma`); // calculate a relative output path to output the logical prisma client into enhancer's output dir const prismaClientOutDir = path_1.default.join(path_1.default.relative(zmodelDir, this.outDir), LOGICAL_CLIENT_GENERATION_PATH); const generateResult = yield prismaGenerator.generate({ provider: '@internal', // doesn't matter schemaPath: this.options.schemaPath, output: logicalPrismaFile, overrideClientGenerationPath: prismaClientOutDir, mode: 'logical', customAttributesAsComments: true, }); // reverse direction of shortNameMap and store for future lookup this.reversedShortNameMap = new Map(Array.from(generateResult.shortNameMap.entries()).map(([key, value]) => [value, key])); // generate the prisma client // only run prisma client generator for the logical schema const prismaClientGeneratorName = this.getPrismaClientGeneratorName(this.model); let generateCmd = `prisma generate --schema "${logicalPrismaFile}" --generator=${prismaClientGeneratorName}`; const prismaVersion = (0, prisma_1.getPrismaVersion)(); if (!prismaVersion || semver_1.default.gte(prismaVersion, '5.2.0')) { // add --no-engine to reduce generation size if the prisma version supports generateCmd += ' --no-engine'; } try { // run 'prisma generate' yield (0, exec_utils_1.execPackage)(generateCmd, { stdio: 'ignore' }); } catch (_a) { yield (0, prisma_2.trackPrismaSchemaError)(logicalPrismaFile); try { // run 'prisma generate' again with output to the console yield (0, exec_utils_1.execPackage)(generateCmd); } catch (_b) { // noop } throw new sdk_1.PluginError(__1.name, `Failed to run "prisma generate" on logical schema: ${logicalPrismaFile}`); } // make a bunch of typing fixes to the generated prisma client yield this.processClientTypes(path_1.default.join(this.outDir, LOGICAL_CLIENT_GENERATION_PATH)); // get the dmmf of the logical prisma schema const dmmf = yield this.getLogicalDMMF(logicalPrismaFile); try { // clean up temp schema if (fs_1.default.existsSync(logicalPrismaFile)) { fs_1.default.rmSync(logicalPrismaFile); } } catch (_c) { // ignore errors } return { prismaSchema: logicalPrismaFile, // load the dmmf of the logical prisma schema dmmf, }; }); } getLogicalDMMF(logicalPrismaFile) { return __awaiter(this, void 0, void 0, function* () { const dmmf = yield (0, prisma_1.getDMMF)({ datamodel: fs_1.default.readFileSync(logicalPrismaFile, { encoding: 'utf-8' }) }); // make necessary fixes // fields that use `auth()` in `@default` are not handled by Prisma so in the DMMF // they may be incorrectly represented as required, we need to fix that for input types // also, if a FK field is of such case, its corresponding relation field should be optional const createInputPattern = new RegExp(`^(.+?)(Unchecked)?Create.*Input$`); for (const inputType of dmmf.schema.inputObjectTypes.prisma) { const match = inputType.name.match(createInputPattern); const modelName = this.resolveName(match === null || match === void 0 ? void 0 : match[1]); if (modelName) { const dataModel = this.model.declarations.find((d) => (0, ast_1.isDataModel)(d) && d.name === modelName); if (dataModel) { for (const field of inputType.fields) { if (field.isRequired && this.shouldBeOptional(field, dataModel)) { // eslint-disable-next-line @typescript-eslint/no-explicit-any field.isRequired = false; } } } } } return dmmf; }); } shouldBeOptional(field, dataModel) { var _a; const dmField = dataModel.fields.find((f) => f.name === field.name); if (!dmField) { return false; } if ((0, sdk_1.hasAttribute)(dmField, '@default')) { return true; } if ((0, ast_1.isDataModel)((_a = dmField.type.reference) === null || _a === void 0 ? void 0 : _a.ref)) { // if FK field should be optional, the relation field should too const fkFields = (0, sdk_1.getForeignKeyFields)(dmField); if (fkFields.length > 0 && fkFields.every((f) => (0, sdk_1.hasAttribute)(f, '@default'))) { return true; } } return false; } getPrismaClientGeneratorName(model) { for (const generator of model.declarations.filter(ast_1.isGeneratorDecl)) { if (generator.fields.some((f) => f.name === 'provider' && (0, sdk_1.getLiteral)(f.value) === 'prisma-client-js')) { return generator.name; } } throw new sdk_1.PluginError(__1.name, `Cannot find prisma-client-js generator in the schema`); } processClientTypes(prismaClientDir) { return __awaiter(this, void 0, void 0, function* () { // make necessary updates to the generated `index.d.ts` file and save it as `index-fixed.d.ts` const project = new ts_morph_1.Project(); const sf = project.addSourceFileAtPath(path_1.default.join(prismaClientDir, 'index.d.ts')); // build a map of delegate models and their sub models const delegateInfo = []; this.model.declarations .filter((d) => (0, sdk_1.isDelegateModel)(d)) .forEach((dm) => { const concreteModels = (0, ast_utils_1.getConcreteModels)(dm); if (concreteModels.length > 0) { delegateInfo.push([dm, concreteModels]); } }); // transform index.d.ts and save it into a new file (better perf than in-line editing) const sfNew = project.createSourceFile(path_1.default.join(prismaClientDir, 'index-fixed.d.ts'), undefined, { overwrite: true, }); this.transformPrismaTypes(sf, sfNew, delegateInfo); this.generateExtraTypes(sfNew); sfNew.formatText(); yield sfNew.save(); }); } transformPrismaTypes(sf, sfNew, delegateInfo) { // copy toplevel imports sfNew.addImportDeclarations(sf.getImportDeclarations().map((n) => n.getStructure())); // copy toplevel import equals sfNew.addStatements(sf.getChildrenOfKind(ts_morph_1.SyntaxKind.ImportEqualsDeclaration).map((n) => n.getFullText())); // copy toplevel exports sfNew.addExportAssignments(sf.getExportAssignments().map((n) => n.getStructure())); // copy toplevel type aliases sfNew.addTypeAliases(sf.getTypeAliases().map((n) => n.getStructure())); // copy toplevel classes sfNew.addClasses(sf.getClasses().map((n) => n.getStructure())); // copy toplevel variables sfNew.addVariableStatements(sf.getVariableStatements().map((n) => n.getStructure())); // copy toplevel namespaces except for `Prisma` sfNew.addModules(sf .getModules() .filter((n) => n.getName() !== 'Prisma') .map((n) => n.getStructure())); // transform the `Prisma` namespace const prismaModule = sf.getModuleOrThrow('Prisma'); const newPrismaModule = sfNew.addModule({ name: 'Prisma', isExported: true }); this.transformPrismaModule(prismaModule, newPrismaModule, delegateInfo); } transformPrismaModule(prismaModule, newPrismaModule, delegateInfo) { // module block is the direct container of declarations inside a namespace const moduleBlock = prismaModule.getFirstChildByKindOrThrow(ts_morph_1.SyntaxKind.ModuleBlock); // most of the toplevel constructs should be copied over // here we use ts-morph batch operations for optimal performance // copy imports newPrismaModule.addStatements(moduleBlock.getChildrenOfKind(ts_morph_1.SyntaxKind.ImportEqualsDeclaration).map((n) => n.getFullText())); // copy classes newPrismaModule.addClasses(moduleBlock.getClasses().map((n) => n.getStructure())); // copy functions newPrismaModule.addFunctions(moduleBlock.getFunctions().map((n) => n.getStructure())); // copy nested namespaces newPrismaModule.addModules(moduleBlock.getModules().map((n) => n.getStructure())); // transform variables const newVariables = moduleBlock .getVariableStatements() .map((variable) => this.transformVariableStatement(variable)); newPrismaModule.addVariableStatements(newVariables); // transform interfaces const newInterfaces = moduleBlock.getInterfaces().map((iface) => this.transformInterface(iface, delegateInfo)); newPrismaModule.addInterfaces(newInterfaces); // transform type aliases const newTypeAliases = moduleBlock .getTypeAliases() .map((typeAlias) => this.transformTypeAlias(typeAlias, delegateInfo)); newPrismaModule.addTypeAliases(newTypeAliases); } transformVariableStatement(variable) { const structure = variable.getStructure(); // remove `delegate_aux_*` fields from the variable's typing const auxFields = this.findAuxDecls(variable); if (auxFields.length > 0) { structure.declarations.forEach((variable) => { if (variable.type) { let source = variable.type.toString(); auxFields.forEach((f) => { source = this.removeFromSource(source, f.getText()); }); variable.type = source; } }); } return structure; } transformInterface(iface, delegateInfo) { var _a, _b, _c; const structure = iface.getStructure(); // filter out aux fields structure.properties = (_a = structure.properties) === null || _a === void 0 ? void 0 : _a.filter((p) => !p.name.includes(runtime_1.DELEGATE_AUX_RELATION_PREFIX)); // filter out aux methods structure.methods = (_b = structure.methods) === null || _b === void 0 ? void 0 : _b.filter((m) => !m.name.includes(runtime_1.DELEGATE_AUX_RELATION_PREFIX)); if (delegateInfo.some(([delegate]) => `${delegate.name}Delegate` === iface.getName())) { // delegate models cannot be created directly, remove create/createMany/upsert structure.methods = (_c = structure.methods) === null || _c === void 0 ? void 0 : _c.filter((m) => !['create', 'createMany', 'createManyAndReturn', 'upsert'].includes(m.name)); } return structure; } transformTypeAlias(typeAlias, delegateInfo) { const structure = typeAlias.getStructure(); let source = structure.type; // remove aux fields source = this.removeAuxFieldsFromTypeAlias(typeAlias, source); // remove discriminator field from concrete input types source = this.removeDiscriminatorFromConcreteInput(typeAlias, delegateInfo, source); // remove create/connectOrCreate/upsert fields from delegate's input types source = this.removeCreateFromDelegateInput(typeAlias, delegateInfo, source); // remove delegate fields from nested mutation input types source = this.removeDelegateFieldsFromNestedMutationInput(typeAlias, delegateInfo, source); // fix delegate payload union type source = this.fixDelegatePayloadType(typeAlias, delegateInfo, source); // fix fk and relation fields related to using `auth()` in `@default` source = this.fixDefaultAuthType(typeAlias, source); // fix json field type source = this.fixJsonFieldType(typeAlias, source); structure.type = source; return structure; } fixDelegatePayloadType(typeAlias, delegateInfo, source) { // change the type of `$<DelegateModel>Payload` type of delegate model to a union of concrete types const typeName = typeAlias.getName(); const payloadRecord = delegateInfo.find(([delegate]) => `$${delegate.name}Payload` === typeName); if (payloadRecord) { const discriminatorDecl = (0, ast_utils_1.getDiscriminatorField)(payloadRecord[0]); if (discriminatorDecl) { source = `${payloadRecord[1] .map((concrete) => `($${concrete.name}Payload<ExtArgs> & { scalars: { ${discriminatorDecl.name}: '${concrete.name}' } })`) .join(' | ')}`; } } return source; } removeCreateFromDelegateInput(typeAlias, delegateInfo, source) { // remove create/connectOrCreate/upsert fields from delegate's input types because // delegate models cannot be created directly const typeName = typeAlias.getName(); const delegateModelNames = delegateInfo.map(([delegate]) => delegate.name); const delegateCreateUpdateInputRegex = new RegExp(`^(${delegateModelNames.join('|')})(Unchecked)?(Create|Update).*Input$`); if (delegateCreateUpdateInputRegex.test(typeName)) { const toRemove = typeAlias .getDescendantsOfKind(ts_morph_1.SyntaxKind.PropertySignature) .filter((p) => ['create', 'createMany', 'connectOrCreate', 'upsert'].includes(p.getName())); toRemove.forEach((r) => { this.removeFromSource(source, r.getText()); }); } return source; } removeDiscriminatorFromConcreteInput(typeAlias, delegateInfo, source) { // remove discriminator field from the create/update input because discriminator cannot be set directly const typeName = typeAlias.getName(); const delegateModelNames = delegateInfo.map(([delegate]) => delegate.name); const concreteModelNames = delegateInfo .map(([_, concretes]) => concretes.flatMap((c) => c.name)) .flatMap((name) => name); const allModelNames = [...new Set([...delegateModelNames, ...concreteModelNames])]; const concreteCreateUpdateInputRegex = new RegExp(`^(${allModelNames.join('|')})(Unchecked)?(Create|Update).*Input$`); const match = typeName.match(concreteCreateUpdateInputRegex); if (match) { const modelName = this.resolveName(match[1]); const dataModel = this.model.declarations.find((d) => (0, ast_1.isDataModel)(d) && d.name === modelName); if (!dataModel) { return source; } for (const field of dataModel.fields) { if ((0, sdk_1.isDiscriminatorField)(field)) { const fieldDef = this.findNamedProperty(typeAlias, field.name); if (fieldDef) { source = this.removeFromSource(source, fieldDef.getText()); } } } } return source; } removeAuxFieldsFromTypeAlias(typeAlias, source) { // remove `delegate_aux_*` fields from the type alias const auxDecls = this.findAuxDecls(typeAlias); if (auxDecls.length > 0) { auxDecls.forEach((d) => { source = this.removeFromSource(source, d.getText()); }); } return source; } removeDelegateFieldsFromNestedMutationInput(typeAlias, _delegateInfo, source) { const name = typeAlias.getName(); // remove delegate model fields (and corresponding fk fields) from // create/update input types nested inside concrete models const match = name.match(this.CreateUpdateWithoutDelegateRelationRegex); if (!match) { return source; } // [modelName]_[relationFieldName]_[concreteModelName] const nameTuple = this.resolveName(match[3], true); const [modelName, relationFieldName, _] = nameTuple.split('_'); const fieldDef = this.findNamedProperty(typeAlias, relationFieldName); if (fieldDef) { // remove relation field of delegate type, e.g., `asset` source = this.removeFromSource(source, fieldDef.getText()); } // remove fk fields related to the delegate type relation, e.g., `assetId` const relationModel = this.model.declarations.find((d) => (0, ast_1.isDataModel)(d) && d.name === modelName); if (!relationModel) { return source; } const relationField = relationModel.fields.find((f) => f.name === relationFieldName); if (!relationField) { return source; } const relAttr = (0, sdk_1.getAttribute)(relationField, '@relation'); if (!relAttr) { return source; } const fieldsArg = (0, sdk_1.getAttributeArg)(relAttr, 'fields'); let fkFields = []; if ((0, ast_1.isArrayExpr)(fieldsArg)) { fkFields = fieldsArg.items.map((e) => e.target.$refText); } fkFields.forEach((fkField) => { const fieldDef = this.findNamedProperty(typeAlias, fkField); if (fieldDef) { source = this.removeFromSource(source, fieldDef.getText()); } }); return source; } // resolves a potentially shortened name back to the original resolveName(name, withDelegateAuxPrefix = false) { if (!name) { return name; } const shortNameLookupKey = withDelegateAuxPrefix ? `${runtime_1.DELEGATE_AUX_RELATION_PREFIX}_${name}` : name; if (this.reversedShortNameMap.has(shortNameLookupKey)) { name = this.reversedShortNameMap.get(shortNameLookupKey); if (withDelegateAuxPrefix) { name = name.substring(runtime_1.DELEGATE_AUX_RELATION_PREFIX.length + 1); } } return name; } fixDefaultAuthType(typeAlias, source) { const match = typeAlias.getName().match(this.modelsWithAuthInDefaultCreateInputPattern); if (!match) { return source; } const modelName = this.resolveName(match[1]); const dataModel = this.model.declarations.find((d) => (0, ast_1.isDataModel)(d) && d.name === modelName); if (dataModel) { for (const fkField of dataModel.fields.filter((f) => f.attributes.some(enhancer_utils_1.isDefaultWithAuth))) { // change fk field to optional since it has a default source = source.replace(new RegExp(`^(\\s*${fkField.name}\\s*):`, 'm'), `$1?:`); const relationField = (0, sdk_1.getRelationField)(fkField); if (relationField) { // change relation field to optional since its fk has a default source = source.replace(new RegExp(`^(\\s*${relationField.name}\\s*):`, 'm'), `$1?:`); } } } return source; } fixJsonFieldType(typeAlias, source) { const typeName = typeAlias.getName(); const getTypedJsonFields = (model) => { return model.fields.filter((f) => { var _a; return (0, ast_1.isTypeDef)((_a = f.type.reference) === null || _a === void 0 ? void 0 : _a.ref); }); }; const replacePrismaJson = (source, field) => { let replaceValue = `$1: ${field.type.reference.$refText}`; if (field.type.array) { replaceValue += '[]'; } if (field.type.optional) { replaceValue += ' | null'; } // Check if the field in the source is optional (has a `?`) const isOptionalInSource = new RegExp(`(${field.name}\\?\\s*):`).test(source); if (isOptionalInSource) { replaceValue += ' | $Types.Skip'; } return source.replace(new RegExp(`(${field.name}\\??\\s*):[^\\n]+`), replaceValue); }; // fix "$[Model]Payload" type const payloadModelMatch = this.modelsWithJsonTypeFields.find((m) => `$${m.name}Payload` === typeName); if (payloadModelMatch) { const scalars = typeAlias .getDescendantsOfKind(ts_morph_1.SyntaxKind.PropertySignature) .find((p) => p.getName() === 'scalars'); if (!scalars) { return source; } const fieldsToFix = getTypedJsonFields(payloadModelMatch); for (const field of fieldsToFix) { source = replacePrismaJson(source, field); } } // fix input/output types, "[Model]CreateInput", etc. for (const pattern of this.modelsWithJsonTypeFieldsInputOutputPattern) { const match = typeName.match(pattern); if (!match) { continue; } // first capture group is the model name const modelName = this.resolveName(match[1]); const model = this.modelsWithJsonTypeFields.find((m) => m.name === modelName); const fieldsToFix = getTypedJsonFields(model); for (const field of fieldsToFix) { source = replacePrismaJson(source, field); } break; } return source; } generateExtraTypes(sf) { return __awaiter(this, void 0, void 0, function* () { for (const decl of this.model.declarations) { if ((0, ast_1.isTypeDef)(decl)) { (0, model_typedef_generator_1.generateTypeDefType)(sf, decl); } } }); } findNamedProperty(typeAlias, name) { return typeAlias.getFirstDescendant((d) => d.isKind(ts_morph_1.SyntaxKind.PropertySignature) && d.getName() === name); } findAuxDecls(node) { return node .getDescendantsOfKind(ts_morph_1.SyntaxKind.PropertySignature) .filter((n) => n.getName().includes(runtime_1.DELEGATE_AUX_RELATION_PREFIX)); } saveSourceFile(sf) { if (this.options.preserveTsFiles) { (0, sdk_1.saveSourceFile)(sf); } } get generatePermissionChecker() { return this.options.generatePermissionChecker === true; } removeFromSource(source, text) { source = source.replace(text, ''); return this.trimEmptyLines(source); } trimEmptyLines(source) { return source.replace(/^\s*[\r\n]/gm, ''); } } exports.EnhancerGenerator = EnhancerGenerator; //# sourceMappingURL=index.js.map