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

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

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.