Skip to content

parsing.ts:686 calls pdf.destroy() unconditionally — broken under unpdf@1.8.0 #158

Description

@cioinside

Summary

packages/ragmir-core/src/parsing.ts:686 calls await pdf.destroy() unconditionally inside a finally block. Under unpdf@1.8.0 (the latest released version) this throws pdf.destroy is not a function and breaks PDF ingestion.

Repro

  1. Clone this repo and run pnpm install. "unpdf": "^1.4.0" in packages/ragmir-core/package.json resolves to 1.8.0.
  2. Add a PDF to a project:
    mkdir -p /tmp/repro/sources
    cp some.pdf /tmp/repro/sources/
    mkdir -p /tmp/repro/.ragmir
    printf '{"version":"1.0","sources":["sources/**/*.pdf"]}\n' > /tmp/repro/.ragmir/config.json
  3. Run rgr ingest inside /tmp/repro.
  4. Observe (from a runtime trace):
    Parsing: pdf.destroy is not a function
    

Expected

PDF parsing succeeds. The finally block should clean up safely even if pdf.destroy is missing in the current unpdf release.

Root cause

  • packages/ragmir-core/package.json declares "unpdf": "^1.4.0".
  • In unpdf@1.6.2, PDFDocumentProxy.destroy(): Promise<void> exists. Verified at runtime:
    typeof pdf.destroy === 'function'   // true
    await pdf.destroy()                  // resolves cleanly
  • In unpdf@1.8.0, .destroy() was removed from the proxy. Verified at runtime against the same getDocumentProxy(...) call:
    typeof pdf.destroy === 'undefined'   // true
    await pdf.destroy()                  // THROWS: pdf.destroy is not a function
    There is no replacement method on the returned proxy in 1.8.0 (the prototype still exposes numPages, getPage, getAttachments, but no destroy, cleanup, or close).

The caret range in ^1.4.0 allowed this breaking bump into a release that silently removed the only cleanup API the parser relies on.

Proposed fixes

Pick one (or both):

Option A — Pin the dependency (smallest blast radius):

-    "unpdf": "^1.4.0",
+    "unpdf": "~1.6.2",

Keeps pdf.destroy() working without code changes. Revisit when unpdf ships a replacement cleanup API.

Option B — Defensive guard in the parser (forward-compatible):

   } finally {
-    await pdf.destroy()
+    if (typeof pdf.destroy === "function") {
+      await pdf.destroy()
+    }
   }

Robust against future API churn in unpdf / pdfjs-dist.

I recommend B + A together: the pin prevents surprise breakage for users running fresh pnpm install, and the guard makes the parser correct against any future API shape (and against any current build where unpdf has been replaced or bundled differently).

Environment

  • @jcode.labs/ragmir 4.4.0
  • unpdf 1.8.0 (auto-resolved from ^1.4.0)
  • Node 22.23.1
  • pdfjs-dist bundled transitively inside unpdf 1.8.0

Workaround

A one-line patch against the installed dist/parsing.js makes ingest pass end-to-end on 4.4.0:

-    await pdf.destroy();
+    if (typeof pdf.destroy === "function") await pdf.destroy();

After this patch, rgr ingest against a real PDF runs clean (no pdf.destroy is not a function, exit 0).

Note for maintainers

If unpdf has intentionally removed destroy() because the proxy is now GC-managed, the parser's finally block can simply be removed entirely — there is no observable leak from leaving the document proxy unreferenced. That decision should be made consciously, not by accident via a dependency bump.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions