platform-database
Prisma ORM integration with PostgreSQL featuring a repository base class, type-safe query pattern, transactional support, and cursor-based pagination.
Package: @breadstone/archipel-platform-database
Quick Start
typescript
import { Module } from '@nestjs/common';
import { DatabaseModule } from '@breadstone/archipel-platform-database';
@Module({
imports: [
// Global registration (recommended)
DatabaseModule.forRoot(),
// Feature-scoped registration
DatabaseModule.register(),
],
})
export class AppModule {}Module Configuration
typescript
interface IDatabaseModuleConfig {
middlewares?: Array<unknown>; // Future extension point
}
// Global setup
DatabaseModule.forRoot(config?: IDatabaseModuleConfig): DynamicModule
// Feature-scoped
DatabaseModule.register(config?: IDatabaseModuleConfig): DynamicModuleEnvironment Variables
| Variable | Description | Required |
|---|---|---|
DATABASE_URL | PostgreSQL connection string | Yes |
DB_OPTIMIZE | Enable Prisma query optimization | No |
DB_OPTIMIZE_API_KEY | Prisma Optimize API key | If optimize enabled |
DB_AUTO_CONNECTION | Auto-connect on module init | No (default: true) |
Repository Pattern
RepositoryBase
All repositories extend RepositoryBase, which provides the execute() method for running typed queries.
typescript
import { Injectable } from '@nestjs/common';
import { RepositoryBase, DatabaseService } from '@breadstone/archipel-platform-database';
import type { Prisma } from '@prisma/client';
@Injectable()
export class OrderRepository extends RepositoryBase<Prisma.OrderDelegate, Prisma.OrderFindManyArgs, unknown> {
constructor(database: DatabaseService) {
super(database.order, database);
}
}Query Pattern
Queries are first-class objects that encapsulate data access logic:
typescript
import { query, type IRepositoryQuery } from '@breadstone/archipel-platform-database';
import type { Prisma } from '@prisma/client';
// Define a query
export const findOrdersByUser = (userId: string) =>
query<Prisma.OrderDelegate, IOrderEntity[]>('findOrdersByUser', async (model) => {
return model.findMany({
where: { userId },
orderBy: { createdAt: 'desc' },
select: {
id: true,
status: true,
total: true,
createdAt: true,
},
});
});
// Define another query
export const findOrderById = (orderId: string) =>
query<Prisma.OrderDelegate, IOrderEntity | null>('findOrderById', async (model) => {
return model.findUnique({
where: { id: orderId },
include: { items: true },
});
});Executing Queries
typescript
@Injectable()
export class OrderService {
constructor(private readonly _orderRepository: OrderRepository) {}
public async getOrdersByUser(userId: string): Promise<IOrderEntity[]> {
return this._orderRepository.execute(findOrdersByUser(userId));
}
public async getOrder(orderId: string): Promise<IOrderEntity | null> {
return this._orderRepository.execute(findOrderById(orderId));
}
}Transactional Queries
For multi-model writes, use transactionalQuery:
typescript
import { transactionalQuery } from '@breadstone/archipel-platform-database';
export const createOrderWithItems = (
userId: string,
items: Array<{ productId: string; quantity: number; price: number }>,
) =>
transactionalQuery<IOrderEntity>('createOrderWithItems', async (tx) => {
const order = await tx.order.create({
data: {
userId,
status: 'pending',
total: items.reduce((sum, i) => sum + i.price * i.quantity, 0),
},
});
await tx.orderItem.createMany({
data: items.map((item) => ({
orderId: order.id,
productId: item.productId,
quantity: item.quantity,
unitPrice: item.price,
})),
});
return order;
});
// Execute in a repository
const order = await this._orderRepository.executeTransactional(createOrderWithItems(userId, items));Database Services
PrismaService
Extended PrismaClient with Prisma Optimize support:
typescript
import { PrismaService } from '@breadstone/archipel-platform-database';
// Typically injected via DatabaseModule, not used directly
@Injectable()
export class MyService {
constructor(private readonly _prisma: PrismaService) {}
}DatabaseService
High-level database operations extending PrismaService:
typescript
import { DatabaseService } from '@breadstone/archipel-platform-database';
@Injectable()
export class TransferService {
constructor(private readonly _db: DatabaseService) {}
public async transfer(fromId: string, toId: string, amount: number): Promise<void> {
await this._db.transactionCallback(async (tx) => {
await tx.account.update({
where: { id: fromId },
data: { balance: { decrement: amount } },
});
await tx.account.update({
where: { id: toId },
data: { balance: { increment: amount } },
});
});
}
}Pagination
Cursor-based pagination utility:
typescript
import { paginator, type IPaginatedResult } from '@breadstone/archipel-platform-database';
const paginate = paginator({ perPage: 20 });
@Injectable()
export class ProductService {
constructor(private readonly _prisma: PrismaService) {}
public async listProducts(page: number): Promise<IPaginatedResult<IProductEntity>> {
return paginate<IProductEntity>(
this._prisma.product,
{ where: { isActive: true }, orderBy: { name: 'asc' } },
{ page, perPage: 20 },
);
}
}IPaginatedResult
typescript
interface IPaginatedResult<T> {
data: T[];
meta: {
total: number; // Total matching records
lastPage: number; // Last page number
currentPage: number; // Current page
perPage: number; // Items per page
prev: number | null; // Previous page (null if first)
next: number | null; // Next page (null if last)
};
}Entity Models
Domain entities are defined as interfaces under models/entities/. They mirror the Prisma schema and serve as the type contract between repositories and services.
typescript
// Property names MUST match Prisma schema exactly
interface IOrderEntity {
readonly id: string;
readonly userId: string;
readonly status: string;
readonly total: number;
readonly createdAt: Date;
readonly updatedAt: Date;
}Rules
- Entity properties match Prisma schema property names exactly.
- Entities are pure data — no methods or business logic.
- Entities reference only other entity interfaces or primitives.
- One entity interface per file.
Health Check
typescript
import { DatabaseHealthIndicator } from '@breadstone/archipel-platform-database';
// Automatically registered with the HealthOrchestrator
// Accessible via /health endpointExports Summary
| Export | Type | Description |
|---|---|---|
DatabaseModule | NestJS Module | forRoot() / register() |
DatabaseService | Service | Extended Prisma client with transactions |
PrismaService | Service | Prisma client with extensions |
RepositoryBase | Abstract class | Base class for repositories |
query() | Factory | Create typed query objects |
transactionalQuery() | Factory | Create transactional query objects |
paginator() | Factory | Create pagination helper |
DatabaseHealthIndicator | Health | PostgreSQL health check |
DB (Prisma) | Namespace | Re-exported Prisma types |