Skip to content

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-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.

Capacity limit: The registry enforces a maximum of 200 registered contributors. Attempts to exceed this limit are logged as warnings and silently ignored.

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. Each executeQuery() call is subject to a 30-second timeout — queries that exceed this limit throw a ReportingExecutionError.

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

ExportTypeDescription
ReportingModuleNestJS ModuleGlobal reporting module
ReportingFacadeServiceCatalog + query execution
ReportingContributorRegistryServiceContributor registration
IReportingContributorInterfaceContributor contract
ReportingDatasetModelDataset definition
ReportingDatasetFieldModelField definition
ReportingQueryModelQuery structure
ReportingQueryResultModelQuery result
ReportingDatasetNotFoundErrorErrorDataset lookup failure

Released under the MIT License.