* fix: two unbounded-cache memory leaks in common and schemas
Two module-level Map caches that never evict and store multi-MB strings
as keys, silently leaking for the entire lifetime of any consumer.
1. packages/common/src/expression.ts — parseDataCache
parseData() was memoized via a module-level parseDataCache keyed by
JSON.stringify(data). replacePlaceholders() calls it with a merged
{ ...schemaNameDefaults, ...variables } object where values may be
arbitrary strings from the caller. Whenever inputs contain base64
(image schemas with embedded data URLs, embedded fonts, large text),
the cache key is a multi-MB JSON string that gets pinned permanently;
every unique inputs state adds its own key, never collected. Parsing
is O(fields) and cheap, so removing the cache is strictly a win.
Regression test: packages/common/__tests__/expression.test.ts
'replacePlaceholders memory safety > does not retain call inputs in
a module-level cache' — runs 30 replacePlaceholders() calls with
unique ~500 KB payloads, captures a V8 heap snapshot via
v8.writeHeapSnapshot, aggregates string nodes >= 200 KB and asserts
the total retained size is below 2 MB. Pre-fix: ~30 MB retained
(FAILS). Post-fix: 0 bytes retained (passes).
2. packages/schemas/src/graphics/image.ts — getCacheKey
getCacheKey(schema, input) returned `${schema.type}${input}`, using
the full base64 bytes of the image as part of the cache key. Every
unique image processed by the PDF render path added a permanent Map
entry whose key byte length matched the image itself.
Replaced with a short fingerprint that samples the total length plus
three 16-char regions (first, middle, last). The middle-region
sample is essential: base64 PNGs share a common header and IEND
trailer, so distinct images of the same size would collide if only
first/last regions were sampled. Middle bytes are pixel data and
differ between distinct images with overwhelming probability. Keys
stay under 80 chars regardless of input size.
Regression tests: packages/schemas/__tests__/image.test.ts
- 'does not pin the full base64 input as a cache key' — asserts
key length < 100 chars. Pre-fix: 139 chars for a minimal PNG and
proportionally more for realistic images (FAILS).
- 'distinguishes different images via the fingerprint' — guards
against future over-shortening of the fingerprint that could
reintroduce collisions between distinct images.
Both leaks were originally identified via a V8 heap-snapshot diff taken
across a UI workload (typing + field tabbing) against a consumer app
with image schemas carrying base64 content. Before the fix, the top two
growing allocations by retained size were multi-MB string entries — one
per module-level cache in this PR — together accounting for hundreds of
MB of retained JS heap in a single 3-iteration run. After the fix, both
string entries disappear from the top 25 growing allocations and
aggregate JS heap is net flat / slightly shrinking across iterations.
No public API change. No behavioral change for consumers. Both caches
were module-local implementation details.
* fix(schemas): harden image cache key with FNV-1a hash; fix stale test comments
Addresses Greptile review on #1426:
- Replace 3-region sampling fingerprint in getCacheKey with an FNV-1a
32-bit hash over the full input. The old first-16 slice was a
constant data-URI prefix for any image of the same MIME type,
contributing no entropy; hashing every byte removes that weakness
at the same O(n) cost without retaining any slice as a Map key.
Key format is now `${type}:${len}:${fnv1a-hex}` (~40 chars).
- Rewrite stale comments in image.test.ts that referred to a
padding/mutation scheme the test never performs, and update the
fingerprint-format comment to match the new hash-based key.
- Add trailing newline to expression.test.ts.
All pre-existing and new tests still pass.
PDFME
Website | pdfme Cloud | Discord
TypeScript-based PDF generator and React-based UI. Open source, developed by the community, and completely free to use under the MIT license!
Features
| Fast PDF Generator | Easy PDF Template Design | Simple JSON Template |
|---|---|---|
| Works on Node and in the browser. Use templates to generate PDFs—complex operations are not required. | Anyone can easily create templates using the designer. | Templates are JSON data that is easy to understand and work with. |
Custom Feature Requests
While pdfme is an open-source project released under the MIT License, we are open to considering custom feature additions for a fee.
If you are willing to pay, we can evaluate and implement your requested features.
Please note that any additional functionality will always be released as open source. If this approach works for you, please contact us.
For a detailed list of supported features, please refer to the Supported Features page.
Documentation
For complete documentation on pdfme, please refer to the Getting Started guide.
For the planned next major release changes, see the migration guide draft.
Need interactive help? Use DeepWiki to ask questions about pdfme's documentation and source code directly.
CLI Workflow
For agentic workflows, local verification, or JSON-first template iteration, use @pdfme/cli.
pdfme validate: validate template or unified job JSON before generationpdfme doctor: diagnose runtime, font,basePdf, and output-path issues beforegeneratepdfme generate --image --grid: generate PDFs and inspect layout via rendered page imagespdfme examples --withInputs: export official example assets as a unified job you can edit and regenerate
Examples Using pdfme
If you're looking for code examples to get started with pdfme, check out the pdfme-playground website and the playground source code. Setup instructions can be found in the DEVELOPMENT.md file.
Cloud Service Option
While pdfme is a powerful open-source library, we understand that some users might prefer a managed solution. For those looking for a ready-to-use, scalable PDF generation service without the hassle of setup and maintenance, we offer pdfme Cloud.
Try pdfme Cloud - Hassle-free PDF Generation
pdfme Cloud provides all the features of the open-source library, plus:
- PDF generation at scale without infrastructure management
- Hosted WYSIWYG template designer
- Simple API integration
- Automatic updates and maintenance
pdfme will always remain open source. The cloud service is an optional offering for those who prefer a managed solution.
Sponsors
Support this project by becoming a sponsor. Your logo will appear here with a link to your website.
![]() |
|||
|---|---|---|---|
| ProgressLab | PhotoQuest | Famly | New Sponsor |
Contributors
Special Thanks
- pdf-lib: Used for PDF generation.
- fontkit: Used for font rendering.
- PDF.js: Used for PDF viewing.
- React: Used in building the UI.
- form-render: Used in building the UI.
- antd: Used in building the UI.
- react-moveable, react-selecto, @scena/react-guides: Used in the Designer UI.
- dnd-kit: Used in the Designer UI.
- Lucide: Used in the Designer UI and Schema's icon.
I definitely could not have created pdfme without these libraries. I am grateful to the developers of these projects.
If you want to contribute to pdfme, please refer to the Development Guide.
We look forward to your contributions!
