zenstack
Version:
FullStack enhancement for Prisma ORM: seamless integration from database to UI
739 lines (738 loc) • 39.7 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.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
;