Recount
Node.js

Tables

Show the data behind a decision as a table in a Node.js trace, with highlighted rows, summary rows and labelled sections.

The recount.table() builder constructs a single TableData object. Call .build() to get a value ready to pass directly as span input or output.

Basic usage

recount.addSpan('table', 'Policy checks', {
  output: recount
    .table('Validation Results')
    .fromRecords([
      { check: 'Policy active', result: 'Pass' },
      { check: 'Cover active at incident', result: 'Pass' },
      { check: 'Comprehensive cover', result: 'Pass' }
    ])
    .highlightRows((row) => (row.result === 'Pass' ? 'success' : 'danger'))
    .build()
});

recount.table(title?)

Returns a TableBuilder. Chain methods and call .build() to get a TableData object, ready to use as span input or output.

Data sources

.fromRecords(data)

Build from an array of objects. Column names are the object keys as-is.

recount
  .table('Users')
  .fromRecords([
    { name: 'Alice', score: 98.5 },
    { name: 'Bob', score: 72.1 }
  ])
  .build();

.fromObject(data)

Build a key-value table from a plain object. Renders as two columns: Key and Value.

recount
  .table('Config')
  .fromObject({ timeout: 5000, retries: 3, env: 'production' })
  .build();

Column selection

.pick(...keys)

Only include the specified columns, in order.

.pick('name', 'email', 'role')

Labels & formatting

.label(map)

Override column display labels.

.label({ usageKwh: 'Usage (kWh)', ratePerKwh: 'Rate ($/kWh)' })

.round(decimals)

Round all numeric values to decimals decimal places.

.round(2)

.transformValues(map)

Apply a transform function to values in specific columns.

.transformValues({
  status: (v) => String(v).toUpperCase(),
  createdAt: (v) => new Date(v as string).toLocaleDateString(),
})

Highlighting

.highlightColumn(key, highlight)

Apply a highlight color to an entire column header and cells.

.highlightColumn('revenue', 'success')

.highlightRows(fn)

Apply a per-row highlight based on the row's data.

.highlightRows((row) => {
  if ((row.errorRate as number) > 0.1) return 'danger';
  if ((row.errorRate as number) > 0.05) return 'warn';
  return null;
})

.highlightCells(key, fn)

Apply a per-cell highlight for a specific column.

.highlightCells('status', (v) => {
  if (v === 'error') return 'danger';
  if (v === 'ok') return 'success';
  return null;
})

Highlight values: 'danger' | 'warn' | 'success' | 'info' | 'muted'


Column spans

.spanColumn(key, span)

Make a column header span multiple columns.

.spanColumn('metrics', 3)

Extra rows

.appendRow(cells, highlight?)

Append a summary row at the bottom of the table.

.appendRow(['Total', 450, 890], 'info')

Use recount.spanCell(value, span) to create a cell that spans multiple columns:

.appendRow([recount.spanCell('Grand total: $1,234', 4)], 'success')

.summary(stats)

Define stats shown when this table is collapsed in the dashboard. Each stat has a text string and an optional highlight color.

.summary([
  { text: '$64.70', highlight: 'info' },
  { text: '28.8% less', highlight: 'success' },
])

Highlight values: 'danger' | 'warn' | 'success' | 'info' | 'muted'

.buildSection()

Returns a TableSection (rows and optional highlights/summary, without column definitions). Used as input to recount.tableSections() when building sectioned tables.

const section = recount
  .table('Active policies')
  .fromRecords(activePolicies)
  .highlightRows((row) => (row.overdue ? 'danger' : null))
  .buildSection();

recount.tableSections(options)

Builds a single table with named sections that share the same columns. Use this when you want to group rows under labelled headings within one table span.

recount.addSpan('table', 'Plan comparison', {
  output: recount.tableSections({
    title: 'Coverage breakdown',
    columns: ['Benefit', 'Current plan', 'Recommended plan'],
    sections: [
      recount
        .table('Medical')
        .fromRecords([
          {
            Benefit: 'Hospital',
            'Current plan': '$500',
            'Recommended plan': '$250'
          },
          {
            Benefit: 'Specialist',
            'Current plan': 'Not covered',
            'Recommended plan': '$50'
          }
        ])
        .highlightRows((row) =>
          row['Recommended plan'] === 'Not covered' ? 'warn' : null
        ),
      recount.table('Extras').fromRecords([
        {
          Benefit: 'Dental',
          'Current plan': 'Not covered',
          'Recommended plan': '80%'
        },
        { Benefit: 'Optical', 'Current plan': '50%', 'Recommended plan': '80%' }
      ])
    ]
  })
});

Options

OptionTypeDescription
titlestringOptional title for the whole table
columnsTableColumn[]Column definitions shared across all sections. Can be omitted when the first section is a TableBuilder; columns are derived from it automatically
sections(TableBuilder | TableSection)[]At least one section. Pass TableBuilder instances (built with .buildSection() internally) or raw TableSection objects

When columns is omitted, the first section must be a TableBuilder instance, not a plain object.

Raw TableSection objects

Sections can also be plain objects instead of builder instances:

recount.tableSections({
  columns: ['Region', 'Claims', 'Avg settlement'],
  sections: [
    {
      title: 'Q1',
      rows: [
        ['NSW', 142, '$4,200'],
        ['VIC', 98, '$3,800']
      ]
    },
    {
      title: 'Q2',
      rows: [
        ['NSW', 167, '$4,500'],
        ['VIC', 110, '$4,100']
      ]
    }
  ]
});

recount.spanCell(value, span)

Creates a cell that spans span columns. Only useful inside .appendRow().

recount.spanCell('Total Cost: $99.00', 3);

Multiple tables

To display multiple tables that share the same columns as labelled sections, use recount.tableSections().

To display distinct tables together, use a group span. Each table is its own span inside the group:

recount.addGroup('Claim report', (g) => {
  g.span('table', 'Summary', {
    output: recount
      .table('Summary')
      .fromObject({
        claimId: 'CLM-2024-03941',
        decision: 'Approved',
        settlement: '$6,840.00'
      })
      .build()
  });

  g.span('table', 'Breakdown', {
    output: recount
      .table('Breakdown')
      .fromRecords([
        { item: 'Vehicle repairs', amount: 5200 },
        { item: 'Rental car', amount: 840 },
        { item: 'Medical', amount: 800 }
      ])
      .round(2)
      .build()
  });
});

Shorthand exports

For simple cases where you don't need to chain builder methods, two direct exports are available:

recount.tableFromRecords({ data, title? })

Equivalent to recount.table(title).fromRecords(data).build(). Returns TableData directly.

recount.addSpan('table', 'Policy checks', {
  output: recount.tableFromRecords({
    title: 'Validation Results',
    data: [
      { check: 'Policy active', result: 'Pass' },
      { check: 'Cover active at incident', result: 'Pass' }
    ]
  })
});

recount.tableFromObject({ data, title? })

Equivalent to recount.table(title).fromObject(data).build(). Returns TableData directly.

recount.addSpan('table', 'Claim summary', {
  output: recount.tableFromObject({
    title: 'Claim Details',
    data: {
      claimId: 'CLM-2024-03941',
      vehicle: '2019 Toyota Camry',
      decision: 'Approved'
    }
  })
});

On this page