npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bways-report

v0.1.5

Published

Enterprise-grade, metadata-driven reporting engine and visual page builder for Angular 18+ ERP applications.

Readme

BWays-Report — Integration & Developer Guide

bways-report is an enterprise-grade, metadata-driven reporting platform and visual dynamic page builder for Angular 18+ business & ERP applications.

It allows non-developers to visually design reporting pages with filters, KPI metric cards, charts, and grids. The report configuration is saved purely as JSON metadata (ReportDefinition), and the runtime engine dynamically instantiates the reactive forms, controls, data queries, and results grid on the fly.


npm version license

Table of Contents

  1. Automated Setup (ng add)
  2. Quick Start (Rule of 1)
  3. Configuration Options & Zero-Backend Dev
  4. Implementing Host Adapters (Advanced)
  5. Routing & ERP Navigation Integration
  6. Drill-Down Drill-Through & Slide-Over Drawers
  7. Starter Blueprints Gallery & Branded Exports

1. Automated Setup (ng add)

Run the Angular CLI schematic to configure your application in seconds:

ng add bways-report

This automatically installs required dependencies and adds provideReporting() to your src/app/app.config.ts.


2. Quick Start (Rule of 1)

A. 1-Line App Setup

In your Angular 18+ application:

// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideReporting } from 'bways-report';

export const appConfig: ApplicationConfig = {
  providers: [
    // 1-line setup: automatically hooks up HTTP adapters, UltraGridComponent, and session headers
    provideReporting({ apiBaseUrl: '/api/reporting' }),
  ],
};

Zero-Backend / Local Storage Mode: For local prototyping, offline demos, or frontend-only dev, use:

provideReporting(withLocalStorage()); // seeds built-in blueprints and saves to browser localStorage

B. 1-Tag Runtime Page

Render any saved report with its dynamic filter form, auto-bound stored procedure results, top toolbar, export actions, and saved view presets:

import { Component } from '@angular/core';
import { RptReportComponent } from 'bways-report';

@Component({
  standalone: true,
  imports: [RptReportComponent],
  template: `
    <!-- 1 tag to run and render the full report page -->
    <rpt-report [id]="'sales-ledger'" />
  `,
})
export class SalesReportPage {}

C. 1-Tag Visual Report Designer

Embed the full drag-and-drop report builder into your ERP settings or admin portal:

import { Component } from '@angular/core';
import { RptReportDesignerComponent } from 'bways-report';

@Component({
  standalone: true,
  imports: [RptReportDesignerComponent],
  template: `
    <!-- 1 tag to build, preview, or modify any report -->
    <rpt-report-designer 
      [id]="'sales-ledger'" 
      (saved)="onReportSaved($event)" 
    />
  `,
})
export class ReportBuilderPage {
  onReportSaved(definition: any) {
    console.log('Report saved successfully:', definition);
  }
}

D. Saved Views & Presets in JSON

When users filter, reorder, group, or format columns in the grid, they can click "+ Save View" to persist their customized view preset. Presets are saved directly into the report's JSON:

{
  "grid": {
    "dataSourceId": "ds-sales",
    "columns": [...],
    "options": {
      "presets": [
        {
          "id": "preset_1728200000",
          "name": "Q3 High Value Clients",
          "gridState": {
            "filters": { "amount": { "operator": "gte", "value": 50000 } },
            "columnOrder": ["invoiceId", "amount", "customerName"]
          }
        }
      ]
    }
  }
}

Users can switch between views seamlessly from the top dropdown, keeping all formatting and custom filters intact.


Backend Integration: Mounting BwaysReportingModule in NestJS

To power the built-in HTTP adapters, mount BwaysReportingModule directly inside your existing NestJS host application:

// app.module.ts (in your NestJS backend)
import { Module } from '@nestjs/common';
import { BwaysReportingModule } from '@bways/reporting-backend';

@Module({
  imports: [
    BwaysReportingModule.forRoot({
      security: {
        allowedSchemas: ['dbo', 'reporting'],
        disallowRawSql: true,                    // SQL injection prevention
        validateEntitiesAgainstCatalog: true,    // Checks sys.procedures catalog
      },
    }),
  ],
})
export class AppModule {}

Option B: Custom Adapters (Full Manual Control)

If you don't use the standard backend endpoints, you can implement your own adapters:

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideBwaysReport({
      gridComponent: BWaysGridComponent,
      dataSourceAdapter: MyCustomDataSourceAdapter,
      persistenceAdapter: MyCustomPersistenceAdapter,
      masterDataProvider: MyCustomMasterDataProvider,
      defaultPageSize: 25,
      enableDesigner: true,
    }),
  ],
};

3. Implementing Host Adapters

The library is decoupled from your backend. You provide your business application's API handlers by extending three abstract contracts:

A. Data Source Adapter

Responsible for executing report queries and returning tabular result rows:

// src/app/services/erp-datasource.adapter.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { ReportDataSourceAdapter, ReportDataSourceDefinition } from 'bways-report';

