zenstack
Version:
FullStack enhancement for Prisma ORM: seamless integration from database to UI
689 lines (681 loc) • 36.1 kB
JavaScript
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
;