@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
Markdown
# 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)