# Server-Side Pivot
URL: https://pro.rv-grid.com/guides/pivot/server-side/server-side/
Source: src/content/docs/guides/pivot/server-side/server-side.mdx
Description: Connect RevoGrid Pivot to a remote analytical engine without changing the public Pivot configuration model.
Server-side Pivot is for datasets that are too large, too sensitive, or too analytically expensive to fully materialize on the client. RevoGrid still owns the viewport and interaction model, but the analytical engine owns aggregation, windowing, drilldown facts, and cache-aware execution.

For raw type signatures, see [Pivot API](/api/pivot/). For the full request and store contract, continue with [Remote API Reference](/guides/pivot/remote-api-reference/).

## Architecture

The key boundary is:

```txt
Pivot UI / RevoGrid Plugin
        ->
  Pivot engine adapter
        ->
  remote store or analytical backend
```

In practical terms:

- `PivotPlugin` receives the direct `pivot` config
- `pivot.engine.mode = 'server'` switches the plugin to remote loading
- `PivotEngineAdapter` or `PivotRemoteStore` handles the analytical request
- the grid only receives the visible block to render

## Why The Public Config Stays The Same

The core Pivot model does not change:

- `rows`
- `columns`
- `values`
- `totals`
- drill-down related UI state

That is deliberate. The UI should not need one config model for client Pivot and another for server Pivot.

## Basic Setup

```ts
import {
  PivotPlugin,
  HttpPivotRemoteStore,
  type PivotConfig,
} from '@revolist/revogrid-enterprise';
import { PaginationPlugin } from '@revolist/revogrid-pro';

const remoteStore = new HttpPivotRemoteStore({
  baseUrl: 'https://example.internal',
  tenantId: 'tenant-a',
  datasetWatermark: 'sales-v42',
  authProvider: async () => ({
    Authorization: `Bearer ${await getToken()}`,
  }),
});

const pivot: PivotConfig = {
  dimensions: [...],
  rows: ['Category', 'Subcategory'],
  columns: ['Date'],
  values: [{ prop: 'SalesAmount', aggregator: 'sum' }],
  totals: { grandTotal: true, subtotals: true },
  collapsed: true,
  engine: {
    mode: 'server',
    remoteStore,
    viewId: 'sales-by-region',
    fieldsVersion: 'sha256:fields-v1',
    rowAxis: { },
    columnAxis: { },
  },
};

// PaginationPlugin drives row-axis paging automatically.
// PivotPlugin reads itemsPerPage as rowAxis.limit and writes
// response.rowAxis.totalCount back to pagination.total after each load.
grid.plugins = [PivotPlugin, PaginationPlugin];
grid.pivot = pivot;
grid.additionalData = {
  pagination: {
    itemsPerPage: 50,
    initialPage: 0,
    total: 0,         // auto-populated from the first server response
  },
};
```

## Pagination With Remote Pivot

`PivotPlugin` integrates directly with `PaginationPlugin`. When both plugins are active:

- `pagination.itemsPerPage` becomes the row-axis page size (`rowAxis.limit`).
- When the user changes page, `PivotPlugin` reloads the server window at the new `rowAxis.offset`.
- After each successful load, `response.rowAxis.totalCount` is written back to `pagination.total` so the pagination panel shows the correct count.
- Changing the pivot structure (rows, columns, values) resets the offset to 0 automatically.

You do not need to handle page-change events yourself. Drop both plugins into `grid.plugins`, bind the Pivot config to `grid.pivot`, and add `pagination` to `additionalData`.

## What RevoGrid Owns vs What The Engine Owns

RevoGrid and `PivotPlugin` own:

- visible grid rendering
- generated RevoGrid columns
- visible row block
- grouped-row metadata used by current drill-down UI
- cancellation and last-response-wins behavior in the plugin lifecycle

The remote engine or backend owns:

- selector validation
- filter execution
- analytical grouping
- summary execution
- row-axis and column-axis windows
- drilldown fact pages
- cache policy and invalidation