@Injectable({ providedIn: 'root' })
export class ErpDataSourceAdapter extends ReportDataSourceAdapter {
  private readonly http = inject(HttpClient);

  execute(
    dataSource: ReportDataSourceDefinition,
    parameters: Record<string, unknown>
  ): Observable<unknown[]> {
    // Send report filter parameters to your backend API endpoint
    return this.http.post<unknown[]>(dataSource.endpoint, parameters);
  }
}

B. Persistence Adapter

Responsible for saving, loading, listing, and publishing report metadata definitions (ReportDefinition):

// src/app/services/erp-persistence.adapter.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import {
  ReportPersistenceAdapter,
  ReportDefinition,
  ReportSummary,
} from 'bways-report';

@Injectable({ providedIn: 'root' })
export class ErpPersistenceAdapter extends ReportPersistenceAdapter {
  private readonly http = inject(HttpClient);
  private readonly baseUrl = '/api/reports';

  load(reportId: string): Observable<ReportDefinition> {
    return this.http.get<ReportDefinition>(`${this.baseUrl}/${reportId}`);
  }

  save(report: ReportDefinition): Observable<ReportDefinition> {
    return this.http.post<ReportDefinition>(this.baseUrl, report);
  }

  list(): Observable<ReportSummary[]> {
    return this.http.get<ReportSummary[]>(this.baseUrl);
  }

  delete(reportId: string): Observable<void> {
    return this.http.delete<void>(`${this.baseUrl}/${reportId}`);
  }

  publish(reportId: string, version: number): Observable<void> {
    return this.http.post<void>(`${this.baseUrl}/${reportId}/publish`, { version });
  }
}

C. Master Data Provider

Supplies option lists for dropdowns and lookups (e.g., Customers, Warehouses, Statuses):

// src/app/services/erp-masterdata.provider.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { MasterDataProvider, DropdownOption } from 'bways-report';

@Injectable({ providedIn: 'root' })
export class ErpMasterDataProvider extends MasterDataProvider {
  private readonly http = inject(HttpClient);

  getOptions(
    masterName: string,
    filter?: Record<string, unknown>
  ): Observable<DropdownOption[]> {
    return this.http.get<DropdownOption[]>(`/api/master-data/${masterName}`, {
      params: filter as any,
    });
  }
}

4. Routing & ERP Navigation Integration

Dynamic Route Generation

Mount all report runtime and designer routes with a single call in your app.routes.ts:

// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { createReportRoutes } from 'bways-report';

export const routes: Routes = [
  {
    path: 'reports',
    children: createReportRoutes({
      // Optional: enforce auth or role guards
      runtimeGuards: [],
      designerGuards: [], // e.g. [AdminGuard]
      enableDesignerRoutes: true, // creates 'new' and ':reportId/edit' routes
    }),
  },
];

This instantly creates:

  • /reports/:reportId — Renders RoutedReportRuntimeComponent (resolves report via reportResolver, presents interactive filters, KPI metrics, chart, and result grid).
  • /reports/:reportId/edit — Renders RoutedReportDesignerComponent (visual drag-and-drop builder loaded with current definition).
  • /reports/new — Renders RoutedReportDesignerComponent initialized with a blank template.

Categorized ERP Sidebar / Header Navigation

Use ReportMenuService to generate hierarchical sidebar menus grouped by category (e.g. Finance, Sales, Inventory):

// src/app/components/sidebar/erp-sidebar.component.ts
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { ReportMenuService } from 'bways-report';

@Component({
  selector: 'erp-sidebar',
  standalone: true,
  imports: [CommonModule, RouterModule],
  template: `
    <aside class="sidebar">
      <input
        type="search"
        placeholder="Search reports..."
        (input)="onSearch($event)"
      />

      @for (group of menuService.menuGroups(); track group.category) {
        <div class="menu-group">
          <h4>{{ group.displayName }}</h4>
          <ul>
            @for (item of group.items; track item.id) {
              <li>
                <a [routerLink]="item.route" routerLinkActive="active">
                  {{ item.name }}
                  @if (item.isDraft) {
                    <span class="badge">Draft</span>
                  }
                </a>
              </li>
            }
          </ul>
        </div>
      }
    </aside>
  `,
})
export class ErpSidebarComponent {
  readonly menuService = inject(ReportMenuService);

  onSearch(event: Event): void {
    const query = (event.target as HTMLInputElement).value;
    this.menuService.setSearchQuery(query);
  }
}

5. Embedding Components Directly

If you prefer embedding components inside existing pages instead of routing:

Report Runtime (Interactive Report Page)

import { Component } from '@angular/core';
import { ReportRuntimeComponent, ReportDefinition } from 'bways-report';

@Component({
  selector: 'my-report-page',
  standalone: true,
  imports: [ReportRuntimeComponent],
  template: `
    <!-- Option A: Load by ID via persistence adapter -->
    <rpt-runtime [reportId]="'sales-register-2024'" />

    <!-- Option B: Pass ReportDefinition directly -->
    <!-- <rpt-runtime [report]="customDefinition" /> -->
  `,
})
export class MyReportPageComponent {}

