Skip to content

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): DynamicModule

Environment Variables

VariableDescriptionRequired
DATABASE_URLPostgreSQL connection stringYes
DB_OPTIMIZEEnable Prisma query optimizationNo
DB_OPTIMIZE_API_KEYPrisma Optimize API keyIf optimize enabled
DB_AUTO_CONNECTIONAuto-connect on module initNo (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 endpoint

Exports Summary

ExportTypeDescription
DatabaseModuleNestJS ModuleforRoot() / register()
DatabaseServiceServiceExtended Prisma client with transactions
PrismaServiceServicePrisma client with extensions
RepositoryBaseAbstract classBase class for repositories
query()FactoryCreate typed query objects
transactionalQuery()FactoryCreate transactional query objects
paginator()FactoryCreate pagination helper
DatabaseHealthIndicatorHealthPostgreSQL health check
DB (Prisma)NamespaceRe-exported Prisma types

Released under the MIT License.