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
npm install @breadstone/archipel-platform-databaseQuick Start
import { Module } from '@nestjs/common';
import { ConfigModule } from '@breadstone/archipel-platform-core';
import { DatabaseModule, PLATFORM_DATABASE_CONFIG_ENTRIES } from '@breadstone/archipel-platform-database';
@Module({
imports: [
ConfigModule.register('database', PLATFORM_DATABASE_CONFIG_ENTRIES),
// Global registration (recommended)
DatabaseModule.forRoot(),
// Feature-scoped registration
DatabaseModule.register(),
],
})
export class AppModule {}Module Configuration
interface IDatabaseModuleConfig {
accelerateUrl?: string; // Prisma Accelerate URL (overrides DB_ACCELERATE_URL env var)
middlewares?: Array<unknown>; // Future extension point
}
// Global setup
DatabaseModule.forRoot(config?: IDatabaseModuleConfig): DynamicModule
// Feature-scoped
DatabaseModule.register(config?: IDatabaseModuleConfig): DynamicModuleConfig Keys
Register all database config keys with PLATFORM_DATABASE_CONFIG_ENTRIES:
import { ConfigModule } from '@breadstone/archipel-platform-core';
import { PLATFORM_DATABASE_CONFIG_ENTRIES } from '@breadstone/archipel-platform-database';
ConfigModule.register('database', PLATFORM_DATABASE_CONFIG_ENTRIES);Environment Variables
| Variable | Description | Required | Default |
|---|---|---|---|
DATABASE_URL | PostgreSQL connection string | Yes | — |
DB_ACCELERATE_URL | Prisma Accelerate proxy URL | No | '' |
DB_AUTO_CONNECTION | Auto-connect on module init | No | true |
DB_OPTIMIZE | Enable Prisma Optimize query analysis | No | true |
DB_OPTIMIZE_API_KEY | API key for the Prisma Optimize dashboard | If optimize enabled | '' |
Accelerate resolution order:
accelerateUrlmodule parameter →DB_ACCELERATE_URLenvironment variable → no Accelerate.
Repository Pattern
RepositoryBase
All repositories extend RepositoryBase, which provides the execute() method for running typed queries.
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, database.order);
}
}Query Pattern
Queries are first-class objects that encapsulate data access logic:
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
@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:
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:
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:
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:
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
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.
// 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
The DatabaseHealthIndicator verifies database connectivity via a Prisma ping (SELECT 1). Import it from the /health subpath:
import { DatabaseHealthIndicator } from '@breadstone/archipel-platform-database/health';
import { HealthModule } from '@breadstone/archipel-platform-health';
@Module({
imports: [
DatabaseModule,
HealthModule.withIndicators([DatabaseHealthIndicator]),
],
})
export class AppModule {}| Key | Check | Dependencies |
|---|---|---|
database | Prisma SELECT 1 ping | PrismaHealthIndicator, DatabaseService |
Exports 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 (/health subpath) |
PLATFORM_DATABASE_CONFIG_ENTRIES | Constant | Config entries for ConfigModule |
DB_ACCELERATE_URL | Config Key | Prisma Accelerate URL |
DB_AUTO_CONNECTION | Config Key | Auto-connect toggle |
DB_OPTIMIZE | Config Key | Prisma Optimize toggle |
DB_OPTIMIZE_API_KEY | Config Key | Prisma Optimize API key |
DB (Prisma) | Namespace | Re-exported Prisma types |