Skip to content
Lamina PDF
Unreleased

These docs describe changes that aren’t released yet. For the newest release, see the released docs.

Keep the page responsive

You’ll run Lamina PDF in a Web Worker, so opening or saving a large file never freezes your page. You need this in a browser app once files get big: on the main thread, a full rewrite of a 50 MB file blocks the page for about 200 ms.

@laminapdf/sdk is synchronous after init(). @laminapdf/sdk/worker runs the same API in a Web Worker, where you call the same methods and each returns a Promise. This example opens a file in a Web Worker and saves it there. The save reports its progress, and aborting the signal stops the work, for example when the user presses Cancel:

packages/sdk/examples/worker-client.ts
// Saving a PDF in a Web Worker, so a large file never blocks the page: `@laminapdf/sdk/worker`
// runs the same API there, and each method returns a Promise. The save reports its progress step
// by step, and aborting the signal (a Cancel button, say) stops the work.
import { type CallOptions, connect } from "@laminapdf/sdk/worker";
/** What the save reports to and listens to. */
export type SaveInWorkerOptions = Pick<CallOptions, "onProgress" | "signal">;
/**
* Opens `bytes` in a Web Worker and saves them there as a full rewrite.
*
* @param bytes - any PDF; it is copied to the worker (`transfer: true` would move it).
* @param options - `onProgress` is called after each step of the save. Aborting `signal` stops
* the work wherever it is (starting the Web Worker, opening, or at the save's next step), and
* the promise rejects with a `cancelled` error.
* @returns the saved file.
*/
export async function saveInWorker(bytes: Uint8Array, options: SaveInWorkerOptions = {}): Promise<Uint8Array> {
const client = await connect(options);
try {
await using doc = await client.open(bytes, options);
return await doc.save({ mode: "fullRewrite" }, options);
} finally {
client.terminate();
}
}

Getters become methods: await doc.pageCount(). doc.page(i) returns a remote page, and page.text() returns a PageText, once you’ve called client.loadExtract().

The Web Worker leaves out a few members, each for a reason:

  • transact: its callback can’t cross to the Web Worker, so pass the edits to apply(ops) instead.
  • saveJob: the Web Worker’s save already works in steps.
  • ensure: the Web Worker fetches the byte ranges a call needs by itself.
  • free: call doc.close(), or use await using, instead.
  • Builder: build on the main thread, or in a Web Worker of your own.

Pass an AbortSignal as signal to any call. An aborted call rejects with cancelled, and error.cause holds the signal’s reason. What happens to the work depends on where it was:

  • A call that hasn’t started never runs.
  • save stops at its next step, and the document stays usable.
  • Any other call runs to the end in the Web Worker, because it is one synchronous call. client.terminate() stops it at once.

Saved bytes and page text come back transferred, not copied. Your input is copied unless you pass transfer: true, which moves it and leaves your buffer empty.

If the Web Worker dies, pending calls reject with worker-crashed, and client.on("crashed", …) fires. client.restart() starts a fresh Web Worker, and you open your documents again.

connect() also checks that the Web Worker comes from the same @laminapdf/sdk build as your page. It rejects with worker-version-mismatch otherwise.

Vite and webpack bundle the Web Worker with no setup. In Vite, exclude @laminapdf/sdk from optimizeDeps, as for the main entry. To host the Web Worker’s file yourself, for example under a strict content security policy, pass connect({ worker: () => new Worker(url, { type: "module" }) }).

Next: browse the recipes, with the PDF each one makes, or the troubleshooting page when something goes wrong.