Visual Drag-and-Drop Designer

import { Component } from '@angular/core';
import { DesignerComponent, ReportDefinition } from 'bways-report';

@Component({
  selector: 'my-designer-page',
  standalone: true,
  imports: [DesignerComponent],
  template: `
    <rpt-designer
      [initialReport]="reportDefinition"
      (save)="onSaveReport($event)"
    />
  `,
})
export class MyDesignerPageComponent {
  reportDefinition: ReportDefinition = /* ... */;

  onSaveReport(updatedReport: ReportDefinition): void {
    console.log('Saved report schema:', updatedReport);
  }
}

6. Grid & Chart Bridge Integration (BWays-Grid)

Using BWays-Grid or Custom Grid

Provide your grid component via the GRID_COMPONENT token in provideBwaysReport():

provideBwaysReport({
  gridComponent: BWaysGridComponent, // or AgGridWrapperComponent
})

ReportGridBridgeComponent dynamically passes the data rows, columns configuration, sorting, and aggregate settings directly to your grid component. If no grid is provided, bways-report renders its high-performance built-in tabular grid with pagination and column sorting.

Using Chart Components

Provide a chart component wrapper (Chart.js, ApexCharts, Highcharts) via chartComponent:

provideBwaysReport({
  chartComponent: MyHostChartComponent,
})

If not provided, the built-in SVG visual chart renderer outputs crisp, responsive Bar charts, Donut charts, and Line/Area charts with zero external dependencies.


7. Theming & CSS Custom Properties

bways-report uses CSS custom properties (--rpt-*) throughout all components, ensuring seamless alignment with your ERP application's design system:

/* In your host application's styles.scss or theme file */
:root {
  /* Brand Colors */
  --rpt-primary: #2563eb;
  --rpt-primary-hover: #1d4ed8;
  --rpt-primary-focus: rgba(37, 99, 235, 0.25);

  /* Surface & Text */
  --rpt-surface-bg: #ffffff;
  --rpt-bg-subtle: #f8fafc;
  --rpt-text-color: #0f172a;
  --rpt-text-muted: #64748b;
  --rpt-border-color: #e2e8f0;

  /* Geometry & Typography */
  --rpt-radius-md: 6px;
  --rpt-radius-lg: 12px;
  --rpt-font-family: 'Inter', system-ui, sans-serif;
  --rpt-font-size-base: 0.875rem;

  /* Status Colors */
  --rpt-success-color: #10b981;
  --rpt-warning-color: #f59e0b;
  --rpt-error-color: #ef4444;
}

8. Registering Custom Host Controls

You can register custom Angular components (e.g., custom slider, multi-select, rich customer picker) into the ComponentRegistry so they appear in the Visual Designer palette and render dynamically at runtime:

import { ComponentRegistry, ComponentMetadata } from 'bways-report';
import { MyCustomPickerComponent } from './my-custom-picker.component';

export function configureCustomControls(registry: ComponentRegistry): void {
  registry.register({
    type: 'customer-picker',
    displayName: 'Customer Picker',
    category: 'input',
    icon: 'person_search',
    component: MyCustomPickerComponent,
    createDefaultConfig: () => ({
      id: 'cust-picker',
      type: 'customer-picker',
      label: 'Select Customer',
      properties: { placeholder: 'Search customer...' },
      layout: { columnSpan: 4 },
    }),
    propertySchema: [
      { key: 'label', label: 'Label', type: 'string', required: true, group: 'General' },
      { key: 'key', label: 'Field Name (Key)', type: 'string', required: true, group: 'General' },
      { key: 'required', label: 'Required', type: 'boolean', group: 'Validation' },
    ],
  });
}

9. Exporting Data (CSV & JSON)

Use ReportExportService for one-click file generation:

import { Component, inject } from '@angular/core';
import { ReportExportService } from 'bways-report';

@Component({
  selector: 'export-actions',
  standalone: true,
  template: `
    <button (click)="downloadCsv()">Download CSV</button>
    <button (click)="downloadJson()">Download Metadata</button>
  `,
})
export class ExportActionsComponent {
  private readonly exportService = inject(ReportExportService);

  downloadCsv(): void {
    const data = [
      { invoiceNo: 'INV-001', customer: 'Acme Corp', total: 4950 },
      { invoiceNo: 'INV-002', customer: 'Globex Inc', total: 14080 },
    ];

    this.exportService.exportCsv(data, {
      fileName: 'invoices-export.csv',
      columns: [
        { field: 'invoiceNo', header: 'Invoice #' },
        { field: 'customer', header: 'Customer' },
        {
          field: 'total',
          header: 'Total ($)',
          formatter: (val: number) => `$${val.toLocaleString()}`
        },
      ],
      addBom: true, // Includes UTF-8 BOM so Microsoft Excel renders international characters properly
    });
  }

  downloadJson(): void {
    this.exportService.exportJson(myReportDefinition, {
      fileName: 'report-schema.json',
      pretty: true,
    });
  }
}

License

MIT © BWays Technologies