@xenterprises/fastify-xpdf 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example ADDED
@@ -0,0 +1,11 @@
1
+ # Puppeteer Configuration (optional - uses defaults if not provided)
2
+ PUPPETEER_HEADLESS=true
3
+ PUPPETEER_ARGS=--no-sandbox,--disable-setuid-sandbox
4
+
5
+ # xPDF Configuration
6
+ PDF_DEFAULT_FORMAT=A4
7
+ PDF_DEFAULT_FOLDER=pdfs
8
+ PDF_USE_STORAGE=false
9
+
10
+ # Port for example server
11
+ PORT=3000
package/CHANGELOG.md ADDED
@@ -0,0 +1,106 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2024-12-29
9
+
10
+ ### Added
11
+ - Initial release of xPDF Fastify v5 plugin
12
+ - HTML to PDF generation using Puppeteer
13
+ - Markdown to PDF generation with styled formatting
14
+ - PDF form filling with support for text, checkbox, radio, and dropdown fields
15
+ - PDF form flattening (make fields non-editable)
16
+ - PDF merging - combine multiple PDFs into one
17
+ - Optional xStorage integration for cloud storage (S3-compatible)
18
+ - Lazy Puppeteer browser initialization for resource efficiency
19
+ - Automatic browser cleanup on Fastify shutdown
20
+ - Comprehensive error handling and input validation
21
+ - Configurable page formats (A4, Letter, A3, A5, etc.)
22
+ - Support for custom margins and landscape orientation
23
+ - Helper utilities for PDF operations
24
+ - 64 comprehensive tests across 14 test suites
25
+ - Complete documentation (README + QUICK_START)
26
+ - Example HTTP server implementation
27
+
28
+ ### Features
29
+ - **Library pattern**: Decorate methods on Fastify instance, not HTTP routes
30
+ - **xStorage integration**: Optional, auto-detected, seamless cloud storage
31
+ - **Configuration profiles**: Development, production, and high-performance configs
32
+ - **Memory efficient**: Pages closed after use, lazy initialization
33
+ - **Concurrent operations**: Stress tested with 5+ simultaneous PDF generations
34
+ - **Unicode support**: Handles emoji, CJK, Arabic, and other Unicode content
35
+
36
+ ### Documentation
37
+ - Complete README with API reference
38
+ - Quick Start guide with 10-step setup
39
+ - 11 real-world usage examples
40
+ - Configuration guide with profiles
41
+ - Troubleshooting section
42
+ - Best practices guide
43
+
44
+ ### Security
45
+ - Input validation on HTML/Markdown content
46
+ - PDF buffer validation before processing
47
+ - No eval or dynamic code execution
48
+ - Sandboxing configuration for Puppeteer
49
+ - Content type enforcement for storage
50
+
51
+ ### Tested Environments
52
+ - Node.js v20+ (ES modules)
53
+ - Fastify v5.1.0+
54
+ - macOS and Linux
55
+
56
+ ---
57
+
58
+ ## [Future Versions - Planned]
59
+
60
+ ### [1.1.0] - Planned
61
+ - Configuration validation using Zod
62
+ - Browser initialization retry logic
63
+ - Concurrency limiting with optional queue support
64
+ - Prometheus metrics integration
65
+ - Error code standardization
66
+ - Production error sanitization
67
+
68
+ ### [1.2.0] - Planned
69
+ - PDF text extraction
70
+ - PDF page splitting
71
+ - PDF watermarking
72
+ - PDF encryption with password protection
73
+ - Digital signature support
74
+ - Content Security Policy headers for HTML
75
+ - Optional HTML sanitization integration
76
+
77
+ ### [2.0.0] - Planned (Breaking Changes)
78
+ - Separate Puppeteer pool service option
79
+ - WebWorker-based page handling
80
+ - Streaming API for large PDFs
81
+ - Performance optimizations
82
+
83
+ ---
84
+
85
+ ## Notes
86
+
87
+ ### Dependencies
88
+ - **puppeteer**: ^23.0.0 (HTML to PDF rendering engine)
89
+ - **pdf-lib**: ^1.17.1 (PDF manipulation and form operations)
90
+ - **marked**: ^15.0.0 (Markdown to HTML conversion)
91
+ - **fastify-plugin**: ^5.0.0 (Fastify plugin wrapper)
92
+
93
+ ### System Requirements
94
+ - Node.js >= 20.0.0
95
+ - Fastify >= 5.0.0
96
+ - Chrome/Chromium browser (downloaded automatically by Puppeteer)
97
+ - At least 512MB RAM (recommended 1GB+)
98
+
99
+ ### License
100
+ ISC
101
+
102
+ ### Author
103
+ Tim Mushen
104
+
105
+ ### Repository
106
+ https://gitlab.com/x-enterprises/fastify-plugins/fastify-x-pdf
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2024 Tim Mushen
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/QUICK_START.md ADDED
@@ -0,0 +1,462 @@
1
+ # Quick Start Guide
2
+
3
+ Get up and running with xPDF in 5 minutes.
4
+
5
+ ## 1. Install
6
+
7
+ ```bash
8
+ npm install @xenterprises/fastify-xpdf puppeteer marked pdf-lib
9
+ ```
10
+
11
+ **For optional storage integration:**
12
+
13
+ ```bash
14
+ npm install @xenterprises/fastify-xstorage
15
+ ```
16
+
17
+ ## 2. Basic Setup
18
+
19
+ ```javascript
20
+ import Fastify from "fastify";
21
+ import xPDF from "@xenterprises/fastify-xpdf";
22
+
23
+ const fastify = Fastify({ logger: true });
24
+
25
+ // Register xPDF
26
+ await fastify.register(xPDF, {
27
+ headless: true,
28
+ useStorage: false,
29
+ defaultFolder: "pdfs",
30
+ });
31
+
32
+ await fastify.listen({ port: 3000 });
33
+ ```
34
+
35
+ ## 3. Generate PDF from HTML
36
+
37
+ ```javascript
38
+ // Simple HTML to PDF
39
+ const result = await fastify.xPDF.generateFromHtml(
40
+ "<h1>Hello World</h1><p>This is a PDF</p>",
41
+ {
42
+ filename: "hello.pdf",
43
+ }
44
+ );
45
+
46
+ console.log(result);
47
+ // {
48
+ // buffer: Buffer,
49
+ // filename: "hello.pdf",
50
+ // size: 12345
51
+ // }
52
+
53
+ // Save buffer to file
54
+ import fs from "fs";
55
+ fs.writeFileSync("hello.pdf", result.buffer);
56
+ ```
57
+
58
+ ## 4. Generate PDF from Markdown
59
+
60
+ ```javascript
61
+ const markdown = `
62
+ # My Document
63
+
64
+ This is **bold** and this is *italic*.
65
+
66
+ ## Section
67
+
68
+ - Point 1
69
+ - Point 2
70
+ - Point 3
71
+
72
+ \`\`\`javascript
73
+ console.log("Code blocks work!");
74
+ \`\`\`
75
+ `;
76
+
77
+ const result = await fastify.xPDF.generateFromMarkdown(markdown, {
78
+ filename: "document.pdf",
79
+ });
80
+
81
+ fs.writeFileSync("document.pdf", result.buffer);
82
+ ```
83
+
84
+ ## 5. Fill PDF Forms
85
+
86
+ ```javascript
87
+ // Read a PDF form
88
+ const pdfBuffer = fs.readFileSync("form-template.pdf");
89
+
90
+ // Fill form fields
91
+ const result = await fastify.xPDF.fillForm(
92
+ pdfBuffer,
93
+ {
94
+ firstName: "John",
95
+ lastName: "Doe",
96
+ email: "john@example.com",
97
+ agreeToTerms: true,
98
+ },
99
+ {
100
+ flatten: true, // Make non-editable
101
+ filename: "form-filled.pdf",
102
+ }
103
+ );
104
+
105
+ fs.writeFileSync("form-filled.pdf", result.buffer);
106
+ ```
107
+
108
+ ## 6. List Form Fields
109
+
110
+ Before filling a form, see what fields are available:
111
+
112
+ ```javascript
113
+ const pdfBuffer = fs.readFileSync("form-template.pdf");
114
+
115
+ const fields = await fastify.xPDF.listFormFields(pdfBuffer);
116
+
117
+ console.log(fields);
118
+ // [
119
+ // { name: "firstName", type: "text", value: null },
120
+ // { name: "lastName", type: "text", value: null },
121
+ // { name: "email", type: "text", value: null },
122
+ // { name: "agreeToTerms", type: "checkbox", value: false }
123
+ // ]
124
+ ```
125
+
126
+ ## 7. Merge PDFs
127
+
128
+ ```javascript
129
+ // Read multiple PDFs
130
+ const page1 = fs.readFileSync("page1.pdf");
131
+ const page2 = fs.readFileSync("page2.pdf");
132
+ const page3 = fs.readFileSync("page3.pdf");
133
+
134
+ // Merge them
135
+ const result = await fastify.xPDF.mergePDFs([page1, page2, page3], {
136
+ filename: "merged.pdf",
137
+ });
138
+
139
+ console.log(result);
140
+ // {
141
+ // buffer: Buffer,
142
+ // filename: "merged.pdf",
143
+ // size: 345678,
144
+ // pageCount: 15 // Total pages
145
+ // }
146
+
147
+ fs.writeFileSync("merged.pdf", result.buffer);
148
+ ```
149
+
150
+ ## 8. With xStorage Integration
151
+
152
+ ```javascript
153
+ import xStorage from "@xenterprises/fastify-xstorage";
154
+ import xPDF from "@xenterprises/fastify-xpdf";
155
+
156
+ const fastify = Fastify();
157
+
158
+ // Register xStorage first
159
+ await fastify.register(xStorage, {
160
+ endpoint: "https://nyc3.digitaloceanspaces.com",
161
+ region: "nyc3",
162
+ accessKeyId: process.env.DO_SPACES_KEY,
163
+ secretAccessKey: process.env.DO_SPACES_SECRET,
164
+ bucket: "my-bucket",
165
+ publicUrl: "https://my-bucket.nyc3.digitaloceanspaces.com",
166
+ });
167
+
168
+ // Register xPDF
169
+ await fastify.register(xPDF, {
170
+ useStorage: true,
171
+ defaultFolder: "pdfs",
172
+ });
173
+
174
+ // Now PDFs save automatically to storage
175
+ const result = await fastify.xPDF.generateFromHtml("<h1>Invoice</h1>", {
176
+ filename: "invoice.pdf",
177
+ saveToStorage: true,
178
+ folder: "invoices",
179
+ });
180
+
181
+ console.log(result.url);
182
+ // https://my-bucket.nyc3.digitaloceanspaces.com/invoices/invoice-xxx.pdf
183
+ ```
184
+
185
+ ## 9. HTTP Endpoints
186
+
187
+ Use xPDF in your routes:
188
+
189
+ ```javascript
190
+ // HTML to PDF endpoint
191
+ fastify.post("/api/pdf/html", async (request, reply) => {
192
+ const { html, filename = "document.pdf" } = request.body;
193
+
194
+ const result = await fastify.xPDF.generateFromHtml(html, {
195
+ filename,
196
+ saveToStorage: true,
197
+ folder: "generated",
198
+ });
199
+
200
+ return {
201
+ success: true,
202
+ url: result.url,
203
+ filename: result.filename,
204
+ };
205
+ });
206
+
207
+ // Markdown to PDF endpoint
208
+ fastify.post("/api/pdf/markdown", async (request, reply) => {
209
+ const { markdown } = request.body;
210
+
211
+ const result = await fastify.xPDF.generateFromMarkdown(markdown, {
212
+ saveToStorage: true,
213
+ });
214
+
215
+ return { success: true, url: result.url };
216
+ });
217
+
218
+ // Form filling endpoint
219
+ fastify.post("/api/pdf/fill-form", async (request, reply) => {
220
+ const { formUrl, fieldValues } = request.body;
221
+
222
+ // Download form from storage
223
+ const key = formUrl.replace(process.env.STORAGE_PUBLIC_URL + "/", "");
224
+ const pdfBuffer = await fastify.xStorage.download(key);
225
+
226
+ // Fill and save
227
+ const result = await fastify.xPDF.fillForm(pdfBuffer, fieldValues, {
228
+ flatten: true,
229
+ saveToStorage: true,
230
+ folder: "submitted-forms",
231
+ });
232
+
233
+ return { success: true, url: result.url };
234
+ });
235
+
236
+ // Merge PDFs endpoint
237
+ fastify.post("/api/pdf/merge", async (request, reply) => {
238
+ const { pdfUrls } = request.body;
239
+
240
+ // Download all PDFs
241
+ const buffers = await Promise.all(
242
+ pdfUrls.map((url) => {
243
+ const key = url.replace(process.env.STORAGE_PUBLIC_URL + "/", "");
244
+ return fastify.xStorage.download(key);
245
+ })
246
+ );
247
+
248
+ // Merge
249
+ const result = await fastify.xPDF.mergePDFs(buffers, {
250
+ saveToStorage: true,
251
+ folder: "merged",
252
+ });
253
+
254
+ return {
255
+ success: true,
256
+ url: result.url,
257
+ pages: result.pageCount,
258
+ };
259
+ });
260
+ ```
261
+
262
+ ## 10. Common Use Cases
263
+
264
+ ### Invoice Generation
265
+
266
+ ```javascript
267
+ const invoiceHtml = `
268
+ <h1>Invoice #INV-2024-001</h1>
269
+ <p><strong>Date:</strong> January 1, 2024</p>
270
+ <table>
271
+ <tr>
272
+ <th>Item</th>
273
+ <th>Qty</th>
274
+ <th>Price</th>
275
+ </tr>
276
+ <tr>
277
+ <td>Widget</td>
278
+ <td>10</td>
279
+ <td>$100.00</td>
280
+ </tr>
281
+ </table>
282
+ <h2>Total: $1,000.00</h2>
283
+ `;
284
+
285
+ const result = await fastify.xPDF.generateFromHtml(invoiceHtml, {
286
+ filename: "invoice-001.pdf",
287
+ saveToStorage: true,
288
+ folder: "invoices",
289
+ });
290
+ ```
291
+
292
+ ### Certificate Generation
293
+
294
+ ```javascript
295
+ const certificateHtml = `
296
+ <div style="text-align: center; padding: 2cm;">
297
+ <h1>Certificate of Completion</h1>
298
+ <p style="font-size: 18px; margin: 2cm 0;">
299
+ This certifies that <strong>Jane Doe</strong>
300
+ </p>
301
+ <p>has successfully completed the course</p>
302
+ <p style="font-size: 16px; color: #0066cc;">
303
+ <strong>Advanced Web Development</strong>
304
+ </p>
305
+ <p style="margin-top: 3cm;">January 1, 2024</p>
306
+ </div>
307
+ `;
308
+
309
+ const result = await fastify.xPDF.generateFromHtml(certificateHtml, {
310
+ filename: "certificate-jane-doe.pdf",
311
+ format: "A4",
312
+ landscape: false,
313
+ });
314
+ ```
315
+
316
+ ### Report with Multiple Sections
317
+
318
+ ```javascript
319
+ const sections = [
320
+ { title: "Executive Summary", content: "..." },
321
+ { title: "Findings", content: "..." },
322
+ { title: "Recommendations", content: "..." },
323
+ ];
324
+
325
+ const reportHtml = sections
326
+ .map((s) => `<h2>${s.title}</h2><p>${s.content}</p>`)
327
+ .join("");
328
+
329
+ const result = await fastify.xPDF.generateFromHtml(reportHtml, {
330
+ filename: "report.pdf",
331
+ saveToStorage: true,
332
+ folder: "reports",
333
+ });
334
+ ```
335
+
336
+ ## Tips & Tricks
337
+
338
+ ### Styled HTML Documents
339
+
340
+ ```javascript
341
+ const styledHtml = `
342
+ <style>
343
+ body { font-family: Arial, sans-serif; }
344
+ h1 { color: #0066cc; border-bottom: 2px solid #0066cc; }
345
+ table { width: 100%; border-collapse: collapse; }
346
+ th { background: #f4f4f4; padding: 8px; text-align: left; }
347
+ td { padding: 8px; border-bottom: 1px solid #ddd; }
348
+ </style>
349
+
350
+ <h1>Report</h1>
351
+ <table>
352
+ <tr><th>Col 1</th><th>Col 2</th></tr>
353
+ <tr><td>Data</td><td>Data</td></tr>
354
+ </table>
355
+ `;
356
+
357
+ await fastify.xPDF.generateFromHtml(styledHtml);
358
+ ```
359
+
360
+ ### Custom Page Margins
361
+
362
+ ```javascript
363
+ await fastify.xPDF.generateFromHtml(html, {
364
+ margin: {
365
+ top: "2cm",
366
+ right: "1.5cm",
367
+ bottom: "2cm",
368
+ left: "1.5cm",
369
+ },
370
+ });
371
+ ```
372
+
373
+ ### Different Page Sizes
374
+
375
+ ```javascript
376
+ // Letter size (US)
377
+ await fastify.xPDF.generateFromHtml(html, { format: "Letter" });
378
+
379
+ // A3 (larger)
380
+ await fastify.xPDF.generateFromHtml(html, { format: "A3" });
381
+
382
+ // A5 (smaller)
383
+ await fastify.xPDF.generateFromHtml(html, { format: "A5" });
384
+ ```
385
+
386
+ ### Landscape Orientation
387
+
388
+ ```javascript
389
+ await fastify.xPDF.generateFromHtml(html, {
390
+ landscape: true,
391
+ });
392
+ ```
393
+
394
+ ### Batch Form Filling
395
+
396
+ ```javascript
397
+ const applicationData = [
398
+ { firstName: "John", lastName: "Doe", email: "john@example.com" },
399
+ { firstName: "Jane", lastName: "Smith", email: "jane@example.com" },
400
+ { firstName: "Bob", lastName: "Johnson", email: "bob@example.com" },
401
+ ];
402
+
403
+ for (const data of applicationData) {
404
+ const template = await fastify.xStorage.download("forms/template.pdf");
405
+
406
+ await fastify.xPDF.fillForm(template, data, {
407
+ flatten: true,
408
+ filename: `application-${data.lastName}.pdf`,
409
+ saveToStorage: true,
410
+ folder: "applications",
411
+ });
412
+ }
413
+ ```
414
+
415
+ ## Troubleshooting
416
+
417
+ ### Chrome Not Found
418
+
419
+ Install Chromium:
420
+
421
+ ```bash
422
+ # macOS
423
+ brew install chromium
424
+
425
+ # Linux
426
+ apt-get install chromium-browser
427
+ ```
428
+
429
+ Or use sandbox args:
430
+
431
+ ```javascript
432
+ await fastify.register(xPDF, {
433
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
434
+ });
435
+ ```
436
+
437
+ ### Timeout Errors
438
+
439
+ Reduce HTML complexity or use async images:
440
+
441
+ ```javascript
442
+ // ❌ Large external image
443
+ const html = '<img src="https://example.com/huge-image.jpg">';
444
+
445
+ // ✅ Optimized image
446
+ const html = '<img src="/images/logo.png" style="width: 200px;">';
447
+ ```
448
+
449
+ ### Form Fields Not Filling
450
+
451
+ List fields first to verify names:
452
+
453
+ ```javascript
454
+ const fields = await fastify.xPDF.listFormFields(pdfBuffer);
455
+ console.log(fields); // Check exact names
456
+ ```
457
+
458
+ ## Next Steps
459
+
460
+ - [Full Documentation](./README.md)
461
+ - [Complete Examples](./EXAMPLES.md)
462
+ - [Testing Guide](./TESTING.md)