UNPKG

zenstack

Version:

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

739 lines (738 loc) 39.7 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.PrismaSchemaGenerator = void 0; const ast_1 = require("@zenstackhq/language/ast"); const sdk_1 = require("@zenstackhq/sdk"); const prisma_1 = require("@zenstackhq/sdk/prisma"); const ts_pattern_1 = require("ts-pattern"); const runtime_1 = require("@zenstackhq/runtime"); const sdk_2 = require("@zenstackhq/sdk"); const fs_1 = __importDefault(require("fs")); const promises_1 = require("fs/promises"); const lower_case_first_1 = require("lower-case-first"); const path_1 = __importDefault(require("path")); const semver_1 = __importDefault(require("semver")); const _1 = require("."); const utils_1 = require("../../language-server/validator/utils"); const ast_utils_1 = require("../../utils/ast-utils"); const exec_utils_1 = require("../../utils/exec-utils"); const enhancer_utils_1 = require("../enhancer/enhancer-utils"); const prisma_builder_1 = require("./prisma-builder"); const MODEL_PASSTHROUGH_ATTR = '@@prisma.passthrough'; const FIELD_PASSTHROUGH_ATTR = '@prisma.passthrough'; const PROVIDERS_SUPPORTING_NAMED_CONSTRAINTS = ['postgresql', 'mysql', 'cockroachdb']; const PROVIDERS_SUPPORTING_TYPEDEF_FIELDS = ['postgresql', 'sqlite']; // Some database providers like postgres and mysql have default limit to the length of identifiers // Here we use a conservative value that should work for most cases, and truncate names if needed const IDENTIFIER_NAME_MAX_LENGTH = 50 - runtime_1.DELEGATE_AUX_RELATION_PREFIX.length; /** * Generates Prisma schema file */ class PrismaSchemaGenerator { constructor(zmodel) { this.zmodel = zmodel; this.zModelGenerator = new sdk_2.ZModelCodeGenerator(); this.PRELUDE = `////////////////////////////////////////////////////////////////////////////////////////////// // DO NOT MODIFY THIS FILE // // This file is automatically generated by ZenStack CLI and should not be manually updated. // ////////////////////////////////////////////////////////////////////////////////////////////// `; this.mode = 'physical'; this.customAttributesAsComments = false; // a mapping from full names to shortened names this.shortNameMap = new Map(); } generate(options) { return __awaiter(this, void 0, void 0, function* () { if (!options.output) { throw new sdk_2.PluginError(_1.name, 'Output file is not specified'); } const outFile = options.output; const warnings = []; if (options.mode) { this.mode = options.mode; } if (options.customAttributesAsComments !== undefined && typeof options.customAttributesAsComments !== 'boolean') { throw new sdk_2.PluginError(_1.name, 'option "customAttributesAsComments" must be a boolean'); } this.customAttributesAsComments = options.customAttributesAsComments === true; const prismaVersion = (0, prisma_1.getPrismaVersion)(); if (prismaVersion && semver_1.default.lt(prismaVersion, runtime_1.PRISMA_MINIMUM_VERSION)) { warnings.push(`ZenStack requires Prisma version "${runtime_1.PRISMA_MINIMUM_VERSION}" or higher. Detected version is "${prismaVersion}".`); } const prisma = new prisma_builder_1.PrismaModel(); for (const decl of this.zmodel.declarations) { switch (decl.$type) { case ast_1.DataSource: this.generateDataSource(prisma, decl); break; case ast_1.Enum: this.generateEnum(prisma, decl); break; case ast_1.DataModel: this.generateModel(prisma, decl); break; case ast_1.GeneratorDecl: this.generateGenerator(prisma, decl, options); break; } } if (!fs_1.default.existsSync(path_1.default.dirname(outFile))) { fs_1.default.mkdirSync(path_1.default.dirname(outFile), { recursive: true }); } yield (0, promises_1.writeFile)(outFile, this.PRELUDE + prisma.toString()); if (options.format !== false) { try { // run 'prisma format' yield (0, exec_utils_1.execPackage)(`prisma format --schema ${outFile}`, { stdio: 'ignore' }); } catch (_a) { warnings.push(`Failed to format Prisma schema file`); } } return { warnings, shortNameMap: this.shortNameMap }; }); } generateDataSource(prisma, dataSource) { const fields = dataSource.fields.map((f) => ({ name: f.name, text: this.configExprToText(f.value), })); prisma.addDataSource(dataSource.name, fields); } configExprToText(expr) { if ((0, ast_1.isLiteralExpr)(expr)) { return this.literalToText(expr); } else if ((0, ast_1.isInvocationExpr)(expr)) { const fc = this.makeFunctionCall(expr); return fc.toString(); } else { return this.configArrayToText(expr); } } configArrayToText(expr) { return ('[' + expr.items .map((item) => { if ((0, ast_1.isLiteralExpr)(item)) { return this.literalToText(item); } else { return (item.name + (item.args.length > 0 ? '(' + item.args.map((arg) => this.configInvocationArgToText(arg)).join(', ') + ')' : '')); } }) .join(', ') + ']'); } configInvocationArgToText(arg) { return `${arg.name}: ${this.literalToText(arg.value)}`; } literalToText(expr) { return JSON.stringify(expr.value); } exprToText(expr) { return new sdk_2.ZModelCodeGenerator({ quote: 'double' }).generate(expr); } generateGenerator(prisma, decl, options) { var _a, _b; const generator = prisma.addGenerator(decl.name, decl.fields.map((f) => ({ name: f.name, text: this.configExprToText(f.value) }))); // deal with configuring PrismaClient preview features const provider = generator.fields.find((f) => f.name === 'provider'); if ((provider === null || provider === void 0 ? void 0 : provider.text) === JSON.stringify('prisma-client-js')) { const prismaVersion = (0, prisma_1.getPrismaVersion)(); if (prismaVersion) { const previewFeatures = JSON.parse((_b = (_a = generator.fields.find((f) => f.name === 'previewFeatures')) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : '[]'); if (!Array.isArray(previewFeatures)) { throw new sdk_2.PluginError(_1.name, 'option "previewFeatures" must be an array'); } if (previewFeatures.length > 0) { const curr = generator.fields.find((f) => f.name === 'previewFeatures'); if (!curr) { generator.fields.push({ name: 'previewFeatures', text: JSON.stringify(previewFeatures) }); } else { curr.text = JSON.stringify(previewFeatures); } } } if (typeof options.overrideClientGenerationPath === 'string') { const output = generator.fields.find((f) => f.name === 'output'); if (output) { output.text = JSON.stringify(options.overrideClientGenerationPath); } else { generator.fields.push({ name: 'output', text: JSON.stringify(options.overrideClientGenerationPath), }); } } } } generateModel(prisma, decl) { const model = decl.isView ? prisma.addView(decl.name) : prisma.addModel(decl.name); for (const field of decl.fields) { if (field.$inheritedFrom) { const inheritedFromDelegate = (0, sdk_2.getInheritedFromDelegate)(field); if ( // fields inherited from delegate are excluded from physical schema !inheritedFromDelegate || // logical schema keeps all inherited fields this.mode === 'logical' || // id fields are always kept (0, sdk_2.isIdField)(field)) { this.generateModelField(model, field); } } else { this.generateModelField(model, field); } } for (const attr of decl.attributes.filter((attr) => this.isPrismaAttribute(attr))) { this.generateContainerAttribute(model, attr); } // user defined comments pass-through decl.comments.forEach((c) => model.addComment(c)); this.getCustomAttributesAsComments(decl).forEach((c) => model.addComment(c)); // physical: generate relation fields on base models linking to concrete models this.generateDelegateRelationForBase(model, decl); // physical: generate reverse relation fields on concrete models this.generateDelegateRelationForConcrete(model, decl); // logical: expand relations on other models that reference delegated models to concrete models this.expandPolymorphicRelations(model, decl); // logical: ensure relations inherited from delegate models this.ensureRelationsInheritedFromDelegate(model, decl); } generateDelegateRelationForBase(model, decl) { if (this.mode !== 'physical') { return; } if (!(0, sdk_2.isDelegateModel)(decl)) { return; } // collect concrete models inheriting this model const concreteModels = (0, ast_utils_1.getConcreteModels)(decl); // generate an optional relation field in delegate base model to each concrete model concreteModels.forEach((concrete) => { const auxName = this.truncate(`${runtime_1.DELEGATE_AUX_RELATION_PREFIX}_${(0, lower_case_first_1.lowerCaseFirst)(concrete.name)}`); model.addField(auxName, new prisma_builder_1.ModelFieldType(concrete.name, false, true)); }); } generateDelegateRelationForConcrete(model, concreteDecl) { if (this.mode !== 'physical') { return; } // generate a relation field for each delegated base model const baseModels = concreteDecl.superTypes .map((t) => t.ref) .filter((t) => !!t) .filter((t) => (0, sdk_2.isDelegateModel)(t)); baseModels.forEach((base) => { const idFields = (0, sdk_1.getIdFields)(base); // add relation fields const relationField = this.truncate(`${runtime_1.DELEGATE_AUX_RELATION_PREFIX}_${(0, lower_case_first_1.lowerCaseFirst)(base.name)}`); model.addField(relationField, base.name, [ new prisma_builder_1.FieldAttribute('@relation', [ new prisma_builder_1.AttributeArg('fields', new prisma_builder_1.AttributeArgValue('Array', idFields.map((idField) => new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference(idField.name))))), new prisma_builder_1.AttributeArg('references', new prisma_builder_1.AttributeArgValue('Array', idFields.map((idField) => new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference(idField.name))))), new prisma_builder_1.AttributeArg('onDelete', new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference('Cascade'))), new prisma_builder_1.AttributeArg('onUpdate', new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference('Cascade'))), ]), ]); }); } expandPolymorphicRelations(model, dataModel) { if (this.mode !== 'logical') { return; } // the logical schema needs to expand relations to the delegate models to concrete ones // for the given model, find relation fields of delegate model type, find all concrete models // of the delegate model and generate an auxiliary opposite relation field to each of them dataModel.fields.forEach((field) => { var _a; // don't process fields inherited from a delegate model if (field.$inheritedFrom && (0, sdk_2.isDelegateModel)(field.$inheritedFrom)) { return; } const fieldType = (_a = field.type.reference) === null || _a === void 0 ? void 0 : _a.ref; if (!(0, ast_1.isDataModel)(fieldType)) { return; } // find concrete models that inherit from this field's model type const concreteModels = dataModel.$container.declarations.filter((d) => (0, ast_1.isDataModel)(d) && isDescendantOf(d, fieldType)); concreteModels.forEach((concrete) => { // aux relation name format: delegate_aux_[model]_[relationField]_[concrete] // e.g., delegate_aux_User_myAsset_Video const auxRelationName = this.truncate(`${runtime_1.DELEGATE_AUX_RELATION_PREFIX}_${dataModel.name}_${field.name}_${concrete.name}`); const auxRelationField = model.addField(auxRelationName, new prisma_builder_1.ModelFieldType(concrete.name, field.type.array, field.type.optional)); const relAttr = (0, sdk_2.getAttribute)(field, '@relation'); let relAttrAdded = false; if (relAttr) { if ((0, sdk_2.getAttributeArg)(relAttr, 'fields')) { // for reach foreign key field pointing to the delegate model, we need to create an aux foreign key // to point to the concrete model const relationFieldPairs = (0, sdk_2.getRelationKeyPairs)(field); const addedFkFields = []; for (const { foreignKey } of relationFieldPairs) { const addedFkField = this.replicateForeignKey(model, dataModel, concrete, foreignKey); addedFkFields.push(addedFkField); } // the `@relation(..., fields: [...])` attribute argument const fieldsArg = new prisma_builder_1.AttributeArgValue('Array', addedFkFields.map((addedFk) => new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference(addedFk.name)))); // the `@relation(..., references: [...])` attribute argument const referencesArg = new prisma_builder_1.AttributeArgValue('Array', relationFieldPairs.map(({ id }) => new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference(id.name)))); const addedRel = new prisma_builder_1.FieldAttribute('@relation', [ // use field name as relation name for disambiguation new prisma_builder_1.AttributeArg(undefined, new prisma_builder_1.AttributeArgValue('String', auxRelationField.name)), new prisma_builder_1.AttributeArg('fields', fieldsArg), new prisma_builder_1.AttributeArg('references', referencesArg), ]); if (this.supportNamedConstraints) { addedRel.args.push( // generate a `map` argument for foreign key constraint disambiguation new prisma_builder_1.AttributeArg('map', new prisma_builder_1.AttributeArgValue('String', `${auxRelationField.name}_fk`))); } auxRelationField.attributes.push(addedRel); relAttrAdded = true; } } if (!relAttrAdded) { auxRelationField.attributes.push(new prisma_builder_1.FieldAttribute('@relation', [ // use field name as relation name for disambiguation new prisma_builder_1.AttributeArg(undefined, new prisma_builder_1.AttributeArgValue('String', auxRelationField.name)), ])); } }); }); } replicateForeignKey(model, delegateModel, concreteModel, origForeignKey) { // aux fk name format: delegate_aux_[model]_[fkField]_[concrete] // e.g., delegate_aux_User_myAssetId_Video // generate a fk field based on the original fk field const addedFkField = this.generateModelField(model, origForeignKey); // `@map` attribute should not be inherited addedFkField.attributes = addedFkField.attributes.filter((attr) => !('name' in attr && attr.name === '@map')); // `@unique` attribute should be recreated with disambiguated name addedFkField.attributes = addedFkField.attributes.filter((attr) => !('name' in attr && attr.name === '@unique')); const uniqueAttr = addedFkField.addAttribute('@unique'); const constraintName = this.truncate(`${delegateModel.name}_${addedFkField.name}_${concreteModel.name}_unique`); uniqueAttr.args.push(new prisma_builder_1.AttributeArg('map', new prisma_builder_1.AttributeArgValue('String', constraintName))); // fix its name const addedFkFieldName = `${delegateModel.name}_${origForeignKey.name}_${concreteModel.name}`; addedFkField.name = this.truncate(`${runtime_1.DELEGATE_AUX_RELATION_PREFIX}_${addedFkFieldName}`); // we also need to go through model-level `@@unique` and replicate those involving fk fields this.replicateForeignKeyModelLevelUnique(model, delegateModel, origForeignKey, addedFkField); return addedFkField; } replicateForeignKeyModelLevelUnique(model, dataModel, origForeignKey, addedFkField) { for (const uniqueAttr of dataModel.attributes.filter((attr) => { var _a; return ((_a = attr.decl.ref) === null || _a === void 0 ? void 0 : _a.name) === '@@unique'; })) { const fields = (0, sdk_2.getAttributeArg)(uniqueAttr, 'fields'); if (fields && (0, ast_1.isArrayExpr)(fields)) { const found = fields.items.find((fieldRef) => (0, ast_1.isReferenceExpr)(fieldRef) && fieldRef.target.ref === origForeignKey); if (found) { // replicate the attribute and replace the field reference with the new FK field const args = []; const fieldNames = []; for (const arg of fields.items) { if (!(0, ast_1.isReferenceExpr)(arg)) { throw new sdk_2.PluginError(_1.name, 'Unexpected field reference in @@unique attribute'); } if (arg.target.ref === origForeignKey) { // replace fieldNames.push(addedFkField.name); args.push(new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference(addedFkField.name))); } else { // copy fieldNames.push(arg.target.$refText); args.push(new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference(arg.target.$refText))); } } const constraintName = this.truncate(`${dataModel.name}_${fieldNames.join('_')}_unique`); model.addAttribute('@@unique', [ new prisma_builder_1.AttributeArg(undefined, new prisma_builder_1.AttributeArgValue('Array', args)), new prisma_builder_1.AttributeArg('map', new prisma_builder_1.AttributeArgValue('String', constraintName)), ]); } } } } truncate(name) { if (name.length <= IDENTIFIER_NAME_MAX_LENGTH) { return name; } const existing = this.shortNameMap.get(name); if (existing) { return existing; } const baseName = name.slice(0, IDENTIFIER_NAME_MAX_LENGTH); let index = 0; let shortName = `${baseName}_${index}`; // eslint-disable-next-line no-constant-condition while (true) { const conflict = Array.from(this.shortNameMap.values()).find((v) => v === shortName); if (!conflict) { this.shortNameMap.set(name, shortName); break; } // try next index index++; shortName = `${baseName}_${index}`; } return shortName; } ensureRelationsInheritedFromDelegate(model, decl) { if (this.mode !== 'logical') { return; } decl.fields.forEach((f) => { var _a, _b; if (!(0, ast_1.isDataModel)((_a = f.type.reference) === null || _a === void 0 ? void 0 : _a.ref)) { // only process relation fields return; } if (!f.$inheritedFrom) { // only process inherited fields return; } // Walk up the inheritance chain to find a field with matching name // which is where this field is inherited from. // // Note that we can't walk all the way up to the $inheritedFrom model // because it may have been eliminated because of being abstract. const baseField = this.findUpMatchingFieldFromDelegate(decl, f); if (!baseField) { // only process fields inherited from delegate models return; } const prismaField = model.fields.find((field) => field.name === f.name); if (!prismaField) { return; } // find the opposite side of the relation const oppositeRelationField = this.getOppositeRelationField(f.type.reference.ref, baseField); if (!oppositeRelationField) { return; } const oppositeRelationAttr = (0, sdk_2.getAttribute)(oppositeRelationField, '@relation'); const fieldType = f.type.reference.ref; // relation name format: delegate_aux_[relationType]_[oppositeRelationField]_[concrete] const relName = this.truncate(`${runtime_1.DELEGATE_AUX_RELATION_PREFIX}_${fieldType.name}_${oppositeRelationField.name}_${decl.name}`); // recreate `@relation` attribute prismaField.attributes = prismaField.attributes.filter((attr) => attr.name !== '@relation'); const relKeyPairs = (0, sdk_2.getRelationKeyPairs)(f); if ( // array relation doesn't need FK f.type.array || // FK field is defined on this side relKeyPairs.length > 0 || // opposite relation already has FK, we don't need to generate on this side (oppositeRelationAttr && (0, sdk_2.getAttributeArg)(oppositeRelationAttr, 'fields'))) { const relationArgs = [new prisma_builder_1.AttributeArg(undefined, new prisma_builder_1.AttributeArgValue('String', relName))]; const isSelfRelation = f.type.reference.ref === ((_b = f.$inheritedFrom) !== null && _b !== void 0 ? _b : f.$container); if (relKeyPairs.length > 0 && !isSelfRelation) { // carry over "fields" and "references" args if not a self-relation relationArgs.push(new prisma_builder_1.AttributeArg('fields', new prisma_builder_1.AttributeArgValue('Array', relKeyPairs.map((pair) => new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference(pair.foreignKey.name)))))); relationArgs.push(new prisma_builder_1.AttributeArg('references', new prisma_builder_1.AttributeArgValue('Array', relKeyPairs.map((pair) => new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference(pair.id.name)))))); } prismaField.attributes.push(new prisma_builder_1.FieldAttribute('@relation', relationArgs)); } else { // generate FK field const oppositeModelIds = (0, sdk_1.getIdFields)(oppositeRelationField.$container); const fkFieldNames = []; oppositeModelIds.forEach((idField) => { const fkFieldName = this.truncate(`${runtime_1.DELEGATE_AUX_RELATION_PREFIX}_${f.name}_${idField.name}`); model.addField(fkFieldName, new prisma_builder_1.ModelFieldType(idField.type.type, false, f.type.optional), [ // one-to-one relation requires FK field to be unique, we're just including it // in all cases since it doesn't hurt new prisma_builder_1.FieldAttribute('@unique'), ]); fkFieldNames.push(fkFieldName); }); prismaField.attributes.push(new prisma_builder_1.FieldAttribute('@relation', [ new prisma_builder_1.AttributeArg(undefined, new prisma_builder_1.AttributeArgValue('String', relName)), new prisma_builder_1.AttributeArg('fields', new prisma_builder_1.AttributeArgValue('Array', fkFieldNames.map((fk) => new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference(fk))))), new prisma_builder_1.AttributeArg('references', new prisma_builder_1.AttributeArgValue('Array', oppositeModelIds.map((idField) => new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference(idField.name))))), ])); } }); } findUpMatchingFieldFromDelegate(start, target) { for (const base of start.superTypes) { if ((0, ast_1.isDataModel)(base.ref)) { if ((0, sdk_2.isDelegateModel)(base.ref)) { const field = base.ref.fields.find((f) => f.name === target.name); if (field) { if (!field.$inheritedFrom || !(0, sdk_2.isDelegateModel)(field.$inheritedFrom)) { // if this field is not inherited from an upper delegate, we're done return field; } } } const upper = this.findUpMatchingFieldFromDelegate(base.ref, target); if (upper) { return upper; } } } return undefined; } getOppositeRelationField(oppositeModel, relationField) { const relName = this.getRelationName(relationField); const matches = oppositeModel.fields.filter((f) => { var _a; return ((_a = f.type.reference) === null || _a === void 0 ? void 0 : _a.ref) === relationField.$container && this.getRelationName(f) === relName; }); if (matches.length === 0) { return undefined; } else if (matches.length === 1) { return matches[0]; } else { // if there are multiple matches, prefer to use the one with the same field name, // this can happen with self-relations const withNameMatch = matches.find((f) => f.name === relationField.name); if (withNameMatch) { return withNameMatch; } else { return matches[0]; } } } getRelationName(field) { const relAttr = (0, sdk_2.getAttribute)(field, '@relation'); if (!relAttr) { return undefined; } return (0, sdk_2.getAttributeArgLiteral)(relAttr, 'name'); } get supportNamedConstraints() { const ds = this.zmodel.declarations.find(ast_1.isDataSource); if (!ds) { return false; } const provider = ds.fields.find((f) => f.name === 'provider'); if (!provider) { return false; } const value = (0, utils_1.getStringLiteral)(provider.value); return value && PROVIDERS_SUPPORTING_NAMED_CONSTRAINTS.includes(value); } isPrismaAttribute(attr) { if (!attr.decl.ref) { return false; } const attrDecl = (0, sdk_2.resolved)(attr.decl); return (!!attrDecl.attributes.find((a) => { var _a; return ((_a = a.decl.ref) === null || _a === void 0 ? void 0 : _a.name) === '@@@prisma'; }) || // the special pass-through attribute attrDecl.name === MODEL_PASSTHROUGH_ATTR || attrDecl.name === FIELD_PASSTHROUGH_ATTR); } getUnsupportedFieldType(fieldType) { if (fieldType.unsupported) { const value = (0, utils_1.getStringLiteral)(fieldType.unsupported.value); if (value) { return `Unsupported("${value}")`; } else { return undefined; } } else { return undefined; } } generateModelField(model, field, addToFront = false) { var _a, _b; let fieldType; if (field.type.type) { // intrinsic type fieldType = field.type.type; } else if ((_a = field.type.reference) === null || _a === void 0 ? void 0 : _a.ref) { // model, enum, or type-def if ((0, ast_1.isTypeDef)(field.type.reference.ref)) { this.ensureSupportingTypeDefFields(this.zmodel); fieldType = 'Json'; } else { fieldType = field.type.reference.ref.name; } } else { // Unsupported type const unsupported = this.getUnsupportedFieldType(field.type); if (unsupported) { fieldType = unsupported; } } if (!fieldType) { throw new sdk_2.PluginError(_1.name, `Field type is not resolved: ${field.$container.name}.${field.name}`); } const isArray = // typed-JSON fields should be translated to scalar Json type (0, ast_1.isTypeDef)((_b = field.type.reference) === null || _b === void 0 ? void 0 : _b.ref) ? false : field.type.array; const type = new prisma_builder_1.ModelFieldType(fieldType, isArray, field.type.optional); const attributes = field.attributes .filter((attr) => this.isPrismaAttribute(attr)) // `@default` with `auth()` is handled outside Prisma .filter((attr) => !(0, enhancer_utils_1.isDefaultWithAuth)(attr)) .filter((attr) => // when building physical schema, exclude `@default` for id fields inherited from delegate base !(this.mode === 'physical' && (0, sdk_2.isIdField)(field) && this.isInheritedFromDelegate(field) && attr.decl.$refText === '@default')) .map((attr) => this.makeFieldAttribute(attr)); // user defined comments pass-through const docs = [...field.comments, ...this.getCustomAttributesAsComments(field)]; const result = model.addField(field.name, type, attributes, docs, addToFront); if (this.mode === 'logical') { if (field.attributes.some((attr) => (0, enhancer_utils_1.isDefaultWithAuth)(attr))) { // field has `@default` with `auth()`, turn it into a dummy default value, and the // real default value setting is handled outside Prisma this.setDummyDefault(result, field); } } return result; } setDummyDefault(result, field) { const dummyDefaultValue = (0, ts_pattern_1.match)(field.type.type) .with('String', () => new prisma_builder_1.AttributeArgValue('String', '')) .with(ts_pattern_1.P.union('Int', 'BigInt', 'Float', 'Decimal'), () => new prisma_builder_1.AttributeArgValue('Number', '0')) .with('Boolean', () => new prisma_builder_1.AttributeArgValue('Boolean', false)) .with('DateTime', () => new prisma_builder_1.AttributeArgValue('FunctionCall', new prisma_builder_1.FunctionCall('now'))) .with('Json', () => new prisma_builder_1.AttributeArgValue('String', '{}')) .with('Bytes', () => new prisma_builder_1.AttributeArgValue('String', '')) .otherwise(() => { throw new sdk_2.PluginError(_1.name, `Unsupported field type with default value: ${field.type.type}`); }); result.attributes.push(new prisma_builder_1.FieldAttribute('@default', [new prisma_builder_1.AttributeArg(undefined, dummyDefaultValue)])); } ensureSupportingTypeDefFields(zmodel) { const dsProvider = (0, sdk_2.getDataSourceProvider)(zmodel); if (dsProvider && !PROVIDERS_SUPPORTING_TYPEDEF_FIELDS.includes(dsProvider)) { throw new sdk_2.PluginError(_1.name, `Datasource provider "${dsProvider}" does not support "@json" fields`); } } isInheritedFromDelegate(field) { return field.$inheritedFrom && (0, sdk_2.isDelegateModel)(field.$inheritedFrom); } makeFieldAttribute(attr) { const attrName = (0, sdk_2.resolved)(attr.decl).name; if (attrName === FIELD_PASSTHROUGH_ATTR) { const text = (0, sdk_2.getLiteral)(attr.args[0].value); if (text) { return new prisma_builder_1.PassThroughAttribute(text); } else { throw new sdk_2.PluginError(_1.name, `Invalid arguments for ${FIELD_PASSTHROUGH_ATTR} attribute`); } } else { return new prisma_builder_1.FieldAttribute(attrName, attr.args.map((arg) => this.makeAttributeArg(arg))); } } makeAttributeArg(arg) { return new prisma_builder_1.AttributeArg(arg.name, this.makeAttributeArgValue(arg.value)); } makeAttributeArgValue(node) { if ((0, ast_1.isLiteralExpr)(node)) { const argType = (0, ts_pattern_1.match)(node.$type) .with(ast_1.StringLiteral, () => 'String') .with(ast_1.NumberLiteral, () => 'Number') .with(ast_1.BooleanLiteral, () => 'Boolean') .exhaustive(); return new prisma_builder_1.AttributeArgValue(argType, node.value); } else if ((0, ast_1.isArrayExpr)(node)) { return new prisma_builder_1.AttributeArgValue('Array', new Array(...node.items.map((item) => this.makeAttributeArgValue(item)))); } else if ((0, ast_1.isReferenceExpr)(node)) { return new prisma_builder_1.AttributeArgValue('FieldReference', new prisma_builder_1.FieldReference((0, sdk_2.resolved)(node.target).name, node.args.map((arg) => new prisma_builder_1.FieldReferenceArg(arg.name, this.exprToText(arg.value))))); } else if ((0, ast_1.isInvocationExpr)(node)) { // invocation return new prisma_builder_1.AttributeArgValue('FunctionCall', this.makeFunctionCall(node)); } else { throw new sdk_2.PluginError(_1.name, `Unsupported attribute argument expression type: ${node.$type}`); } } makeFunctionCall(node) { return new prisma_builder_1.FunctionCall((0, sdk_2.resolved)(node.function).name, node.args.map((arg) => { const val = (0, ts_pattern_1.match)(arg.value) .when(ast_1.isStringLiteral, (v) => `"${v.value}"`) .when(ast_1.isLiteralExpr, (v) => v.value.toString()) .when(ast_1.isNullExpr, () => 'null') .otherwise(() => { throw new sdk_2.PluginError(_1.name, 'Function call argument must be literal or null'); }); return new prisma_builder_1.FunctionCallArg(val); })); } generateContainerAttribute(container, attr) { const attrName = (0, sdk_2.resolved)(attr.decl).name; if (attrName === MODEL_PASSTHROUGH_ATTR) { const text = (0, sdk_2.getLiteral)(attr.args[0].value); if (text) { container.attributes.push(new prisma_builder_1.PassThroughAttribute(text)); } } else { container.attributes.push(new prisma_builder_1.ContainerAttribute(attrName, attr.args.map((arg) => this.makeAttributeArg(arg)))); } } generateEnum(prisma, decl) { const _enum = prisma.addEnum(decl.name); for (const field of decl.fields) { this.generateEnumField(_enum, field); } for (const attr of decl.attributes.filter((attr) => this.isPrismaAttribute(attr))) { this.generateContainerAttribute(_enum, attr); } // user defined comments pass-through decl.comments.forEach((c) => _enum.addComment(c)); this.getCustomAttributesAsComments(decl).forEach((c) => _enum.addComment(c)); } generateEnumField(_enum, field) { const attributes = field.attributes .filter((attr) => this.isPrismaAttribute(attr)) .map((attr) => this.makeFieldAttribute(attr)); const docs = [...field.comments, ...this.getCustomAttributesAsComments(field)]; _enum.addField(field.name, attributes, docs); } getCustomAttributesAsComments(decl) { if (!this.customAttributesAsComments) { return []; } else { return decl.attributes .filter((attr) => attr.decl.ref && !this.isPrismaAttribute(attr)) .map((attr) => `/// ${this.zModelGenerator.generate(attr)}`); } } } exports.PrismaSchemaGenerator = PrismaSchemaGenerator; function isDescendantOf(model, superModel) { return model.superTypes.some((s) => s.ref === superModel || isDescendantOf(s.ref, superModel)); } //# sourceMappingURL=schema-generator.js.map