## Request Flow

1. `PivotPlugin` sees `engine.mode = 'server'`.
2. It builds a `PivotLoadRequest` from the public Pivot config and current window hints.
3. The adapter or remote store executes the request.
4. The response is materialized into generated columns, visible rows, and pinned totals.
5. RevoGrid renders only the visible analytical window.

## Field Registry And Planner

- Public selectors must be resolved through a field registry.
- Backend expressions must come from the registry, never from client strings.
- Query planning belongs behind a planner abstraction so SQL, OLAP, or warehouse backends can share the same request model.

This repository already includes:

- a field registry model
- request normalization and limit enforcement
- a typed error model
- an SQL-oriented logical planner shape
- a framework-agnostic `HttpPivotRemoteStore`

## Complete Backend Implementation

A production Pivot backend needs to translate the analytical request into your engine's native query format. This example demonstrates a TypeScript handler using the core request and response types.

```ts
import {
  type PivotLoadRequest,
  type PivotLoadResponse,
} from '@revolist/revogrid-enterprise';

/**
 * Handle POST /api/pivot/load
 */
export async function handlePivotLoad(req: Request): Promise<PivotLoadResponse> {
  const request = req.body as PivotLoadRequest;
  const { loadOptions, viewport, viewId, fieldsVersion, uiState } = request;

  // 1. Authenticate and resolve tenant
  const tenantId = await getTenant(req);

  // 2. Extract analytical parameters
  const {
    rows = [],           // Group descriptors for rows
    columns = [],        // Group descriptors for columns
    filter,              // Complex filter expression tree
    sort = [],           // Sorting directives
    totalSummary = [],   // Grand-total measures
    groupSummary = [],   // Group-level measures (for groupAggregations)
  } = loadOptions;

  // 3. Extract viewport windows
  const { rowAxis, columnAxis } = viewport;

  // 4. Execute analytical query (e.g., against SQL or OLAP)
  const result = await myAnalyticalEngine.execute({
    tenantId,
    viewId,
    fieldsVersion,
    rows,
    columns,
    filter,
    sort,
    measures: [...totalSummary, ...groupSummary],
    rowWindow: rowAxis,
    columnWindow: columnAxis,
    // uiState.rowHeaderLayout, uiState.showTotalsPrior, etc.
  });

  // 5. Build the response
  return {
    data: result.cells,           // REQUIRED: Visible analytical payload
    summary: result.totals,       // OPTIONAL: Grand-total or pinned rows
    rowAxis: {
      totalCount: result.rowCount, // REQUIRED: Drives scrollbars/pagination
    },
    columnAxis: {
      paths: result.colPaths,      // REQUIRED: Defines the generated headers
    },
    // Optional: Grouped-row aggregate payloads (if groupSummary requested)
    groupAggregates: result.groupSummaries,
    meta: {
      cacheStatus: result.fromCache ? 'hit' : 'miss', // Used for telemetry
    },
  };
}
```

## Current Scope

Server-side Pivot is a powerful but complex feature. The standard `PivotPlugin` implements the core windowing and materialization logic, but some advanced scenarios require custom engine adapters:

- **Implemented**: Row-axis windowing, generated columns from `paths`, tree-mode row headers, grouped aggregations, and grand totals.
- **Custom Adapters**: Complex sorting-by-summary or calculated measures typically require a custom `PivotEngineAdapter` to map your specific backend capabilities to the Pivot contract.
- **Not implemented here**: Infinite horizontal scrolling (past the requested column window) or full server-side column grouping collapse (currently managed on client).

## Recommended Reading

- [Remote API Reference](/guides/pivot/remote-api-reference/) — Detailed JSON schema for requests and responses.
- [Caching And Cache Keys](/guides/pivot/caching/) — How to ensure deterministic and efficient analytical execution.
- [OLAP Best Practices](/guides/pivot/olap-best-practices/) — Performance and security tips for backend implementations.
