Skip to content

omar-dulaimi/prisma-trpc-shield-generator

Repository files navigation

Prisma tRPC Shield Generator

🛡️ Automatically generate tRPC Shield permissions from your Prisma schema

npm version npm downloads CI License

A powerful Prisma generator that creates tRPC Shield configurations from your Prisma schema. Automatically generates type-safe permission rules for all your database operations, saving you time and reducing boilerplate code.

💖 Support This Project

If this tool helps you build better applications, please consider supporting its development:

GitHub Sponsors

Your sponsorship helps maintain and improve this project. Thank you! 🙏

🚀 Latest Release

Now with full Prisma 6 & tRPC 11 support!

npm install prisma-trpc-shield-generator

This release includes major upgrades to the latest Prisma 6+ and tRPC v11+ - bringing compatibility with the latest versions and their breaking changes. Report any issues to help us continue improving!

📖 Table of Contents

✨ Features

  • 🚀 Zero Configuration - Works out of the box with sensible defaults
  • 🔄 Auto-Generated - Updates every time you run prisma generate
  • 🛡️ Type Safe - Full TypeScript support with proper typing
  • 🎯 Comprehensive - Covers all Prisma operations (queries, mutations, aggregations)
  • ⚙️ Configurable - Customize output directory and context path
  • 📦 Lightweight - Minimal dependencies and fast generation

🚀 Quick Start

Installation

npm install prisma-trpc-shield-generator trpc-shield

Setup

  1. Add the generator to your schema.prisma:
generator trpc_shield {
  provider    = "prisma-trpc-shield-generator"
  contextPath = "../src/context"
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  author    User?    @relation(fields: [authorId], references: [id])
  authorId  Int?
}
  1. Generate your shield:
npx prisma generate
  1. Use the generated permissions:
import { permissions } from './generated/shield';
import { t } from './trpc';

export const permissionsMiddleware = t.middleware(permissions);
export const protectedProcedure = t.procedure.use(permissionsMiddleware);

📋 Generated Output

The generator creates a comprehensive shield configuration with all CRUD operations:

import { shield, allow } from 'trpc-shield';
import { Context } from '../../../context';

export const permissions = shield<Context>({
  query: {
    // Find operations
    findUniqueUser: allow,
    findFirstUser: allow,
    findManyUser: allow,
    findUniquePost: allow,
    findFirstPost: allow,
    findManyPost: allow,
    
    // Aggregation operations
    aggregateUser: allow,
    aggregatePost: allow,
    groupByUser: allow,
    groupByPost: allow,
  },
  mutation: {
    // Create operations
    createOneUser: allow,
    createOnePost: allow,
    
    // Update operations
    updateOneUser: allow,
    updateOnePost: allow,
    updateManyUser: allow,
    updateManyPost: allow,
    
    // Delete operations
    deleteOneUser: allow,
    deleteOnePost: allow,
    deleteManyUser: allow,
    deleteManyPost: allow,
    
    // Upsert operations
    upsertOneUser: allow,
    upsertOnePost: allow,
  },
});

⚙️ Configuration Options

Option Description Type Default
output Output directory for the generated shield string ./generated
contextPath Path to your tRPC context file (relative to output) string ../../../../src/context

Example Configuration

generator trpc_shield {
  provider    = "prisma-trpc-shield-generator"
  output      = "./src/shields"
  contextPath = "../context"
}

🔧 Advanced Usage

Custom Permission Rules

Replace the default allow rules with your custom logic:

import { permissions } from './generated/shield';
import { rule, and, or } from 'trpc-shield';

const isAuthenticated = rule()(async (parent, args, ctx) => {
  return ctx.user !== null;
});

const isOwner = rule()(async (parent, args, ctx) => {
  const post = await ctx.prisma.post.findUnique({
    where: { id: args.where.id },
    select: { authorId: true }
  });
  return post?.authorId === ctx.user?.id;
});

// Override specific permissions
export const customPermissions = {
  ...permissions,
  mutation: {
    ...permissions.mutation,
    createOnePost: and(isAuthenticated),
    updateOnePost: and(isAuthenticated, isOwner),
    deleteOnePost: and(isAuthenticated, isOwner),
  }
};

Integration with tRPC Router

import { initTRPC } from '@trpc/server';
import { customPermissions } from './shields/permissions';

const t = initTRPC.context<Context>().create();

export const permissionsMiddleware = t.middleware(customPermissions);
export const protectedProcedure = t.procedure.use(permissionsMiddleware);

export const appRouter = t.router({
  user: t.router({
    create: protectedProcedure
      .input(z.object({ name: z.string(), email: z.string() }))
      .mutation(({ input, ctx }) => {
        return ctx.prisma.user.create({ data: input });
      }),
  }),
});

📚 Examples

Basic CRUD with Authentication

import { rule, and } from 'trpc-shield';

const isAuthenticated = rule()(async (parent, args, ctx) => {
  return !!ctx.user;
});

const canManagePosts = rule()(async (parent, args, ctx) => {
  if (!ctx.user) return false;
  
  // Admin can manage all posts
  if (ctx.user.role === 'ADMIN') return true;
  
  // Users can only manage their own posts
  if (args.where?.authorId) {
    return args.where.authorId === ctx.user.id;
  }
  
  return false;
});

export const permissions = shield<Context>({
  query: {
    findManyPost: allow, // Public read access
    findUniquePost: allow,
    findManyUser: isAuthenticated, // Authenticated read access
  },
  mutation: {
    createOnePost: isAuthenticated,
    updateOnePost: and(isAuthenticated, canManagePosts),
    deleteOnePost: and(isAuthenticated, canManagePosts),
  },
});

🔍 Troubleshooting

Common Issues

Error: Cannot find module '../context'

  • Ensure your contextPath is correct relative to the output directory
  • Check that your context file exports a Context type

TypeScript errors in generated shield

  • Make sure trpc-shield is installed and up to date
  • Verify your tRPC context is properly typed

Shield not updating after schema changes

  • Run npx prisma generate after modifying your schema
  • Check that the generator is properly configured in schema.prisma

🤝 Contributing

Contributions are welcome! Please read our Contributing Guide for details.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🔗 Related Projects

  • tRPC Shield - The permission system this generator creates
  • Prisma - The database toolkit this integrates with
  • tRPC - The TypeScript RPC framework this works with

Made with ❤️ by Omar Dulaimi

About

Prisma +2 generator to emit a tRPC shield from your Prisma schema

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

No packages published

Contributors 2

  •  
  •