Liasse

Core concepts

The block model, the builder DSL, the theme system and the Renderer interface.

The document model

A Document has meta and a list of blocks. zod is the source of truth — every type is inferred from a schema, and the same schemas validate API payloads.

interface Document {
  meta: { title: string; author?: string; subject?: string };
  blocks: Block[];
}

The block union:

BlockShape
heading{ text, level: 1 | 2 | 3 }
paragraph{ text, emphasis? }
tablesee Tables
keyValue{ items: { label, value, format? }[] }
section{ title?, blocks: Block[], sheet? }
chart{ chartType, data, x, y, title? } — table fallback in xlsx
image{ src: Uint8Array | string, width?, height? }
spacer{ size }
pageBreak{}

The builder DSL

Build the tree with small helpers, then validate once at the boundary with defineDocument, which throws a typed LiasseValidationError on bad input.

import { defineDocument, heading, paragraph, table, keyValue, kv, section } from "@liasse/core";

const doc = defineDocument({
  meta: { title: "Report" },
  blocks: [
    heading("Summary", { level: 1 }),
    paragraph("Quarterly figures by line item."),
    keyValue([kv("MRR", 622350, "currency"), kv("Accounts", 1284, "number")]),
  ],
});

Tables

table is the workhorse:

table({
  columns: [
    { key: "item", header: "Item" },
    { key: "ca", header: "Revenue", format: "currency", align: "right" },
  ],
  rows: data,
  summary: { ca: "sum" },                              // sum | avg | count
  conditional: { column: "ca", rule: "positiveNegative" },
  xlsx: { freezeHeader: true, sheetName: "Detail" },   // format-specific escape hatch
});
  • format per column: text, number, currency, percent, date.
  • summary emits a real Excel formula row (=SUM, =AVERAGE, =COUNTA).
  • conditional: positiveNegative colors cells using the theme's semantic tokens.
  • xlsx is a typed escape hatch: hints read only by the xlsx adapter, so the core stays format-agnostic.

Sections & sheets

A section with sheet: true becomes its own worksheet; otherwise its blocks flow into the current sheet.

section({ title: "Detail", sheet: true, blocks: [ table({ /* … */ }) ] });

The theme

A theme is a plain, JSON-serializable object. Ship defaultTheme or derive one with defineTheme:

interface Theme {
  palette: {
    primary: string; accent: string;
    neutral: { fg: string; muted: string; border: string; bg: string };
    semantic: { positive: string; negative: string };
  };
  typography: { fontFamily: string; baseSize: number; headingSizes: [number, number, number] };
  table: { headerFill: string; headerFg: string; banding: boolean; borderColor: string };
  spacing: number;
  brand?: { logo?: Uint8Array; headerText?: string; footerText?: string };
}

The adapter translates each token into an exceljs style primitive (a color token → a fill/font.color, etc.). Components never hard-code styling.

Rendering

The Renderer interface is tiny:

interface Renderer {
  format: "xlsx" | "pptx" | "docx" | "pdf";
  render(doc: Document, theme: Theme): Promise<Uint8Array>;
}

@liasse/core exposes a thin render(doc, { format, theme? }) that dispatches to a registered renderer. Importing an adapter registers it:

import { render } from "@liasse/core";
import "@liasse/xlsx"; // registers the "xlsx" renderer
import "@liasse/pptx"; // registers the "pptx" renderer
import "@liasse/docx"; // registers the "docx" renderer
import "@liasse/pdf"; //  registers the "pdf" renderer

const workbook = await render(doc, { format: "xlsx" });
const deck = await render(doc, { format: "pptx" });
const wordDoc = await render(doc, { format: "docx" });
const pdf = await render(doc, { format: "pdf" });
  • @liasse/pptx (pptxgenjs) renders titled sections to slides with overflow handling and native PowerPoint charts.
  • @liasse/docx (the docx library) renders a flowing Word document with real tables and an optional brand header/footer.
  • @liasse/pdf (pdfkit) renders a flowing A4 PDF with paginated tables (the header repeats across pages). This is a general report PDF — Factur-X / PDF-A-3 conformance is a separate module on the roadmap.

In xlsx, exceljs native charts are incomplete, so a chart block renders as a clean data-table fallback (and logs a one-time warning). In pptx, charts are native.

On this page