From 543cd31161a264d9d59100d46886882d69df640b Mon Sep 17 00:00:00 2001 From: Leif Niemczik Date: Wed, 1 Mar 2023 20:39:57 +0100 Subject: [PATCH 1/4] add support for providing a middleware file --- prisma/middleware.ts | 4 ++ prisma/schema.prisma | 2 +- src/config.ts | 4 +- src/helpers.ts | 106 +++++++++++++++++++++++++++---------------- 4 files changed, 76 insertions(+), 40 deletions(-) create mode 100644 prisma/middleware.ts diff --git a/prisma/middleware.ts b/prisma/middleware.ts new file mode 100644 index 0000000..3b9c5e3 --- /dev/null +++ b/prisma/middleware.ts @@ -0,0 +1,4 @@ +export default async ({ ctx, next }) => { + console.log("Hello from the imported Middleware"); + return next({ ctx }); +} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5f2ba16..dd092c7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -6,7 +6,7 @@ generator trpc { provider = "node ./lib/generator.js" isGenerateSelect = true isGenerateInclude = true - withMiddleware = true + withMiddleware = "./middleware" withShield = true contextPath = "./context" trpcOptionsPath = "./trpcOptions" diff --git a/src/config.ts b/src/config.ts index baf7709..6336277 100644 --- a/src/config.ts +++ b/src/config.ts @@ -5,10 +5,12 @@ const configBoolean = z .enum(['true', 'false']) .transform((arg) => JSON.parse(arg)); +const configStringOrBoolean = z.union([configBoolean, z.string().default("../../../../src/middleware")]) + const modelActionEnum = z.nativeEnum(DMMF.ModelAction); export const configSchema = z.object({ - withMiddleware: configBoolean.default('true'), + withMiddleware: configStringOrBoolean.default('true'), withShield: configBoolean.default('true'), contextPath: z.string().default('../../../../src/context'), trpcOptionsPath: z.string().optional(), diff --git a/src/helpers.ts b/src/helpers.ts index 104535b..3b40056 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,6 +1,7 @@ import { DMMF, EnvValue, GeneratorOptions } from '@prisma/generator-helper'; import { parseEnvValue } from '@prisma/internals'; import { SourceFile } from 'ts-morph'; +import { z } from 'zod'; import { Config } from './config'; import getRelativePath from './utils/getRelativePath'; import { uncapitalizeFirstLetter } from './utils/uncapitalizeFirstLetter'; @@ -9,8 +10,8 @@ const getProcedureName = (config: Config) => { return config.withShield ? 'shieldedProcedure' : config.withMiddleware - ? 'protectedProcedure' - : 'publicProcedure'; + ? 'protectedProcedure' + : 'publicProcedure'; }; export const generateCreateRouterImport = ({ @@ -50,6 +51,17 @@ export const generateShieldImport = ( }); }; +export const generateMiddlewareImport = ( + sourceFile: SourceFile, + options: GeneratorOptions, +) => { + const outputDir = parseEnvValue(options.generator.output as EnvValue); + sourceFile.addImportDeclaration({ + moduleSpecifier: getRelativePath(outputDir, 'middleware'), + namedImports: ['permissions'], + }); +}; + export const generateRouterImport = ( sourceFile: SourceFile, modelNamePlural: string, @@ -88,14 +100,13 @@ export function generateBaseRouter( } sourceFile.addStatements(/* ts */ ` - export const t = trpc.initTRPC.context().create(${ - config.trpcOptionsPath ? 'trpcOptions' : '' - }); + export const t = trpc.initTRPC.context().create(${config.trpcOptionsPath ? 'trpcOptions' : '' + }); `); const middlewares = []; - if (config.withMiddleware) { + if (config.withMiddleware && typeof config.withMiddleware === "boolean") { sourceFile.addStatements(/* ts */ ` export const globalMiddleware = t.middleware(async ({ ctx, next }) => { console.log('inside middleware!') @@ -107,18 +118,36 @@ export function generateBaseRouter( }); } + if (config.withMiddleware && typeof config.withMiddleware === "string") { + sourceFile.addStatements(/* ts */ ` + import defaultMiddleware from '${getRelativePath( + outputDir, + config.withMiddleware, + true, + options.schemaPath, + )}'; + `); + sourceFile.addStatements(/* ts */ ` + export const globalMiddleware = t.middleware(defaultMiddleware);` + ); + middlewares.push({ + type: 'global', + value: /* ts */ `.use(globalMiddleware)`, + }); + } + if (config.withShield) { sourceFile.addStatements(/* ts */ ` - export const permissionsMiddleware = t.middleware(permissions);`); + export const permissionsMiddleware = t.middleware(permissions); `); middlewares.push({ type: 'shield', value: /* ts */ ` - .use(permissions)`, + .use(permissions)`, }); } sourceFile.addStatements(/* ts */ ` - export const publicProcedure = t.procedure;`); + export const publicProcedure = t.procedure; `); if (middlewares.length > 0) { const procName = getProcedureName(config); @@ -126,17 +155,16 @@ export function generateBaseRouter( middlewares.forEach((middleware, i) => { if (i === 0) { sourceFile.addStatements(/* ts */ ` - export const ${procName} = t.procedure - `); + export const ${procName} = t.procedure + `); } sourceFile.addStatements(/* ts */ ` - .use(${ - middleware.type === 'shield' - ? 'permissionsMiddleware' - : 'globalMiddleware' + .use(${middleware.type === 'shield' + ? 'permissionsMiddleware' + : 'globalMiddleware' }) - `); + `); }); } } @@ -156,14 +184,16 @@ export function generateProcedure( input = '{ where: input.where, orderBy: input.orderBy, by: input.by, having: input.having, take: input.take, skip: input.skip }'; } - sourceFile.addStatements(/* ts */ `${config.showModelNameInProcedure ? name : nameWithoutModel}: ${getProcedureName(config)} + sourceFile.addStatements(/* ts */ `${config.showModelNameInProcedure ? name : nameWithoutModel}: ${getProcedureName(config) + } .input(${typeName}) - .${getProcedureTypeByOpName(baseOpType)}(async ({ ctx, input }) => { - const ${name} = await ctx.prisma.${uncapitalizeFirstLetter( - modelName, - )}.${opType.replace('One', '')}(${input}); - return ${name}; - }),`); + .${getProcedureTypeByOpName(baseOpType)} (async ({ ctx, input }) => { + const ${name} = await ctx.prisma.${uncapitalizeFirstLetter( + modelName, + ) + }.${opType.replace('One', '')} (${input}); + return ${name}; +}), `); } export function generateRouterSchemaImports( @@ -192,7 +222,7 @@ export const getRouterSchemaImportByOpName = ( const inputType = getInputTypeByOpName(opType, modelName); return inputType - ? `import { ${inputType} } from "../schemas/${opType}${modelName}.schema";` + ? `import { ${inputType} } from "../schemas/${opType}${modelName}.schema"; ` : ''; }; @@ -200,46 +230,46 @@ export const getInputTypeByOpName = (opName: string, modelName: string) => { let inputType; switch (opName) { case 'findUnique': - inputType = `${modelName}FindUniqueSchema`; + inputType = `${modelName} FindUniqueSchema`; break; case 'findFirst': - inputType = `${modelName}FindFirstSchema`; + inputType = `${modelName} FindFirstSchema`; break; case 'findMany': - inputType = `${modelName}FindManySchema`; + inputType = `${modelName} FindManySchema`; break; case 'findRaw': - inputType = `${modelName}FindRawObjectSchema`; + inputType = `${modelName} FindRawObjectSchema`; break; case 'createOne': - inputType = `${modelName}CreateOneSchema`; + inputType = `${modelName} CreateOneSchema`; break; case 'createMany': - inputType = `${modelName}CreateManySchema`; + inputType = `${modelName} CreateManySchema`; break; case 'deleteOne': - inputType = `${modelName}DeleteOneSchema`; + inputType = `${modelName} DeleteOneSchema`; break; case 'updateOne': - inputType = `${modelName}UpdateOneSchema`; + inputType = `${modelName} UpdateOneSchema`; break; case 'deleteMany': - inputType = `${modelName}DeleteManySchema`; + inputType = `${modelName} DeleteManySchema`; break; case 'updateMany': - inputType = `${modelName}UpdateManySchema`; + inputType = `${modelName} UpdateManySchema`; break; case 'upsertOne': - inputType = `${modelName}UpsertSchema`; + inputType = `${modelName} UpsertSchema`; break; case 'aggregate': - inputType = `${modelName}AggregateSchema`; + inputType = `${modelName} AggregateSchema`; break; case 'aggregateRaw': - inputType = `${modelName}AggregateRawObjectSchema`; + inputType = `${modelName} AggregateRawObjectSchema`; break; case 'groupBy': - inputType = `${modelName}GroupBySchema`; + inputType = `${modelName} GroupBySchema`; break; default: console.log('getInputTypeByOpName: ', { opName, modelName }); From 136ce276a7504378dfef284b622a8f4e2e6b9a1a Mon Sep 17 00:00:00 2001 From: Leif Niemczik Date: Wed, 1 Mar 2023 20:55:01 +0100 Subject: [PATCH 2/4] update readm --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8415978..4e28597 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,11 @@ - [About The Project](#about-the-project) - [Supported Prisma Versions](#supported-prisma-versions) - - [Prisma 4](#prisma-4) - - [Prisma 2/3](#prisma-23) + - [Prisma 4](#prisma-4) + - [Prisma 2/3](#prisma-23) - [Supported tRPC Versions](#supported-trpc-versions) - - [tRPC 10](#trpc-10) - - [tRPC 9](#trpc-9) + - [tRPC 10](#trpc-10) + - [tRPC 9](#trpc-9) - [Installation](#installation) - [Usage](#usage) - [Customizations](#customizations) @@ -172,9 +172,9 @@ model User { # Additional Options | Option | Description | Type | Default | -| -------------------------- | -------------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| -------------------------- | -------------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | | `output` | Output directory for the generated routers and zod schemas | `string` | `./generated` | -| `withMiddleware` | Attaches a global middleware that runs before all procedures | `boolean` | `true` | +| `withMiddleware` | Attaches a global middleware that runs before all procedures | `boolean | string` | `true` | | `withShield` | Generates a tRPC Shield to use as a permissions layer | `boolean` | `true` | | `contextPath` | Sets the context path used in your routers | `string` | `../../../../src/context` | | `trpcOptionsPath` | Sets the tRPC instance options | `string` | `../../../../src/trpcOptions` | @@ -183,14 +183,13 @@ model User { | `showModelNameInProcedure` | When disabled, the generated procedure no longer includes the name of the Prisma model | `boolean` | `true` | | `generateModelActions` | Enables the generation of specific model actions | `string` | `aggregate,aggregateRaw,count,create,createMany,delete,deleteMany,findFirst,findFirstOrThrow,findMany,findRaw,findUnique,findUniqueOrThrow,groupBy,update,updateMany,upsert` | - Use additional options in the `schema.prisma` ```prisma generator trpc { provider = "prisma-trpc-generator" output = "./trpc" - withMiddleware = false + withMiddleware = "../middleware" withShield = false contextPath = "../context" trpcOptionsPath = "../trpcOptions" From de7297541af28e0da79c1c5b9d207825c175a355 Mon Sep 17 00:00:00 2001 From: Leif Niemczik Date: Wed, 1 Mar 2023 21:01:23 +0100 Subject: [PATCH 3/4] remove obsolete zod import --- src/helpers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/helpers.ts b/src/helpers.ts index 3b40056..a34e354 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,7 +1,6 @@ import { DMMF, EnvValue, GeneratorOptions } from '@prisma/generator-helper'; import { parseEnvValue } from '@prisma/internals'; import { SourceFile } from 'ts-morph'; -import { z } from 'zod'; import { Config } from './config'; import getRelativePath from './utils/getRelativePath'; import { uncapitalizeFirstLetter } from './utils/uncapitalizeFirstLetter'; From 18309375462f6677b1b14ed6eb6153e2a9de9706 Mon Sep 17 00:00:00 2001 From: omar-dulaimi Date: Mon, 6 Mar 2023 23:09:21 +0300 Subject: [PATCH 4/4] fix getInputTypeByOpName --- src/helpers.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index 39e8205..a651ba8 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -228,46 +228,46 @@ export const getInputTypeByOpName = (opName: string, modelName: string) => { let inputType; switch (opName) { case 'findUnique': - inputType = `${modelName} FindUniqueSchema`; + inputType = `${modelName}FindUniqueSchema`; break; case 'findFirst': - inputType = `${modelName} FindFirstSchema`; + inputType = `${modelName}FindFirstSchema`; break; case 'findMany': - inputType = `${modelName} FindManySchema`; + inputType = `${modelName}FindManySchema`; break; case 'findRaw': - inputType = `${modelName} FindRawObjectSchema`; + inputType = `${modelName}FindRawObjectSchema`; break; case 'createOne': - inputType = `${modelName} CreateOneSchema`; + inputType = `${modelName}CreateOneSchema`; break; case 'createMany': - inputType = `${modelName} CreateManySchema`; + inputType = `${modelName}CreateManySchema`; break; case 'deleteOne': - inputType = `${modelName} DeleteOneSchema`; + inputType = `${modelName}DeleteOneSchema`; break; case 'updateOne': - inputType = `${modelName} UpdateOneSchema`; + inputType = `${modelName}UpdateOneSchema`; break; case 'deleteMany': - inputType = `${modelName} DeleteManySchema`; + inputType = `${modelName}DeleteManySchema`; break; case 'updateMany': - inputType = `${modelName} UpdateManySchema`; + inputType = `${modelName}UpdateManySchema`; break; case 'upsertOne': - inputType = `${modelName} UpsertSchema`; + inputType = `${modelName}UpsertSchema`; break; case 'aggregate': - inputType = `${modelName} AggregateSchema`; + inputType = `${modelName}AggregateSchema`; break; case 'aggregateRaw': - inputType = `${modelName} AggregateRawObjectSchema`; + inputType = `${modelName}AggregateRawObjectSchema`; break; case 'groupBy': - inputType = `${modelName} GroupBySchema`; + inputType = `${modelName}GroupBySchema`; break; default: console.log('getInputTypeByOpName: ', { opName, modelName });