v0 → v1
v1 is a complete ground-up rewrite. The xlsx-js-style / SheetJS dependency is gone. Every layer from schema definition to OOXML serialization to ZIP packaging has been rebuilt from scratch. This page covers every breaking change with before/after examples.
Entry point renames
The class-based static constructors are replaced with plain functions.
| v0 | v1 |
|---|---|
ExcelSchemaBuilder.create<T>() | createExcelSchema<T>() |
ExcelBuilder.create() | createWorkbook() |
// v0
import { ExcelSchemaBuilder, ExcelBuilder } from "@chronicstone/typed-xlsx";
const schema = ExcelSchemaBuilder.create<Row>().column(...).build();
const workbook = ExcelBuilder.create();
// v1
import { createExcelSchema, createWorkbook } from "@chronicstone/typed-xlsx";
const schema = createExcelSchema<Row>().column(...).build();
const workbook = createWorkbook();
key → accessor
key accepted only a typed dot-path string — a column's value was always a single field. Any column that needed a computed or multi-field value required a separate transform step.
accessor accepts the same typed dot-path string or a function, at the same definition site. The function form removes the need for transform in the majority of cases.
// v0 — path only, computed values need a separate transform
ExcelSchemaBuilder.create<User>().column("fullName", {
key: "firstName",
transform: (val, row) => `${row.firstName} ${row.lastName}`,
});
// v1 — path or function, unified at the accessor
createExcelSchema<User>().column("fullName", {
accessor: (row) => `${row.firstName} ${row.lastName}`,
header: "Full Name",
});
Path access works exactly as before:
// v0
.column("city", { key: "address.city" })
// v1
.column("city", { accessor: "address.city" })
label → header
// v0
.column("name", { key: "name", label: "Full Name" })
// v1
.column("name", { accessor: "name", header: "Full Name" })
default → defaultValue
// v0
.column("score", { key: "score", default: 0 })
// v1
.column("score", { accessor: "score", defaultValue: 0 })
cellStyle → style
// v0
.column("amount", {
key: "amount",
cellStyle: { numFmt: "$#,##0.00" },
})
// v1
.column("amount", {
accessor: "amount",
style: { numFmt: "$#,##0.00" },
})
headerStyle is unchanged.
CellStyle shape — fill.fgColor → fill.color
v0 used xlsx-js-style's internal style shape. v1 defines and owns its CellStyle type. The only structural change is the fill color key.
// v0 (xlsx-js-style shape)
cellStyle: {
fill: { fgColor: { rgb: "FF0000" } },
font: { bold: true },
}
// v1 (CellStyle)
style: {
fill: { color: { rgb: "FF0000" } },
font: { bold: true },
}
All other style fields (font, border, alignment, numFmt) have the same shape.
transform — signature extended
The transform function gained access to the full row as its second argument. Update any transform that needed row context but was working around the limitation.
// v0 — only the extracted value, no row access
transform: (value, rowIndex) => value.toUpperCase();
// v1 — value, full row, rowIndex
transform: (value, row, rowIndex) => value.toUpperCase();
withTransformers() and withFormatters() removed
Named transformer and formatter registries are gone. Pass the function directly in the column definition, or extract it as a plain TypeScript constant.
// v0
ExcelSchemaBuilder.create<Row>()
.withTransformers({ upper: (v: string) => v.toUpperCase() })
.column("name", { key: "name", transform: "upper" })
.build();
// v1
const upper = (v: string) => v.toUpperCase();
createExcelSchema<Row>()
.column("name", { accessor: "name", transform: (v) => upper(v) })
.build();
Summary API — reducer model
The v0 value function received the full T[] array. This required all rows to be resident at once, making streaming impossible.
v1 uses a three-function reducer (init / step / finalize) that the engine drives row-by-row. This is what enables summary support in stream mode. The cellStyle field on the summary definition is also renamed to style.
// v0
.column("balance", {
key: "balance",
summary: [
{
value: data => data.reduce((acc, row) => acc + row.balance, 0),
format: '"$"#,##0.00',
cellStyle: { font: { bold: true } },
},
{
value: data => data.reduce((acc, row) => acc + row.balance, 0) * 1.2,
format: '"$"#,##0.00',
},
],
})
// v1
.column("balance", {
accessor: "balance",
summary: (summary) => [
summary.cell({
init: () => 0,
step: (acc, row) => acc + row.balance,
finalize: acc => acc,
format: '"$"#,##0.00',
style: { font: { bold: true } },
}),
summary.cell({
init: () => 0,
step: (acc, row) => acc + row.balance,
finalize: acc => acc * 1.2,
format: '"$"#,##0.00',
}),
],
})
If you need a label column, use helpers like summary.label("Total"). Raw SummaryDefinition objects and arrays are still accepted, but the callback-builder form is the preferred public API.
Column selection — include / exclude
v0 used a per-column boolean map. Every column in the schema needed a key in the object, set to true or false.
v1 uses explicit include or exclude arrays. Specify only what you want to show or hide.
// v0 — must enumerate every column
.addTable({
schema,
data: rows,
select: { firstName: true, lastName: true, email: true, internalId: false },
})
// v1 — state only what you care about
.table({
schema,
rows,
select: { include: ["firstName", "lastName", "email"] },
// or equivalently:
// select: { exclude: ["internalId"] },
})
Table input — field renames and method rename
| v0 field / method | v1 equivalent |
|---|---|
.addTable({ ... }) | .table({ ... }) |
data: T[] | rows: T[] |
titleStyle | removed — title is a plain string only |
summary?: boolean | removed — summaries render when defined on columns |
// v0
workbook.sheet("Report").addTable({
schema,
data: rows,
title: "Monthly Report",
titleStyle: { font: { bold: true } },
summary: true,
});
// v1
workbook.sheet("Report").table({
schema,
rows,
title: "Monthly Report", // plain string only, no titleStyle
});
Sheet options
| v0 | v1 |
|---|---|
tablesPerRow | tablesPerRow (unchanged) |
tableSeparatorWidth | tableColumnGap |
rtl on .build() | rightToLeft on .sheet() |
rowHeight | removed |
extraLength | tableColumnGap |
// v0
ExcelBuilder.create()
.sheet("Report", { tablesPerRow: 2, tableSeparatorWidth: 2 })
.addTable(...)
.build({ output: "buffer", rtl: true })
// v1
createWorkbook()
.sheet("Report", { tablesPerRow: 2, tableColumnGap: 2, rightToLeft: true })
.table(...)
.toBuffer()
Output methods
v0 .build() required an output type parameter and returned the result synchronously with a type that depended on that parameter.
v1 separates output into explicit terminal methods on the workbook.
// v0
const buffer = workbook.build({ output: "buffer" });
const uint8 = workbook.build({ output: "uint8array" });
await workbook.build({ output: "file", filePath: "./report.xlsx" });
// v1
const buffer = workbook.toBuffer();
const uint8 = workbook.toUint8Array();
await workbook.writeToFile("./report.xlsx");
Dynamic column groups
The context type is now explicit in the second parameter of the .group() callback. Context is still injected at table creation time via the context field keyed by group id.
// v0
schema.group("dates", (builder, ctx: Record<string, string>) => {
for (const [key, label] of Object.entries(ctx))
builder.column(key, { key: () => ..., label })
})
// v1
createExcelSchema<Row>()
.group("dates", (builder, ctx: Record<string, string>) => {
for (const [key, label] of Object.entries(ctx))
builder.column(key, { accessor: () => ..., header: label })
})
.build()
Stream workbook
createWorkbookStream() is new in v1. There is no equivalent in v0 — the old streaming: true flag on .build() still materialized the full workbook before writing. See the Stream Workbook section for full documentation.
Removed exports
| v0 export | Status in v1 |
|---|---|
ExcelSchemaBuilder | Removed — use createExcelSchema<T>() |
ExcelBuilder | Removed — use createWorkbook() |
TransformersMap | Removed — no longer needed |
FormattersMap | Removed — no longer needed |
FormatterPreset | Removed — no longer needed |
TOutputType | Removed — output is via explicit methods |
ExcelBuildParams | Removed |
ExcelBuildOutput | Removed |
SheetTableBuilder | Removed |
CellStyle is still exported. Its shape changed (see above).
Quick reference
| Change | v0 | v1 |
|---|---|---|
| Schema factory | ExcelSchemaBuilder.create<T>() | createExcelSchema<T>() |
| Workbook factory | ExcelBuilder.create() | createWorkbook() |
| Column field path | key | accessor |
| Column header | label | header |
| Column fallback | default | defaultValue |
| Column style | cellStyle | style |
| Fill color | fill.fgColor.rgb | fill.color.rgb |
| Transform signature | (value, rowIndex) | (value, row, rowIndex) |
| Row array field | data | rows |
| Add table method | .addTable() | .table() |
| Column selection | { col: true/false } | { include: [] } / { exclude: [] } |
| Summary reducer | value: (data) => ... | init / step / finalize |
| Summary style | cellStyle | style |
| Table title style | titleStyle | removed |
| Summary toggle | summary: boolean on table | removed — implicit when columns define summaries |
| Sheet column gap | tableSeparatorWidth / extraLength | tableColumnGap |
| RTL option | .build({ rtl }) | .sheet(name, { rightToLeft }) |
| Buffer output | .build({ output: "buffer" }) | .toBuffer() |
| Uint8Array output | .build({ output: "uint8array" }) | .toUint8Array() |
| File output | .build({ output: "file", filePath }) | .writeToFile(path) |