Writer - Asynchronous XML Stream Writer
Writer - Asynchronous XML Stream Writer
Section titled “Writer - Asynchronous XML Stream Writer”Writer is an asynchronous, stream-based XML writer for non-blocking WritableStream workflows, streaming responses, and real-time XML generation.
For large file or large document output where maximum write throughput matters, prefer WriterSyncSink. The 1GiB writer benchmark shows the sync sink path has the best write throughput while peak RSS stays in the same range as async writing.
Key Features
Section titled “Key Features”- Stream-Based: Writes directly to WritableStream for memory efficiency
- Asynchronous: All methods return promises for non-blocking operations
- Large Document Support: Handles arbitrarily large XML documents when an async stream architecture is required
- Memory Efficient: No need to store entire XML in memory
- Real-time Generation: Perfect for streaming APIs and live data
Note: For small synchronous output, use
WriterSyncas a string builder. For large file output, useWriterSyncSink.
🔧 Quick Start
Section titled “🔧 Quick Start”import { Hono } from 'hono';import { stream } from 'hono/streaming';import { Writer } from 'stax-xml';
const app = new Hono();
app.get('/api/products', (c) => { // Sample product data const products = [ { id: 'P001', name: 'Smartphone', price: 699.99, category: 'Electronics' }, { id: 'P002', name: 'Headphones', price: 199.99, category: 'Electronics' }, { id: 'P003', name: 'Coffee Maker', price: 149.99, category: 'Appliances' } ];
// Set appropriate headers c.header('Content-Type', 'application/xml; charset=utf-8'); c.header('Cache-Control', 'no-cache');
return stream(c, async (stream) => { const writer = new Writer(stream, { prettyPrint: true, indentString: ' ' });
try { // Generate XML directly to stream - all methods need await await writer.writeStartDocument('1.0', 'utf-8'); await writer.writeStartElement('products', { attributes: { count: products.length.toString(), generated: new Date().toISOString() } });
for (const product of products) { await writer.writeStartElement('product', { attributes: { id: product.id, category: product.category } });
await writer.writeStartElement('name'); await writer.writeCharacters(product.name); await writer.writeEndElement();
await writer.writeStartElement('price', { attributes: { currency: 'USD' } }); await writer.writeCharacters(product.price.toString()); await writer.writeEndElement();
await writer.writeEndElement(); // product }
await writer.writeEndElement(); // products await writer.writeEndDocument(); await writer.close(); // Important: close the stream
} catch (error) { console.error('XML generation error:', error); // Note: Error handling in streaming context is limited } });});
export default app;Advanced Features with Namespaces and Custom Entities
Section titled “Advanced Features with Namespaces and Custom Entities”import { Hono } from 'hono';import { stream } from 'hono/streaming';import { Writer } from 'stax-xml';
const app = new Hono();
app.get('/api/catalog', (c) => { const items = [ { id: '001', name: 'Premium Laptop', featured: true }, { id: '002', name: 'Wireless Mouse', featured: false } ];
c.header('Content-Type', 'application/xml; charset=utf-8');
return stream(c, async (stream) => { const writer = new Writer(stream, { prettyPrint: true, indentString: ' ', addEntities: [ { entity: 'company', value: 'Acme Corporation' }, { entity: 'copyright', value: '© 2024' } ], autoEncodeEntities: true });
try { // Write XML with namespaces and custom entities await writer.writeStartDocument('1.0', 'utf-8');
await writer.writeStartElement('catalog', { prefix: 'cat', uri: 'http://example.com/catalog', attributes: { version: '2.0' } }); await writer.writeNamespace('meta', 'http://example.com/metadata');
await writer.writeStartElement('header', { prefix: 'meta' }); await writer.writeStartElement('title'); await writer.writeCharacters('Product Catalog'); await writer.writeEndElement();
await writer.writeStartElement('company'); await writer.writeCharacters('&company;'); // Will be encoded automatically await writer.writeEndElement(); await writer.writeEndElement(); // header
await writer.writeStartElement('products'); for (const item of items) { await writer.writeStartElement('product', { attributes: { id: item.id, featured: item.featured.toString() } });
await writer.writeStartElement('name'); await writer.writeCharacters(item.name); await writer.writeEndElement();
// Self-closing element await writer.writeStartElement('thumbnail', { attributes: { src: `${item.id}.jpg`, alt: 'Product Image' }, selfClosing: true });
await writer.writeStartElement('description'); await writer.writeCData('<p>This is <b>HTML</b> content in CDATA</p>'); await writer.writeEndElement();
await writer.writeEndElement(); // product } await writer.writeEndElement(); // products await writer.writeEndElement(); // catalog
await writer.writeEndDocument(); await writer.close();
} catch (error) { console.error('XML generation error:', error); } });});
export default app;WriteElementOptions API
Section titled “WriteElementOptions API”Writer supports a unified API that simplifies element creation:
// Self-closing element with attributesawait writer.writeStartElement('img', { attributes: { src: 'image.jpg', alt: 'Image' }, selfClosing: true // No need to call writeEndElement()});
// Element with namespaceawait writer.writeStartElement('title', { prefix: 'html', uri: 'http://www.w3.org/1999/xhtml', attributes: { lang: 'en' }});📚 API Reference
Section titled “📚 API Reference”Writer (Asynchronous, Stream-Based)
Section titled “Writer (Asynchronous, Stream-Based)”class Writer { // Constructor - for streaming XML directly to WritableStream constructor(stream: WritableStream<Uint8Array>, options?: WriterOptions)
// Document Level Methods writeStartDocument(version?: string, encoding?: string): Promise<this> writeEndDocument(): Promise<void>
// Element Writing Methods writeStartElement(localName: string, options?: WriteElementOptions): Promise<this> writeEndElement(): Promise<this>
// Content Writing Methods writeCharacters(text: string): Promise<this> writeCData(cdata: string): Promise<this> writeComment(comment: string): Promise<this>
// Stream Management close(): Promise<void> // Closes the underlying stream flush(): Promise<void> // Manual flush getMetrics(): object // Performance metrics}
interface WriterOptions { encoding?: string; // Default: 'utf-8' prettyPrint?: boolean; // Default: false indentString?: string; // Default: ' ' addEntities?: { entity: string, value: string }[]; autoEncodeEntities?: boolean; // Default: true namespaces?: NamespaceDeclaration[]; bufferSize?: number; // Default: 16384 highWaterMark?: number; // Default: 65536 flushThreshold?: number; // Default: 0.8 enableAutoFlush?: boolean; // Default: true}Interfaces
Section titled “Interfaces”interface WriteElementOptions { prefix?: string; uri?: string; attributes?: Record<string, string | AttributeInfo>; selfClosing?: boolean;}
interface AttributeInfo { value: string; localName: string; prefix?: string; uri?: string;}
interface NamespaceDeclaration { prefix?: string; uri: string;}🚀 When to Use Writer
Section titled “🚀 When to Use Writer”Use Writer when:
- Streaming XML to clients in real-time
- Memory efficiency is critical
- Working with asynchronous/streaming architectures
- Processing data that doesn’t fit in memory
- Building APIs that need to stream responses
- Real-time XML generation from live data sources
For large file generation, prefer WriterSyncSink. For small documents or synchronous operations, use WriterSync as an in-memory string builder.