Streaming
Output Targets
All five finalization methods — file, Node writable, Web writable, Node readable, Web readable.
The stream builder supports five finalization methods, all mutually exclusive — only one output can be started per workbook instance.
Write to file
await workbook.writeToFile("./output/report.xlsx");
The file is created (or overwritten) at the given path. Intermediate directories must exist.
Pipe to a Node.js writable
const dest = createWriteStream("./report.xlsx");
await workbook.pipeToNode(dest);
Any NodeJS.WritableStream is accepted. The stream is ended automatically when the workbook finishes.
Express response:
import { createExcelSchema, createWorkbookStream } from "@chronicstone/typed-xlsx";
import type { Request, Response } from "express";
const schema = createExcelSchema<{ name: string; amount: number }>()
.column("name", { accessor: "name" })
.column("amount", { accessor: "amount", style: { numFmt: "#,##0.00" } })
.build();
export async function downloadReport(req: Request, res: Response) {
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
);
res.setHeader("Content-Disposition", 'attachment; filename="report.xlsx"');
const workbook = createWorkbookStream({ tempStorage: "file" });
const table = await workbook.sheet("Report").table({ id: "data", schema });
for await (const batch of fetchRows()) {
await table.commit({ rows: batch });
}
await workbook.pipeToNode(res);
}
Fastify response:
import { createExcelSchema, createWorkbookStream } from "@chronicstone/typed-xlsx";
import type { FastifyReply } from "fastify";
const schema = createExcelSchema<{ name: string }>().column("name", { accessor: "name" }).build();
export async function handler(_req: unknown, reply: FastifyReply) {
reply.header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
reply.header("Content-Disposition", 'attachment; filename="report.xlsx"');
const workbook = createWorkbookStream();
const table = await workbook.sheet("Data").table({ id: "rows", schema });
await table.commit({ rows: [{ name: "Ada" }] });
const stream = workbook.toNodeReadable();
return reply.send(stream);
}
Pipe to a Web Streams writable
const { writable } = new TransformStream<Uint8Array, Uint8Array>();
await workbook.pipeTo(writable);
Hono / Cloudflare Workers response:
import { createExcelSchema, createWorkbookStream } from "@chronicstone/typed-xlsx";
const schema = createExcelSchema<{ name: string }>().column("name", { accessor: "name" }).build();
export default {
async fetch(_req: Request): Promise<Response> {
const workbook = createWorkbookStream({ tempStorage: "memory" });
const table = await workbook.sheet("Data").table({ id: "rows", schema });
await table.commit({ rows: [{ name: "Ada" }] });
const { readable, writable } = new TransformStream<Uint8Array>();
workbook.pipeTo(writable); // don't await — starts async
return new Response(readable, {
headers: {
"Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"Content-Disposition": 'attachment; filename="report.xlsx"',
},
});
},
};
Get a Node.js Readable
const readable = workbook.toNodeReadable();
// pipe to any further transform or upload
readable.pipe(process.stdout);
Get a Web ReadableStream
const readable = workbook.toReadableStream();
One output per workbook
Calling any finalization method a second time throws an error.
await workbook.writeToFile("./a.xlsx");
// Calling again would throw: "Workbook stream output has already started."
If you need the same data in two places, finalize once and copy the file, or pipe through a PassThrough/tee stream.