3.8 KiB
OpenSpec Change: enterprise-server-pdf
- Change ID: enterprise-server-pdf
- Package:
@suaveplan/pdf - Category: server/services
- Target path:
server/services/pdf - Status: PROPOSED
- Date: 2026-04-24
- Reference spec:
/mnt/s/web/suaveplan/enterprise/pdf.md
Summary
Deliver @suaveplan/pdf on the genesis branch from scratch at enterprise quality. The package provides server-side PDF generation for receipts, reports, and invoices. It exposes a PdfBuilder with a fluent page/section/element composition API and a pluggable PdfRenderer interface, shipping two concrete renderers: a PuppeteerRenderer (HTML → PDF via headless Chrome) and a PdfKitRenderer (pure JS, no browser dependency). Zod-validated options, @suaveplan/error codes, OTel spans per render, @suaveplan/templates for HTML template rendering, and AbortSignal support for long renders.
Current-state audit (genesis)
Does not exist on genesis — fresh build.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/error (workspace:^) |
Typed PDF.* error codes |
@suaveplan/types (workspace:^) |
LoggerLike |
@suaveplan/templates (workspace:^) |
HTML template rendering |
@suaveplan/logger (workspace:^) |
Structured logs |
zod (catalog:forms) |
Options validation |
@opentelemetry/api (catalog:observability) |
Spans + counters + histograms |
puppeteer-core (catalog:services) |
Headless Chrome (optional renderer) |
pdfkit (catalog:services) |
Pure-JS PDF generation (optional renderer) |
tslib (catalog:build) |
Runtime helpers |
Public API surface
interface PdfRenderer {
render(document: PdfDocument, options?: RenderOptions): Promise<Uint8Array>
dispose(): Promise<void>
}
class PdfBuilder {
page(options?: PageOptions): this
text(content: string, options?: TextOptions): this
heading(content: string, level?: 1 | 2 | 3): this
table(data: TableData, options?: TableOptions): this
image(src: string | Uint8Array, options?: ImageOptions): this
spacer(height?: number): this
footer(content: string): this
header(content: string): this
build(): PdfDocument
}
function createPdfBuilder(): PdfBuilder
function createPuppeteerRenderer(options?: PuppeteerRendererOptions): PdfRenderer
function createPdfKitRenderer(options?: PdfKitRendererOptions): PdfRenderer
interface PdfDocument {
pages: PdfPage[]
metadata: PdfMetadata
}
interface PdfMetadata {
title?: string
author?: string
subject?: string
keywords?: string[]
creator?: string
}
interface RenderOptions {
signal?: AbortSignal
format?: 'A4' | 'Letter' | 'Legal'
landscape?: boolean
margins?: { top: number; right: number; bottom: number; left: number }
}
type PdfErrorCode =
| 'PDF_RENDER_FAILED'
| 'PDF_TIMEOUT'
| 'PDF_INVALID_CONTENT'
| 'PDF_RENDERER_UNAVAILABLE'
function registerPdfErrorCodes(): void
File layout
server/services/pdf/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/index.ts
types/
pdf.types.ts
pdf.types.md
builder/
pdf-builder/
pdf-builder.ts
pdf-builder.md
pdf-builder.types.ts
pdf-builder.test.ts
index.ts
renderers/
puppeteer-renderer/
puppeteer-renderer.ts
puppeteer-renderer.md
puppeteer-renderer.types.ts
puppeteer-renderer.test.ts
index.ts
pdfkit-renderer/
pdfkit-renderer.ts
pdfkit-renderer.md
pdfkit-renderer.types.ts
pdfkit-renderer.test.ts
index.ts
tests/
integration.test.ts
Impact
Downstream: invoice generation, report exports, document archiving. Server-only.
Prerequisites on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/templates, @suaveplan/logger.