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
npm install @breadstone/archipel-platform-reportingQuick Start
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.
Capacity limit: The registry enforces a maximum of 200 registered contributors. Attempts to exceed this limit are logged as warnings and silently ignored.
IReportingContributor
interface IReportingContributor {
readonly featureKey: string;
listDatasets(): Promise<IReportingDataset[]>;
executeQuery(dataset: IReportingDataset, request: IReportingQuery): Promise<IReportingQueryResult>;
}Implementing a Contributor
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
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. Each executeQuery() call is subject to a 30-second timeout — queries that exceed this limit throw a ReportingExecutionError.
Browse Catalog
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
@Post('query')
public async runQuery(@Body() body: IReportingQuery): Promise<IReportingQueryResult> {
return this._reporting.executeQuery(body);
}Data Model
IReportingDataset
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
interface IReportingDatasetField {
readonly fieldId: string;
readonly label: string;
readonly type: ReportingFieldType; // 'boolean' | 'datetime' | 'json' | 'number' | 'string'
readonly isNullable: boolean;
readonly description?: string;
}IReportingQuery
interface IReportingQuery {
readonly datasetId: string;
readonly filters?: IReportingQueryFilter[];
readonly sorts?: IReportingQuerySort[];
readonly limit?: number;
readonly offset?: number;
}IReportingQueryResult
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 |