UNPKG

prisma-util

Version:

Prisma Util is an easy to use tool that merges multiple Prisma schema files, allows extending of models, resolves naming conflicts both manually and automatically and provides easy access to Prisma commands and timing reports. It's mostly a plug-and-play

892 lines (871 loc) 120 kB
import chalk from "chalk"; import createSpinner from "ora"; import { warn, error, experimental, log, conflict } from "./logger.js"; import { successTag, prismaCLITag } from "./messages.js"; import { flatten, getSchema, endsWithAny, writeTempSchema, runPrismaCommand, convertPathToLocal, getConfigurationPath, getFiles } from "./utils.js"; import pluralize from "pluralize"; import * as fs from 'fs/promises'; import path from "path"; import { EditStrategy, GenerationToolchainInstance, IGNORE_SEARCH_STEP } from "./lib/toolchain/generation.js"; import { MiddlewareToolchainInstance } from "./lib/toolchain/middleware.js"; import json5 from "json5"; import { ExtensionsToolchainInstance } from "./lib/toolchain/extensions.js"; import { Constraints } from "../schema-creator/index.js"; /** Current Experimental features available with Prisma Util. */ export const OptionalFeaturesArray = ["crossFileRelations", "codeSchemas", "pgtrgm", "ignoreEmptyConditions", "customAttributeFunctions", "environmentLock", "virtualEnvironment", "middlewareContext", "deprecatedTag", "staticTake", "prismaGenerators", "refinedTypes", "enhancedIntrospection"]; /** * Allow dynamic configuration generation. */ const configurationDocumentation = { enhancedIntrospection: [{ name: "introspection", required: false, type: "{modelPatterns: {$static?: {[table: string]: string | ((model: IntrospectionModel) => Promise<IntrospectionModel>)}, $regex?: {[tableRegex: string]: ((model: IntrospectionModel, match: string, ...groups: string[]) => Promise<IntrospectionModel>)}}}", description: [ "Enforce conditions on your model names", "To find out more about configuring introspection, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation/configuration-reference/introspection this} documentation section." ] }], refinedTypes: [{ name: "fieldTypes", required: false, type: "{[fileModelColumn: string]: string}", description: [ "Refine the types of model fields.", "To find out more about configuring refined types, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation/configuration-reference/field-types this} documentation section." ] }], prismaGenerators: [{ name: "generators", required: false, type: "{include: FileGeneratorConfig[], run: {[key: string]: string | (() => Promise<boolean>) | boolean}}", description: [ "Include generators that will be ran by Prisma.", "To find out more about configuring Prisma generators, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation/configuration-reference/generators this} documentation section." ] }], staticTake: [{ name: "take", required: false, type: "{$global: number, [model: string]: number} | {[model: string]: number}", description: [ "Set the default take amount for each model.", "To find out more about configuring take amounts, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation/configuration-reference/take this} documentation section." ] }], deprecatedTag: [{ name: "deprecated", required: false, type: "{[fileModelColumn: string]: string}", description: [ "Mark a field as deprecated. The value of this key-value pair represents the message shown in the client.", "To find out more about configuring deprecated fields, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation/configuration-reference/deprecated this} documentation section." ] }], virtualEnvironment: [{ name: "environment", required: false, type: "() => Promise<void>", description: [ "Configure the environment variables for Prisma.", "To find out more about configuring the virtual environment, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation/configuration-reference/environment this} documentation section." ] }], environmentLock: [], crossFileRelations: [{ name: "relations", required: false, type: "{[fileModelColumn: string]: string}", description: [ "Indicate a cross-file relation between the column defined by the key of this key-value entry and the model defined by the value.", "To find out more about configuring cross-file relations, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation/configuration-reference/relations this} documentation section." ] }], codeSchemas: [{ name: "codeGenerators", required: false, type: "Promise<string>[]", description: [ "Code generators allow you to create models at migration-time, to allow dynamic computation.", "To find out more about configuring code generators, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation/configuration-reference/code-generators this} documentation section." ] }], pgtrgm: [{ name: "ftsIndexes", required: true, type: "{[fileModel: string]: {type: \"Gin\" | \"Gist\", indexes: { language: string, field: string, weight: string }[]}}", description: [ "Create FTS Indexes for your models for a faster experience.", "To find out more about configuring full-text seach indexes, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation/configuration-reference/fts-indexes this} documentation section.", "@deprecated This feature has been deprecated because it has been implemented in the official Prisma Client." ] }, { name: "schema", required: true, type: "string", description: [ "The schema of this database.", "To find out more about configuring full-text seach indexes, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation/configuration-reference/schema this} documentation section.", "@deprecated This feature has been deprecated because it has been implemented in the official Prisma Client." ] }], ignoreEmptyConditions: [], customAttributeFunctions: [{ name: "defaultFunctions", required: false, type: `{[fileModelColumn: string]: (dmmf: import("@prisma/client/runtime/index").DMMF.Field) => any}`, description: [ "Indicate which default function should be used for the column defined by the key of this key-value entry.", "To find out more about configuring default functions, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation/configuration-reference/default-functions this} documentation section." ] }], middlewareContext: [] }; /** Small parser utility to resolve conflicts. */ export default class PrismaParser { constructor(config, configPath) { this.loaded = false; this.appliedIntrospection = false; this.config = config; if (this.config.optionalFeatures) { for (const feature of this.config.optionalFeatures) this.config[feature] = true; } if (this.config.virtualEnvironment && !this.config.environment) this.config.environment = async () => { }; this.configPath = configPath; this.models = {}; this.modelColumns = {}; this.solutions = []; this.remapper = this.config.relations ? this.config.relations : {}; this.enums = {}; this.files = []; this.generators = {}; } /**Get all the columns defined for the models loaded. */ getModelColumns() { return this.modelColumns; } async loadEnvironment() { if (this.config.environmentLock) { if (this.config.virtualEnvironment && this.config.environment) await this.config.environment(); else (await import("dotenv")).config(); } } getEnv(env) { if (typeof env == "boolean") return env; if (typeof env == "string") return env == "true"; return !!env; } /** Load .prisma files from config and parse models.*/ async load() { if (!this.config.baseSchema) { error("Base schema not found."); process.exit(1); } if (!this.config.toolchain) { error("Project Toolchain configuration not found."); process.exit(1); } if (!this.config.take) { this.config.take = {}; } if (!this.config.generators) { this.config.generators = { include: [], run: {} }; } if (!this.config.fieldTypes) { this.config.fieldTypes = {}; } if (!this.config.introspection) { this.config.introspection = { modelPatterns: {} }; } // Support for @prisma-util/schema-creator magic if (typeof this.config.baseSchema == "function") this.config.baseSchema = this.config.baseSchema(); const includeFilesStrings = this.config.includeFiles.map(item => { if (typeof item == "function") return item(); return item; }); this.config.includeFiles = includeFilesStrings; this.files = [...includeFilesStrings, this.config.baseSchema]; if (!this.config.excludeModels) { this.config.excludeModels = []; } if (!this.config.includeFiles || this.config.includeFiles.length == 0) { warn("You didn't specify any included files in your config!\n", "\n"); this.config.includeFiles = []; } if (this.config.pgtrgm) { if (!this.config.schema) { error("You didn't set a schema in the configuration file.", "\n"); process.exit(1); } } if (!this.config.extended) this.config.extended = {}; if (this.config.toolchain.resolve.types) this.config.toolchain.resolve.types = await getConfigurationPath(this.config.toolchain.resolve.types); const includeFiles = [this.config.baseSchema, ...this.config.includeFiles].map((val) => { return { type: "FILE", data: val }; }); if (this.config.codeSchemas && this.config.codeGenerators) { for (let i = 0; i < this.config.codeGenerators.length; i++) { const generator = this.config.codeGenerators[i]; let spinner = createSpinner({ text: `${chalk.gray("Running code-schema generator ")}${chalk.blue(`#${i + 1}`)}${chalk.gray("...")}`, prefixText: prismaCLITag }).start(); includeFiles.push({ type: "SCHEMA", data: await generator, additionalName: `#${i + 1}` }); spinner.stopAndPersist({ text: `${chalk.gray("Successfully generated schema from generator ")}${chalk.blue(`#${i + 1}`)}${chalk.gray(".")}`, prefixText: '', symbol: successTag }); } } for (const file of includeFiles) { const fileData = {}; if (typeof file.data == "function") file.data = await file.data(); const name = file.type == "SCHEMA" ? `codeSchemas${file.additionalName}` : file.data; let spinner = createSpinner({ text: `${chalk.gray("Loading schema file from ")}${chalk.blue(name)}${chalk.gray("...")}`, prefixText: prismaCLITag }).start(); const text = file.type == "FILE" ? await getSchema(file.data) : file.data; spinner.stopAndPersist({ text: `${chalk.gray("Successfully loaded schema from ")}${chalk.blue(name)}${chalk.gray(".")}`, prefixText: '', symbol: successTag }); // This is the base schema, parse it to get the generator and datasource. if (file.data == this.config.baseSchema) { spinner = createSpinner({ text: `${chalk.gray("Checking generator and datasource from ")}${chalk.blue(name)}${chalk.gray("...")}`, prefixText: prismaCLITag }).start(); const generatorRegex = /^([gG][eE][nN][eE][rR][aA][tT][oO][rR]\s*([^\s]+)(\s*\{((?=.*\n)[^}]+)\}))/gms; const dataSourceRegex = /^([dD][aA][tT][aA][sS][oO][uU][rR][cC][eE]\s*([^\s]+)(\s*\{((?=.*\n)[^}]+)\}))/gms; const generator = generatorRegex.exec(text); const datasource = dataSourceRegex.exec(text); if (!generator || !datasource) { if (!generator) error("The base schema doesn't contain a generator!"); if (!datasource) error("The base schema doesn't contain a datasource!"); process.exit(1); } spinner.stopAndPersist({ text: `${chalk.gray("Successfully loaded generators and datasource from ")}${chalk.blue(name)}${chalk.gray(".")}`, prefixText: '', symbol: successTag }); this.generator = generator[1]; this.datasource = datasource[1]; } spinner = createSpinner({ text: `${chalk.gray("Adding models from ")}${chalk.blue(name)}${chalk.gray("...")}`, prefixText: prismaCLITag }).start(); const regex = /^([mM][oO][dD][eE][lL]\s*([^\s]+)(\s*\{((?=.*\n)[^}]+)\}))/gms; const enumRegex = /^([eE][nN][uU][mM]\s*([^\s]+)(\s*\{((?=.*\n)[^}]+)\}))/gms; for (let enumsForFile; enumsForFile = enumRegex.exec(text);) { const enumName = enumsForFile[2]; const enumBody = enumsForFile[4]; if (!this.config.excludeModels.includes(`${name}:${enumName}`)) { const enumElements = enumBody.split("\n").filter(line => line.trim()).map(line => line.trim()); this.enums[`${name}:${enumName}`] = { name: enumName, values: enumElements }; fileData[enumName] = enumElements.join("\n"); } } for (let modelsForFile; modelsForFile = regex.exec(text);) { const modelFull = modelsForFile[1]; const modelName = modelsForFile[2]; const modelBody = modelsForFile[4]; // If the model isn't excluded, grab the columns and add it to the models if (!this.config.excludeModels.includes(`${name}:${modelName}`)) { fileData[modelName] = modelFull; const columns = modelBody.split(/[\r\n]+/).filter(line => line.trim()).map(line => line.trim()); this.modelColumns[`${name}:${modelName}`] = columns.map(column => { const [name, type, ...constraints] = column.split(" "); return { name, type, constraints }; }); } } // Add the new models to this specific file this.models[name] = fileData; spinner.stopAndPersist({ text: `${chalk.gray("Successfully added models from ")}${chalk.blue(name)}${chalk.gray(".")}`, symbol: successTag }); } if (this.config.prismaGenerators) { for (let generatorFile of this.config.generators.include) { if (typeof generatorFile == "function") generatorFile = generatorFile(); const generatorRegex = /generator\s*(\w+)\s*{(\s*\w+\s*=\s.*?\s*)}/gims; const content = await getSchema(generatorFile); for (let generatorsForFile; generatorsForFile = generatorRegex.exec(content);) { const match = generatorsForFile[0]; let generatorName = generatorsForFile[1]; const initialGeneratorName = generatorsForFile[1]; const generatorBody = generatorsForFile[2]; let count = 1; while (this.generators[generatorName]) { generatorName = `${initialGeneratorName}${count}`; count++; } if (count > 1) { conflict(`Generator ${chalk.bold(`${generatorFile}:${initialGeneratorName}`)} has been renamed to ${chalk.bold(generatorName)}.`); } const initialCondition = this.config.generators.run[`${generatorFile}:${initialGeneratorName}`]; let run = true; if (initialCondition) { const condition = typeof initialCondition == "function" ? await initialCondition() : initialCondition; if (condition) run = typeof condition == "boolean" ? condition : this.getEnv(process.env[condition]); } this.generators[generatorName] = { code: `generator ${generatorName} {${generatorBody}}`, run: run, meta: { file: generatorFile, name: initialGeneratorName } }; } } } if (this.config.ftsIndexes) { for (const fileModel of Object.keys(this.config.ftsIndexes)) { this.modelColumns[fileModel].push({ name: "textSearch", type: "Unsupported(\"TSVECTOR\")?", constraints: ["@default(dbgenerated())"] }, { name: `@@index([textSearch], type: ${this.config.ftsIndexes[fileModel].type})`, type: "", constraints: [] }); } } this.loaded = true; return this; } async resetMigrations() { const migrationPath = convertPathToLocal("./node_modules/.bin/migrations"); try { await fs.unlink(migrationPath); } catch (_) { } } async prepareDocumentation() { var _a; const configPath = await getConfigurationPath(this.configPath); const schemaCreatorDTSPath = convertPathToLocal("node_modules/prisma-util/schema-creator/index.d.ts"); const content = await fs.readFile(schemaCreatorDTSPath, "utf8"); const models = Object.entries(this.modelColumns).filter((fileModelData) => { const [fileModel, columns] = fileModelData; return !this.getShadowedModels().includes(fileModel) && !this.getExtendedModels().includes(fileModel); }); const goodModels = {}; for (const model of models) { let [fileModel, columns] = model; const [fileName, modelName] = fileModel.split(":"); columns = columns.filter(c => c.name.trim() != "" && !c.name.trim().startsWith("/")); if (goodModels[fileName]) goodModels[fileName][modelName] = columns; else goodModels[fileName] = { [modelName]: columns }; } ; const filesWithoutModels = this.files.filter(f => !models.some(n => n[0].split(":")[0] == f)); const r = content.includes("// ProjectToolchain.exit(\"documentation\");") ? /export declare function constantModel\(path: string\): \(model\?: string, column\?: string\) => string;.*?\/\/ ProjectToolchain.exit\(\"documentation\"\);/gims : /export declare function constantModel\(path: string\): \(model\?: string, column\?: string\) => string;/gims; let final = content.replace(r, `export declare function constantModel(path: string): (model?: string, column?: string) => string; ${filesWithoutModels.map(file => { return `export declare function constantModel(path: "${file}"): (model?: string, column?: string) => string;`; }).join("\n")} ${Object.entries(goodModels).map(entry => { const [file, assoc] = entry; const models = Object.entries(assoc); return `export declare function constantModel(path: "${file}"): <T extends ${models.map(mod => `"${mod[0]}"`).join(" | ")}>(model?: T, column?: ${models.map(mod => `T extends "${mod[0]}" ? ${mod[1].map(c => `"${c.name}"`).join(" | ")} : `).join("")} string) => string;`; }).join("\n")} // ProjectToolchain.exit("documentation");`); if (this.config.prismaGenerators && ((_a = this.config.generators) === null || _a === void 0 ? void 0 : _a.include)) { const gr = final.includes("// ProjectToolchain.exit(\"documentation-generator\");") ? /export declare function constantGenerator\(path: string\): \(generator\?: string\) => string;.*?\/\/ ProjectToolchain.exit\(\"documentation-generator\"\);/gims : /export declare function constantGenerator\(path: string\): \(generator\?: string\) => string;/gims; const generators = Object.values(this.generators).map(gen => `${gen.meta.file}:${gen.meta.name}`); const filesWithoutGenerators = this.config.generators.include .map(f => typeof f == "function" ? f() : f) .filter(f => !generators.some(gen => gen.split(":")[0] == f)); const goodGenerators = {}; for (const generator of generators) { const [fileName, generatorName] = generator.split(":"); if (goodGenerators[fileName]) goodGenerators[fileName].push(generatorName); else goodGenerators[fileName] = [generatorName]; } final = final.replace(gr, `export declare function constantGenerator(path: string): (generator?: string) => string; ${filesWithoutGenerators.map(file => { return `export declare function constantGenerator(path: "${file}"): (generator?: string) => string;`; }).join("\n")} ${Object.entries(goodGenerators).map(entry => { const [file, generators] = entry; return `export declare function constantGenerator(path: "${file}"): <T extends ${generators.map(gen => `"${gen}"`).join(" | ")}>(generator?: T) => string;`; }).join("\n")} // ProjectToolchain.exit("documentation-generator");`); } const types = (await getFiles(this.config.toolchain.resolve.types)).filter(i => i.endsWith(".d.ts")); const getTypeRegex = (typeName) => new RegExp(`^\\s*(?:export)?\\s*(?:declare)?\\s*(?:export)?\\s*(?:type)\\s*(${typeName}.*?);`, "gims"); const typeMap = {}; const emptyTypeFiles = []; for (const type of types) { const name = path.relative(this.config.toolchain.resolve.types, type).replace(/\\/g, "/"); const content = await fs.readFile(type, "utf8"); const allTypesRegex = getTypeRegex(""); for (let typesInFile; typesInFile = allTypesRegex.exec(content);) { const typeData = typesInFile[1]; const typeName = (typeData.split("=", 1)[0] ? typeData.split("=", 1)[0] : "").trim(); if (typeMap[name]) typeMap[name].push(typeName); else typeMap[name] = [typeName]; } if (!typeMap[name]) emptyTypeFiles.push(name); } const tr = content.includes("// ProjectToolchain.exit(\"documentation-types\");") ? /export declare function importType\(path: string, typeName: string\): string;.*?\/\/ ProjectToolchain.exit\(\"documentation-types\"\);/gims : /export declare function importType\(path: string, typeName: string\): string;/gims; final = final.replace(tr, `export declare function importType(path: string, typeName: string): string; ${emptyTypeFiles.map(file => { return `export declare function importType(path: "${file}", typeName: string): string;`; }).join("\n")} ${Object.entries(typeMap).map(entry => { const [file, types] = entry; return `export declare function importType<T extends ${types.map(gen => `"${gen}"`).join(" | ")}>(path: "${file}", typeName: T): string;`; }).join("\n")} // ProjectToolchain.exit("documentation-types");`); await fs.writeFile(schemaCreatorDTSPath, final); log("Prisma Util Toolchain has updated the Schema Creator definitions.", "\n"); let textToWrite = (await fs.readFile(configPath, "utf8")).replace(/@typedef {".*} OptionalFeatures/gms, `@typedef {${OptionalFeaturesArray.map(feature => `"${feature}"`).join(" | ")}} OptionalFeatures`); const regex = /(?<=@typedef {Object} Configuration)(.*?\*\/)/gims; textToWrite = textToWrite.replace(regex, ` * * @property {FileModelConfig} baseSchema * The file that contains your generator and datasource. This path is relative to your project root. * To find out more about configuring the base schema, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation#base-schema this} documentation section. * * @property {FileModelConfig[]} includeFiles * Files in this array will be merged in to the final schema by Prisma Util. * To find out more about configuring the included files, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation#include-files this} documentation section. * * @property {string[]?} [excludeModels] * This array uses the \`file:model\` association defined in the Prisma Util concepts. Models in this array will be excluded from the final build. * To find out more about configuring the excluded models, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation#exclude-models this} documentation section. * * @property {OptionalFeatures[]} optionalFeatures * Allows you to enable optional features to supercharge your Prisma Util setup. * To find out more about configuring optional features, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation#optional-features this} documentation section. * * @property {{[fileModel: string]: string}?} [extended] * Create model inheritance within Prisma! The model defined by the value of this key-value entry will receive all non-id non-relation fields from the model defined by the key. * To find out more about configuring model inheritance, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation#extend-models this} documentation section. * * @property {ProjectToolchainConfiguration} toolchain * Project toolchain configuration block. * To find out more about configuring Project Toolchain, read {@link https://prisma-util.gitbook.io/prisma-util/api-documentation#toolchain this} documentation section. ${Object.entries(configurationDocumentation).filter(entry => { var _a; return (_a = this.config.optionalFeatures) === null || _a === void 0 ? void 0 : _a.includes(entry[0]); }) .map(entry => { return entry[1].map(documentation => { return (`* @property {${documentation.type}} ${documentation.required ? documentation.name : `[${documentation.name}]`} ${documentation.description.map(line => `* ${line}`).join("\n")}`); }).join("\n*\n"); }) .join("\n*\n")} */`); await fs.writeFile(configPath, textToWrite); } /** Get a list of raw models.*/ getModels() { return this.models; } /** Prisma being prisma, creates DROP DEFAULT migrations. */ async fixMigrate() { if (!this.config.pgtrgm) return false; const data = (await fs.readdir(convertPathToLocal("./node_modules/.bin/migrations"), { withFileTypes: true })).filter(entity => entity.isDirectory()).map(entity => entity.name); const markedMigrations = []; for (const file of data) { const content = await fs.readFile(path.join(convertPathToLocal("./node_modules/.bin/migrations"), file, "migration.sql"), "utf-8"); if (!content.includes(`ALTER COLUMN "textSearch" DROP DEFAULT`)) continue; markedMigrations.push(file); } if (markedMigrations.length == 0) return false; experimental("Trying to fix the error from above.", "\n"); for (const migration of markedMigrations) await runPrismaCommand(`migrate resolve --applied "${migration}" --schema ./node_modules/.bin/generated-schema.prisma`); return true; } /**Hooks to run after client generation. */ async generate() { } /** * Hooks to run during generation. */ async toolchain() { let middlewareGenerator = MiddlewareToolchainInstance; let codeGenerator = GenerationToolchainInstance; let extensionsGenerator = ExtensionsToolchainInstance; // @deprecated - No extension if (this.config.pgtrgm && this.config.schema && this.config.ftsIndexes) { const modelMappings = Object.fromEntries(Object.entries(this.config.ftsIndexes).map(index => { return [index[0], index[1].indexes.map(ind => ind.field)]; })); const code = `import { Prisma, PrismaClient } from "@prisma/client"; const ALLOWED_ACTIONS = ["findMany", "findFirst"]; const MAPPED_COLUMNS_MODELS = { ${Object.entries(modelMappings).map(entry => { const split = entry[0].split(":"); return `"${split[split.length - 1]}": [${entry[1].map(en => `"${en}"`).join(", ")}]`; }).join(",\n ")} }; const MAPPED_MODELS = [${Object.keys(modelMappings).map(key => { const split = key.split(":"); return `"${split[split.length - 1]}"`; }).join(", ")}]; const JOINT_FILTERS = ["equals", "has", "not", "in", "notIn", "lt", "lte", "gt", "gte"] const INT_FILTERS = [...JOINT_FILTERS]; const STRING_FILTERS = [...JOINT_FILTERS, "contains", "endsWith", "startsWith", "mode"]; const SCALAR_FILTERS = ["equals", "hasEvery", "hasSome", "isEmpty"]; const BLOCK_FILTERS = ["NOT", "OR", "AND"]; const schema = "${this.config.schema}"; const MAPPED_SYMBOLS = { isEmpty: (first, second, mode) => Prisma.sql\`\${Prisma.raw(second ? \`\${first} = '{}'\` : \`\${first} <> '{}'\`)}\`, equals: (first, second, mode) => Prisma.sql\`\${Prisma.raw(first)} = \${second}\`, has: (first, secondy, mode) => Prisma.sql\`\${Prisma.raw(first)} @> \${[second]}\`, hasEvery: (first, second, mode) => Prisma.sql\`\${Prisma.raw(first)} @> \${second}\`, hasSome: (first, second, mode) => Prisma.sql\`\${Prisma.raw(first)} && \${second}\`, not: (first, second, mode) => Prisma.sql\`\${Prisma.raw(first)} <> \${second}\`, in: (first, second, mode) => Prisma.sql\`\${Prisma.raw(first)} IN (\${Prisma.join(second)})\`, notIn: (first, second, mode) => Prisma.sql\`\${Prisma.raw(first)} NOT IN (\${Prisma.join(second)})\`, lt: (first, second, mode) => Prisma.sql\`\${Prisma.raw(first)} < \${second}\`, lte: (first, second, mode) => Prisma.sql\`\${Prisma.raw(first)} <= \${second}\`, gt: (first, second, mode) => Prisma.sql\`\${Prisma.raw(first)} > \${second}\`, gte: (first, second, mode) => Prisma.sql\`\${Prisma.raw(first)} >= \${second}\`, contains: (first, second, mode) => mode == "default" ? Prisma.sql\`\${Prisma.raw(first)} LIKE \${second}\` : Prisma.sql\`\${Prisma.raw(first)} ILIKE \${\`%\${second}%\`}\`, endsWith: (first, second, mode) => mode == "default" ? Prisma.sql\`\${Prisma.raw(first)} LIKE \${second}}\` : Prisma.sql\`\${Prisma.raw(first)} ILIKE \${\`\${second}%\`}\`, startsWith: (first, second, mode) => mode == "default" ? Prisma.sql\`\${Prisma.raw(first)} LIKE \${second}\` : Prisma.sql\`\${Prisma.raw(first)} ILIKE \${\`%\${second}\`}\` } function check(object, MAPPED_COLUMNS) { const [key, filter] = object; if(BLOCK_FILTERS.includes(key)) return Object.entries(filter).some((val) => check(val, MAPPED_COLUMNS)); return typeof filter == "string" && MAPPED_COLUMNS.includes(key); } function flatten(array) { return array.reduce(function (flatArray, arrayToFlatten) { return flatArray.concat(Array.isArray(arrayToFlatten) ? flatten(arrayToFlatten) : arrayToFlatten); }, []); } const middleware = (prisma) => async (params, next) => { if(!ALLOWED_ACTIONS.includes(params.action)) return next(params); if(!params.model || !params.args.where || !MAPPED_MODELS.includes(params.model)) return next(params); const MAPPED_COLUMNS = (MAPPED_COLUMNS_MODELS)[params.model]; if(!Object.entries(params.args.where).some((val) => check(val, MAPPED_COLUMNS))) return next(params); const table = \`"\${schema}"."\${params.model}"\`; const limit = params.action == "findFirst" ? 1 : params.args.take ? params.args.take : 0; const offset = params.args.skip ? params.args.skip : 0; const selectedColumns = params.args.select ? ([...new Set(Object.keys(params.args.where).map(key => [key, true]).concat(Object.entries(params.args.select)).map(data => { return data[1] ? \`\${table}."\$\{data[0]}"\` : null; }).filter(String))]).map(val => Prisma.raw(val)) : ((prisma)["_baseDmmf"]["typeAndModelMap"][params.model]["fields"].filter((item) => !item.relationName).map((field) => [field.name, true]).map((data) => { return data[1] ? \`\${table}."\${data[0]}"\` : null; })).map((val) => Prisma.raw(val)); const orderBy = params.args.orderBy ? Object.entries(params.args.orderBy)[0] : null; const matches = {}; const cursor = params.args.cursor ? Object.entries(params.args.cursor).map(entry => [\`\${table}."\${entry[0]}"\`, entry[1]]).map((entry) => Prisma.sql\`\${Prisma.raw(entry[0])} > \${entry[1]}\`) : []; function doFilter(root, obj, first, action) { const object = Object.fromEntries(obj); let and = object["AND"]; let or = object["OR"]; let not = object["NOT"]; const intFilters = flatten(obj .filter(entry => Object.keys(entry[1]).some(key => INT_FILTERS.includes(key))) .map(entry => [\`\${table}."\${entry[0]}"\`, entry[1]]).map((entry) => { const data = Object.entries(entry[1]) .filter(en => (typeof en[1] == "number" || (Array.isArray(en[1]) && typeof (en[1])[0] == "number") && en[0] != "equals")) .map(en => { return MAPPED_SYMBOLS[en[0]].apply(root, [entry[0], en[1]]); }); return data; })); const baseIntFilters = flatten(obj .map(entry => [\`\${table}."\${entry[0]}"\`, entry[1]]) .filter(entry => typeof entry[1] != "object") .map((entry) => { const data = Object.entries(entry[1]) .filter(en => (typeof en[1] == "number")) .map(en => { return MAPPED_SYMBOLS.equals.apply(root, [entry[0], en[1]]); }); return data; })); const baseStringFilters = flatten(obj .filter(entry => typeof entry[1] == "string") .map((entry) => { if(MAPPED_COLUMNS.includes(entry[0])) { matches[entry[0]] = entry[1]; return [Prisma.sql\`(\${Prisma.raw(entry[0])} % \${entry[1]})\`]; } entry[0] = \`\${table}."\${entry[0]}"\`; return [MAPPED_SYMBOLS.equals.apply(root, [entry[0], entry[1]])]; })); const stringFilters = flatten(obj .filter(entry => Object.keys(entry[1]).some(key => STRING_FILTERS.includes(key))) .map(entry => [\`\${table}."\${entry[0]}"\`, entry[1]]) .map((entry) => { const data = Object.entries(entry[1]) .filter(en => en[0] != "mode" && typeof en[1] == "string" || (Array.isArray(en[1]) && typeof (en[1])[0] == "string" && en[0] != "equals")) .map(en => { return MAPPED_SYMBOLS[en[0]].apply(root, [entry[0], en[1], entry[1].mode ? entry[1].mode : "default"]); }); return data; })); const scalarFilters = flatten(obj .filter(entry => Object.keys(entry[1]).some(key => SCALAR_FILTERS.includes(key))) .map(entry => [\`\${table}."\${entry[0]}"\`, entry[1]]) .map((entry) => { const data = Object.entries(entry[1]) .filter(en => (Array.isArray(en[1]) || typeof en[1] == "boolean" && en[0] == "isEmpty")) .map(en => { return MAPPED_SYMBOLS[en[0]].apply(root, [entry[0], en[1]]); }); return data; })); const conditions = [ ...(intFilters.length > 0 ? intFilters : []), ...(stringFilters.length > 0 ? stringFilters : []), ...(scalarFilters.length > 0 ? scalarFilters : []), ...(baseIntFilters.length > 0 ? baseIntFilters : []), ...(baseStringFilters.length > 0 ? baseStringFilters : []), ]; let AND, OR, NOT; if(and) AND = doFilter(root, Object.entries(and), false, "AND"); if(or) OR = doFilter(root, Object.entries(or), false, "OR"); if(not) NOT = doFilter(root, Object.entries(not), false, "NOT"); const data = [ ...(AND ? AND : []), ...(OR ? OR : []), ...(NOT ? NOT : []), ...conditions ] if(action && data.length > 0) return action == "NOT" ? [Prisma.sql\`(NOT (\${Prisma.join(data, \` AND \`)}))\`] : [Prisma.sql\`(\${Prisma.join(data, \` \${action} \`)})\`]; return data.length > 0 ? [Prisma.join(data, " AND ")] : []; } const blockFilters = doFilter(this, Object.entries(params.args.where), true); const conditions = [ ...(cursor.length > 0 ? cursor : []), ...(blockFilters.length > 0 ? blockFilters : []), ]; return prisma.$queryRaw\`SELECT \${Array.isArray(selectedColumns) ? Prisma.join(selectedColumns) : selectedColumns}\${orderBy ? Prisma.sql\`, SIMILARITY(\${Prisma.raw(orderBy[0])}, \${matches[orderBy[0]]}) as ftsScore\` : Prisma.empty} FROM \${Prisma.raw(table)} WHERE (\${Prisma.join(conditions, " AND ")})\${orderBy ? Prisma.sql\` ORDER BY ftsScore \${Prisma.raw(orderBy[1].toUpperCase())}\` : Prisma.empty}\${limit > 0 ? Prisma.sql\` LIMIT \${limit}\` : Prisma.empty}\${offset > 0 ? Prisma.sql\` OFFSET \${offset}\` : Prisma.empty}\`; }; export default middleware;`; middlewareGenerator = MiddlewareToolchainInstance.defineMiddleware("pgtrgm", "ftsIndexes", code); } if (this.config.staticTake) { middlewareGenerator = middlewareGenerator.defineMiddleware("staticTake", "alterFindMany", `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 __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; import { getStaticTake } from "../../../../lib/functions.js"; var staticTake = Object.fromEntries(Object.entries(getStaticTake()).map(function (entry) { return entry[0] == "$global" ? entry : [entry[0].split(":")[1], entry[1]]; })); var middleware = function (prisma) { return (function (params, next) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { if (params.action != "findMany" || !params.model || params.args.take) return [2 /*return*/, next(params)]; if (staticTake[params.model]) params.args.take = staticTake[params.model]; else if (staticTake.$global) params.args.take = staticTake.$global; return [2 /*return*/, next(params)]; }); }); }); }; export default middleware; `); extensionsGenerator = extensionsGenerator.defineExtension("staticTake", "alterFindMany", `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 __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; import { getStaticTake } from "../../../../lib/functions.js"; var staticTake = Object.fromEntries(Object.entries(getStaticTake()).map(function (entry) { return entry[0] == "$global" ? entry : [entry[0].split(":")[1], entry[1]]; })); export default function extension(prisma) { var _this = this; return prisma.$extends({ query: { $allModels: { findMany: function (_a) { var model = _a.model, operation = _a.operation, args = _a.args, query = _a.query; return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_b) { if (args.take) return [2 /*return*/, query(args)]; if (staticTake[model]) args.take = staticTake[model]; else if (staticTake.$global) args.take = staticTake.$global; return [2 /*return*/, query(args)]; }); }); } } } }); } `); } // Migration Done if (this.config.ignoreEmptyConditions) { const code = `var process = function (obj) { for (var prop in obj) { if (prop === 'OR' && obj[prop] && Array.isArray(obj[prop]) && obj[prop].length === 0) delete obj[prop]; else if (typeof obj[prop] === 'object') obj[prop] = process(obj[prop]); } return obj; }; var middleware = function (prisma) { return (function (params, next) { if (!params.model) return next(params); params.args = process(params.args); return next(params); }); }; export default middleware;`; middlewareGenerator = middlewareGenerator.defineMiddleware("ignoreEmptyConditions", "removeEmptyOrBlock", code); extensionsGenerator = extensionsGenerator.defineExtension("ignoreEmptyConditions", "removeEmptyOrBlock", `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 __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break;