UNPKG

@algochad/prisma-core

Version:

A comprehensive NestJS library that provides EF-Core-like operations using Prisma and GraphQL. Features LINQ-style query builders, advanced data manipulation, GraphQL integration with genql, and a unified API for both Prisma and GraphQL operations. Includ

551 lines (481 loc) 18.3 kB
# Examples and Tutorials ## 📚 Examples and Tutorials ### Complete Example: Blog Application ```typescript // Models: User, Post, Comment, Tag @Injectable() export class BlogService { constructor(private readonly repository: AppRepository) {} // Get popular posts with authors and comment counts async getPopularPosts(limit: number = 10) { const posts = await this.repository.post .Include({ user: { select: { id: true, name: true } }, comments: { select: { id: true } }, }) .Where({ published: true }) .ToEnumerable(); return posts .Select((post) => ({ id: post.id, title: post.title, author: post.user.name, commentCount: post.comments.length, publishedAt: post.publishedAt, })) .OrderByDescending((post) => post.commentCount) .ThenByDescending((post) => post.publishedAt) .Take(limit) .ToArray(); } // Advanced: Get user engagement analytics (Prisma ORM with transactions) async getUserEngagementAnalytics(userId: number) { return await this.repository.user.Transaction(async (tx) => { const user = await tx.Where({ id: userId }).First(); if (!user) throw new Error('User not found'); const posts = await this.repository.post .Where({ userId }) .Include({ comments: true }) .ToEnumerable(); const analytics = posts .GroupBy((post) => post.createdAt.getMonth()) .Select((monthGroup) => ({ month: monthGroup.Key, postCount: monthGroup.Count(), totalComments: monthGroup.Sum( (post) => post.comments.length, ), averageCommentsPerPost: monthGroup.Average( (post) => post.comments.length, ), })) .OrderBy((stat) => stat.month) .ToArray(); const totalEngagement = posts.Sum((post) => post.comments.length); return { user: { id: user.id, name: user.name }, monthlyStats: analytics, totalEngagement, postsPublished: posts.Count(), averageEngagement: totalEngagement / posts.Count(), }; }); } } ``` ### Real-World Performance Example ```typescript @Injectable() export class AnalyticsService { async generateMonthlyReport(year: number, month: number) { // Start benchmark const benchmark = BenchmarkUtils.createBenchmark('Monthly Report'); try { const startDate = new Date(year, month - 1, 1); const endDate = new Date(year, month, 0); // Parallel data fetching const [users, posts, comments] = await Promise.all([ this.repository.user .Where({ createdAt: { gte: startDate, lte: endDate, }, }) .ToEnumerable(), this.repository.post .Where({ publishedAt: { gte: startDate, lte: endDate, }, }) .Include({ comments: true }) .ToEnumerable(), this.repository.comment .Where({ createdAt: { gte: startDate, lte: endDate, }, }) .ToEnumerable(), ]); // Use AsyncEnumerable for heavy processing const userStats = await AsyncEnumerable.from(users.ToArray()) .Select(async (user) => ({ id: user.id, name: user.name, postsCount: await this.repository.post .Where({ userId: user.id }) .Count(), engagementScore: await this.calculateEngagementScore( user.id, ), })) .Where(async (stat) => stat.postsCount > 0) .OrderByDescending((stat) => stat.engagementScore) .ToArrayAsync(); const report = { period: { year, month }, userStats, totalUsers: users.Count(), totalPosts: posts.Count(), totalComments: comments.Count(), topPerformers: userStats.take(10), }; benchmark.end(); console.log(`Report generated in ${benchmark.duration}ms`); return report; } catch (error) { benchmark.error(error); throw error; } } } ``` ### E-commerce Example ```typescript @Injectable() export class EcommerceService { constructor(private readonly repository: AppRepository) {} // Get product recommendations based on user behavior async getProductRecommendations(userId: number, limit: number = 10) { // Get user's order history const userOrders = await this.repository.order .Where({ userId }) .Include({ orderItems: { include: { product: true } } }) .ToEnumerable(); // Extract categories and brands user has purchased const purchasedCategories = userOrders .SelectMany((order) => order.orderItems) .Select((item) => item.product.categoryId) .Distinct() .ToArray(); const purchasedBrands = userOrders .SelectMany((order) => order.orderItems) .Select((item) => item.product.brandId) .Distinct() .ToArray(); // Get products from similar categories/brands that user hasn't bought const purchasedProductIds = userOrders .SelectMany((order) => order.orderItems) .Select((item) => item.productId) .Distinct() .ToArray(); const recommendations = await this.repository.product .Where({ OR: [ { categoryId: { in: purchasedCategories } }, { brandId: { in: purchasedBrands } }, ], id: { notIn: purchasedProductIds }, isActive: true, }) .Include({ reviews: true }) .ToEnumerable(); return recommendations .Select((product) => ({ id: product.id, name: product.name, price: product.price, rating: product.reviews.length > 0 ? product.reviews.reduce( (sum, r) => sum + r.rating, 0, ) / product.reviews.length : 0, reviewCount: product.reviews.length, recommendationScore: this.calculateRecommendationScore( product, purchasedCategories, purchasedBrands, ), })) .Where((p) => p.rating >= 3.5) // Only recommend well-rated products .OrderByDescending((p) => p.recommendationScore) .ThenByDescending((p) => p.rating) .Take(limit) .ToArray(); } private calculateRecommendationScore( product: any, userCategories: number[], userBrands: number[], ): number { let score = 0; if (userCategories.includes(product.categoryId)) score += 10; if (userBrands.includes(product.brandId)) score += 8; // Add points based on popularity score += Math.min(product.reviews.length / 10, 5); return score; } // Generate sales analytics async generateSalesAnalytics(startDate: Date, endDate: Date) { const orders = await this.repository.order .Where({ createdAt: { gte: startDate, lte: endDate }, status: 'COMPLETED', }) .Include({ orderItems: { include: { product: true } }, user: true, }) .ToEnumerable(); // Sales by category const salesByCategory = orders .SelectMany((order) => order.orderItems) .GroupBy((item) => item.product.categoryId) .Select((group) => ({ categoryId: group.Key, totalSales: group.Sum((item) => item.quantity * item.price), totalQuantity: group.Sum((item) => item.quantity), orderCount: group.Count(), })) .OrderByDescending((cat) => cat.totalSales) .ToArray(); // Top customers const topCustomers = orders .GroupBy((order) => order.userId) .Select((group) => ({ userId: group.Key, userName: group.First().user.name, totalSpent: group.Sum((order) => order.total), orderCount: group.Count(), averageOrderValue: group.Average((order) => order.total), })) .OrderByDescending((customer) => customer.totalSpent) .Take(20) .ToArray(); // Daily sales trend const dailySales = orders .GroupBy((order) => order.createdAt.toDateString()) .Select((group) => ({ date: group.Key, totalSales: group.Sum((order) => order.total), orderCount: group.Count(), averageOrderValue: group.Average((order) => order.total), })) .OrderBy((day) => day.date) .ToArray(); return { summary: { totalRevenue: orders.Sum((order) => order.total), totalOrders: orders.Count(), averageOrderValue: orders.Average((order) => order.total), uniqueCustomers: orders .Select((order) => order.userId) .Distinct() .Count(), }, salesByCategory, topCustomers, dailySales, }; } } ``` ### GraphQL Integration Example ```typescript @Injectable() export class GraphQLExampleService { constructor(private readonly repository: AppRepository) {} // Example using GraphQL data source async getUserProfilesFromGraphQL() { // Fetch user data from GraphQL endpoint const users = await this.repository.user .Where({ isActive: true }) .Select({ id: true, name: true, email: true, profile: { select: { bio: true, avatar: true, location: true, }, }, posts: { select: { id: true, title: true, createdAt: true, }, }, }) .ToArrayAsync(); // Process with LINQ operations const processedProfiles = await AsyncEnumerable.from(users) .Where((user) => user.profile != null) .Select(async (user) => ({ id: user.id, name: user.name, email: user.email, profileComplete: this.calculateProfileCompleteness( user.profile, ), recentPostsCount: user.posts.filter( (post) => new Date(post.createdAt) > new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), ).length, engagementLevel: await this.calculateEngagementLevel(user.id), })) .Where((profile) => profile.profileComplete > 50) .OrderByDescending((profile) => profile.engagementLevel) .ToArrayAsync(); return processedProfiles; } private calculateProfileCompleteness(profile: any): number { let completeness = 0; if (profile.bio) completeness += 30; if (profile.avatar) completeness += 30; if (profile.location) completeness += 40; return completeness; } private async calculateEngagementLevel(userId: number): Promise<number> { // This could be another GraphQL query or calculation return Math.random() * 100; // Placeholder } } ``` ### Data Migration Example ```typescript @Injectable() export class DataMigrationService { constructor(private readonly repository: AppRepository) {} // Migrate data with validation and error handling async migrateUserData(sourceData: any[]) { const migrationResults = { successful: 0, failed: 0, errors: [] as string[], }; // Process in batches for better performance const batchSize = 100; const batches = Enumerable.from(sourceData) .Select((item, index) => ({ item, index })) .GroupBy((x) => Math.floor(x.index / batchSize)) .Select((group) => group.Select((x) => x.item).ToArray()) .ToArray(); for (const batch of batches) { try { await this.repository.user.Transaction(async (tx) => { const processedBatch = await AsyncEnumerable.from(batch) .Where( async (user) => await this.validateUserData(user), ) .Select( async (user) => await this.transformUserData(user), ) .ToArrayAsync(); // Create users in batch const results = await tx.CreateMany(processedBatch); migrationResults.successful += results.count; }); } catch (error) { migrationResults.failed += batch.length; migrationResults.errors.push(`Batch failed: ${error.message}`); } } return migrationResults; } private async validateUserData(userData: any): Promise<boolean> { // Validate required fields if (!userData.email || !userData.name) { return false; } // Check for duplicates const existingUser = await this.repository.user .Where({ email: userData.email }) .First(); return existingUser === null; } private async transformUserData(userData: any): Promise<any> { return { email: userData.email.toLowerCase(), name: userData.name.trim(), isActive: userData.status === 'active', createdAt: new Date(userData.created_date || Date.now()), profile: userData.profile ? { create: { bio: userData.profile.bio, avatar: userData.profile.avatar_url, }, } : undefined, }; } } ``` ### Testing Examples ```typescript describe('PrismaCoreService', () => { let service: TestService; let repository: AppRepository; beforeEach(async () => { // Setup test module with in-memory database const module = await Test.createTestModule({ imports: [ PrismaCoreModule.forRoot({ prismaClientProvider: { provide: 'PRISMA_CLIENT', useFactory: () => new PrismaClient({ datasources: { db: { url: 'file:./test.db' }, }, }), }, }), ], providers: [TestService, AppRepository], }).compile(); service = module.get<TestService>(TestService); repository = module.get<AppRepository>(AppRepository); }); it('should filter and transform data correctly', async () => { // Arrange const testData = await repository.test.CreateMany([ { name: 'Test 1', description: 'Description 1', isActive: true }, { name: 'Test 2', description: null, isActive: true }, { name: 'Test 3', description: 'Description 3', isActive: false }, ]); // Act const results = await repository.test .Where({ isActive: true }) .ToEnumerable() .Where((test) => test.description != null) .Select((test) => ({ id: test.id, displayName: test.name.toUpperCase(), hasDescription: true, })) .ToArray(); // Assert expect(results).toHaveLength(1); expect(results[0].displayName).toBe('TEST 1'); expect(results[0].hasDescription).toBe(true); }); it('should handle async operations correctly', async () => { // Test with AsyncEnumerable const data = [1, 2, 3, 4, 5]; const results = await AsyncEnumerable.from(data) .Where(async (x) => x % 2 === 0) .Select(async (x) => x * 2) .ToArrayAsync(); expect(results).toEqual([4, 8]); }); afterEach(async () => { // Clean up test data await repository.test.DeleteMany({}); }); }); ``` ## Next Steps - [Learn about contributing](./contributing.md) - [Review the license](./LICENSE.md) - [Check the API reference](./api-reference.md)