platform-reporting
Extensible business reporting framework using a contributor registry pattern. Feature modules register data contributors that expose datasets, and the central facade handles catalog discovery and query execution.
Package: @breadstone/archipel-platform-reporting
Quick Start
typescript
import { Module } from '@nestjs/common';
import { ReportingModule } from '@breadstone/archipel-platform-reporting';
@Module({
imports: [ReportingModule],
})
export class AppModule {}Contributor Pattern
Each feature module provides an IReportingContributor that exposes the datasets it can report on. Contributors are registered with the ReportingContributorRegistry and discovered by the ReportingFacade.
IReportingContributor
typescript
interface IReportingContributor {
readonly featureKey: string;
listDatasets(): Promise<IReportingDataset[]>;
executeQuery(dataset: IReportingDataset, request: IReportingQuery): Promise<IReportingQueryResult>;
}Implementing a Contributor
typescript
import { Injectable } from '@nestjs/common';
import type {
IReportingContributor,
IReportingDataset,
IReportingQuery,
IReportingQueryResult,
} from '@breadstone/archipel-platform-reporting';
@Injectable()
export class OrderReportingContributor implements IReportingContributor {
public readonly featureKey = 'orders';
constructor(private readonly _prisma: PrismaService) {}
public async listDatasets(): Promise<IReportingDataset[]> {
return [
{
datasetId: 'orders.summary',
featureKey: 'orders',
datasetKey: 'summary',
name: 'Order Summary',
description: 'Aggregated order data by period',
fields: [
{ fieldId: 'period', label: 'Period', type: 'string', isNullable: false },
{ fieldId: 'orderCount', label: 'Order Count', type: 'number', isNullable: false },
{ fieldId: 'revenue', label: 'Revenue', type: 'number', isNullable: false },
{ fieldId: 'avgOrderValue', label: 'Avg. Order Value', type: 'number', isNullable: false },
],
},
{
datasetId: 'orders.by-product',
featureKey: 'orders',
datasetKey: 'by-product',
name: 'Orders by Product',
description: 'Order breakdown by product',
fields: [
{ fieldId: 'productName', label: 'Product', type: 'string', isNullable: false },
{ fieldId: 'quantity', label: 'Quantity Sold', type: 'number', isNullable: false },
{ fieldId: 'revenue', label: 'Revenue', type: 'number', isNullable: false },
],
},
];
}
public async executeQuery(dataset: IReportingDataset, request: IReportingQuery): Promise<IReportingQueryResult> {
// Execute query against database
const rows = await this._prisma.$queryRaw`
SELECT /* ... */
`;
return {
datasetId: dataset.datasetId,
fields: dataset.fields,
rows,
total: rows.length,
};
}
}Registering a Contributor
typescript
import { Module, type OnModuleInit } from '@nestjs/common';
import { ReportingContributorRegistry } from '@breadstone/archipel-platform-reporting';
@Module({
providers: [OrderReportingContributor],
})
export class OrderModule implements OnModuleInit {
constructor(
private readonly _registry: ReportingContributorRegistry,
private readonly _contributor: OrderReportingContributor,
) {}
public onModuleInit(): void {
this._registry.register(this._contributor);
}
}ReportingFacade
Central API for catalog browsing and query execution.
Browse Catalog
typescript
import { ReportingFacade } from '@breadstone/archipel-platform-reporting';
@Controller('api/v1/reports')
export class ReportController {
constructor(private readonly _reporting: ReportingFacade) {}
@Get('datasets')
public async listDatasets(): Promise<IReportingDataset[]> {
return this._reporting.getCatalog();
}
@Get('datasets/:id')
public async getDataset(@Param('id') id: string): Promise<IReportingDataset> {
return this._reporting.getDataset(id);
}
}Execute Query
typescript
@Post('query')
public async runQuery(@Body() body: IReportingQuery): Promise<IReportingQueryResult> {
return this._reporting.executeQuery(body);
}Data Model
IReportingDataset
typescript
interface IReportingDataset {
readonly datasetId: string; // e.g. 'orders.summary'
readonly featureKey: string; // e.g. 'orders'
readonly datasetKey: string; // e.g. 'summary'
readonly name: string; // Human-readable name
readonly description?: string;
readonly fields: IReportingDatasetField[];
}IReportingDatasetField
typescript
interface IReportingDatasetField {
readonly fieldId: string;
readonly label: string;
readonly type: ReportingFieldType; // 'boolean' | 'datetime' | 'json' | 'number' | 'string'
readonly isNullable: boolean;
readonly description?: string;
}IReportingQuery
typescript
interface IReportingQuery {
readonly datasetId: string;
readonly filters?: IReportingQueryFilter[];
readonly sorts?: IReportingQuerySort[];
readonly limit?: number;
readonly offset?: number;
}IReportingQueryResult
typescript
interface IReportingQueryResult {
readonly datasetId: string;
readonly fields: IReportingDatasetField[];
readonly rows: ReadonlyArray<Record<string, unknown>>;
readonly total: number;
}Exports Summary
| Export | Type | Description |
|---|---|---|
ReportingModule | NestJS Module | Global reporting module |
ReportingFacade | Service | Catalog + query execution |
ReportingContributorRegistry | Service | Contributor registration |
IReportingContributor | Interface | Contributor contract |
ReportingDataset | Model | Dataset definition |
ReportingDatasetField | Model | Field definition |
ReportingQuery | Model | Query structure |
ReportingQueryResult | Model | Query result |
ReportingDatasetNotFoundError | Error | Dataset lookup failure |