UNPKG

@hexys/prismix

Version:

Create multiple Prisma schema files with shared model relations.

397 lines (393 loc) 16.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // lib/prismix.ts var prismix_exports = {}; __export(prismix_exports, { prismix: () => prismix }); module.exports = __toCommonJS(prismix_exports); var import_fs = __toESM(require("fs")); var import_util = require("util"); var import_path = __toESM(require("path")); var import_internals = require("@prisma/internals"); // lib/utils.ts function valueIs(value, types) { return types.map((type) => type.name.toLowerCase() == typeof value).includes(true); } // lib/deserializer.ts var renderAttribute = (field) => { const { kind, type } = field; return { default: (value) => { if (value == null || value == void 0) return ""; if (kind === "scalar" && type !== "BigInt" && typeof value == "string") value = `"${value}"`; if (valueIs(value, [Number, String, Boolean]) || kind === "enum") return `@default(${value})`; if (typeof value === "object") { if (value.name === "dbgenerated") return `@default(${value.name}("${value.args}"))`; return `@default(${value.name}(${value.args}))`; } throw new Error(`Prismix: Unsupported field attribute ${value}`); }, isId: (value) => value ? "@id" : "", isUnique: (value) => value ? "@unique" : "", isUpdatedAt: (value) => value ? "@updatedAt" : "", columnName: (value) => value ? `@map("${value}")` : "", dbType: (value) => value != null ? value : "" }; }; function renderAttributes(field) { const { relationFromFields, relationToFields, relationName, kind, relationOnDelete, relationOnUpdate } = field; if (kind == "scalar" || kind == "enum") { return `${Object.keys(field).map( (property) => renderAttribute(field)[property] && renderAttribute(field)[property](field[property]) ).filter((x) => !!x).join(" ")}`; } if (relationFromFields && kind === "object") { return relationFromFields.length > 0 ? `@relation(name: "${relationName}", fields: [${relationFromFields}], references: [${relationToFields}]${relationOnDelete ? `, onDelete: ${relationOnDelete}` : ""}${relationOnUpdate ? `, onUpdate: ${relationOnUpdate}` : ""})` : `@relation(name: "${relationName}")`; } return ""; } function renderDocumentation(documentation, tab) { if (!documentation) return ""; const documentationLines = documentation.split("\n"); return documentationLines.length == 1 ? `/// ${documentationLines[0]} ${tab ? " " : ""}` : documentationLines.map((text, idx) => idx == 0 ? `/// ${text}` : ` /// ${text}`).join("\n") + (tab ? "\n " : "\n"); } function renderModelFields(fields) { return fields.map((field) => { const { name, kind, type, documentation, isRequired, isList } = field; if (kind == "scalar") return `${renderDocumentation(documentation, true)}${name} ${type}${isList ? "[]" : isRequired ? "" : "?"} ${renderAttributes(field)}`; if (kind == "object" || kind == "enum") return `${renderDocumentation(documentation, true)}${name} ${type}${isList ? "[]" : isRequired ? "" : "?"} ${renderAttributes(field)}`; throw new Error(`Prismix: Unsupported field kind "${kind}"`); }); } function renderIdFieldsOrPrimaryKey(idFields) { if (!idFields) return ""; return idFields.length > 0 ? `@@id([${idFields.join(", ")}])` : ""; } function renderUniqueIndexes(uniqueIndexes) { return uniqueIndexes.length > 0 ? uniqueIndexes.map( ({ name, fields }) => `@@unique([${fields.join(", ")}]${name ? `, name: "${name}"` : ""})` ) : []; } function renderDbName(dbName) { return dbName ? `@@map("${dbName}")` : ""; } function renderUrl(envValue) { const value = envValue.fromEnvVar ? `env("${envValue.fromEnvVar}")` : `"${envValue.value}"`; return `url = ${value}`; } function renderProvider(provider) { return `provider = "${provider}"`; } function renderOutput(path2) { return path2 ? `output = "${path2}"` : ""; } function renderPreviewFeatures(previewFeatures) { return previewFeatures.length ? `previewFeatures = ${JSON.stringify(previewFeatures)}` : ""; } function renderBlock(type, name, things, documentation) { return `${renderDocumentation(documentation)}${type} ${name} { ${things.filter((thing) => thing.length > 1).map((thing) => ` ${thing}`).join("\n")} }`; } function deserializeModel(model) { const { name, fields, dbName, idFields, primaryKey, doubleAtIndexes, uniqueIndexes, documentation } = model; return renderBlock( "model", name, [ ...renderModelFields(fields), ...renderUniqueIndexes(uniqueIndexes), ...doubleAtIndexes != null ? doubleAtIndexes : [], renderDbName(dbName), renderIdFieldsOrPrimaryKey(idFields || (primaryKey == null ? void 0 : primaryKey.fields)) ], documentation ); } function deserializeDatasource(datasource) { const { activeProvider: provider, name, url } = datasource; return renderBlock("datasource", name, [renderProvider(provider), renderUrl(url)]); } function deserializeGenerator(generator) { const { binaryTargets, name, output, provider, previewFeatures } = generator; return renderBlock("generator", name, [ renderProvider(provider.value), renderOutput((output == null ? void 0 : output.value) || null), renderPreviewFeatures(previewFeatures) ]); } function deserializeEnum({ name, values, dbName, documentation }) { const outputValues = values.map(({ name: name2, dbName: dbName2 }) => { let result = name2; if (name2 !== dbName2 && dbName2) result += `@map("${dbName2}")`; return result; }); return renderBlock("enum", name, [...outputValues, renderDbName(dbName || null)], documentation); } function deserializeModels(models) { return __async(this, null, function* () { return models.map((model) => deserializeModel(model)).join("\n"); }); } function deserializeDatasources(datasources) { return __async(this, null, function* () { return datasources.map((datasource) => deserializeDatasource(datasource)).join("\n"); }); } function deserializeGenerators(generators) { return __async(this, null, function* () { return generators.map((generator) => deserializeGenerator(generator)).join("\n"); }); } function deserializeEnums(enums) { return __async(this, null, function* () { return Array.from(new Set(enums.map((each) => deserializeEnum(each)))).join("\n"); }); } // lib/prismix.ts var import_glob = __toESM(require("glob")); var readFile = (0, import_util.promisify)(import_fs.default.readFile); var writeFile = (0, import_util.promisify)(import_fs.default.writeFile); function getSchema(schemaPath) { return __async(this, null, function* () { try { const schema = yield readFile(import_path.default.join(process.cwd(), schemaPath), { encoding: "utf-8" }); const dmmf = yield (0, import_internals.getDMMF)({ datamodel: schema }); const customAttributes = getCustomAttributes(schema); const models = dmmf.datamodel.models.map((model) => { var _a; return __spreadProps(__spreadValues({}, model), { doubleAtIndexes: (_a = customAttributes[model.name]) == null ? void 0 : _a.doubleAtIndexes, fields: model.fields.map( (field) => { var _a2, _b; const attributes = (_b = (_a2 = customAttributes[model.name]) == null ? void 0 : _a2.fields[field.name]) != null ? _b : {}; return __spreadProps(__spreadValues({}, field), { columnName: attributes.columnName, dbType: attributes.dbType, relationOnUpdate: attributes.relationOnUpdate }); } ) }); }); const config = yield (0, import_internals.getConfig)({ datamodel: schema }); return { models, enums: dmmf.datamodel.enums, datasources: config.datasources, generators: config.generators }; } catch (e) { console.error( `Prismix failed to parse schema located at "${schemaPath}". Did you attempt to reference to a model without creating an alias? Remember you must define a "blank" alias model with only the "@id" field in your extended schemas otherwise we can't parse your schema.`, e ); } }); } function mixModels(inputModels) { var _a, _b, _c, _d, _e; const models = {}; for (const newModel of inputModels) { const existingModel = models[newModel.name]; if (existingModel) { const existingFieldNames = existingModel.fields.map((f) => f.name); for (const newField of newModel.fields) { if (existingFieldNames.includes(newField.name)) { const existingFieldIndex = existingFieldNames.indexOf(newField.name); const existingField = existingModel.fields[existingFieldIndex]; if (!newField.columnName && existingField.columnName) { newField.columnName = existingField.columnName; } if (!newField.hasDefaultValue && existingField.hasDefaultValue) { newField.hasDefaultValue = true; newField.default = existingField.default; } existingModel.fields[existingFieldIndex] = newField; } else { existingModel.fields.push(newField); } } if (!existingModel.dbName && newModel.dbName) { existingModel.dbName = newModel.dbName; } if ((_a = newModel.doubleAtIndexes) == null ? void 0 : _a.length) { existingModel.doubleAtIndexes = [ ...(_b = existingModel.doubleAtIndexes) != null ? _b : [], ...newModel.doubleAtIndexes ]; } if ((_c = newModel.uniqueIndexes) == null ? void 0 : _c.length) { existingModel.uniqueIndexes = [ ...(_d = existingModel.uniqueIndexes) != null ? _d : [], ...newModel.uniqueIndexes ]; existingModel.uniqueFields = [ ...(_e = existingModel.uniqueFields) != null ? _e : [], ...newModel.uniqueFields ]; } } else { models[newModel.name] = newModel; } } return Object.values(models); } function getCustomAttributes(datamodel) { const modelChunks = datamodel.split("\n}"); return modelChunks.reduce( (modelDefinitions, modelChunk) => { var _a; let pieces = modelChunk.split("\n").filter((chunk) => chunk.trim().length); const modelName = (_a = pieces.find((name) => name.match(/model (.*) {/))) == null ? void 0 : _a.split(" ")[1]; if (!modelName) return modelDefinitions; const mapRegex = new RegExp(new RegExp('[^@]@map\\("(?<name>.*)"\\)')); const dbRegex = new RegExp(new RegExp("(?<type>@db\\.(.*))")); const relationOnUpdateRegex = new RegExp( new RegExp("onUpdate: (?<op>Cascade|NoAction|Restrict|SetDefault|SetNull)") ); const doubleAtIndexRegex = new RegExp(new RegExp("(?<index>@@index\\(.*\\))")); const doubleAtIndexes = pieces.reduce((ac, field) => { var _a2, _b; const item = (_b = (_a2 = field.match(doubleAtIndexRegex)) == null ? void 0 : _a2.groups) == null ? void 0 : _b.index; return item ? [...ac, item] : ac; }, []).filter((f) => f); const fieldsWithCustomAttributes = pieces.map((field) => { var _a2, _b, _c, _d, _e, _f; const columnName = (_b = (_a2 = field.match(mapRegex)) == null ? void 0 : _a2.groups) == null ? void 0 : _b.name; const dbType = (_d = (_c = field.match(dbRegex)) == null ? void 0 : _c.groups) == null ? void 0 : _d.type; const relationOnUpdate = (_f = (_e = field.match(relationOnUpdateRegex)) == null ? void 0 : _e.groups) == null ? void 0 : _f.op; return [field.trim().split(" ")[0], { columnName, dbType, relationOnUpdate }]; }).filter((f) => { var _a2, _b, _c; return ((_a2 = f[1]) == null ? void 0 : _a2.columnName) || ((_b = f[1]) == null ? void 0 : _b.dbType) || ((_c = f[1]) == null ? void 0 : _c.relationOnUpdate); }); return __spreadProps(__spreadValues({}, modelDefinitions), { [modelName]: { fields: Object.fromEntries(fieldsWithCustomAttributes), doubleAtIndexes } }); }, {} ); } function prismix(options) { return __async(this, null, function* () { for (const mixer of options.mixers) { const schemasToMix = []; for (const input of mixer.input) { for (const file of import_glob.default.sync(input)) { const parsedSchema = yield getSchema(file); if (parsedSchema) schemasToMix.push(parsedSchema); } } let models = []; for (const schema of schemasToMix) models = [...models, ...schema.models]; models = mixModels(models); let enums = []; schemasToMix.forEach((schema) => !!schema.enums && (enums = [...enums, ...schema.enums])); let datasources = []; schemasToMix.forEach( (schema) => schema.datasources.length > 0 && schema.datasources.filter((d) => d.url.value).length > 0 && (datasources = schema.datasources) ); let generators = []; schemasToMix.forEach( (schema) => schema.generators.length > 0 && (generators = schema.generators) ); let outputSchema = [ "// *** GENERATED BY PRISMIX :: DO NOT EDIT ***", yield deserializeDatasources(datasources), yield deserializeGenerators(generators), yield deserializeModels(models), yield deserializeEnums(enums) ].filter((e) => e).join("\n"); yield writeFile(import_path.default.join(process.cwd(), mixer.output), outputSchema); } }); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { prismix }); //# sourceMappingURL=prismix.js.map