Off the main thread
@lamina/pdf is synchronous after init(). On the main thread, a full rewrite of a 50 MB file blocks the page for about 200 ms. @lamina/pdf/worker runs the same API in a Web Worker. You call the same method names, and each returns a Promise.
// `@lamina/pdf/worker`: the sync API's async twin in a Web Worker, so a large file never blocks// the page. Open, count the pages, save with progress, and abort a save the user cancels. Runs// in browsers (test/browser/worker.browser.test.ts).import { isLaminaError } from "@lamina/pdf";import { connect } from "@lamina/pdf/worker";
/** What the example saw: the page count, the saved size, and whether the abort was reported. */export interface WorkerClientResult { readonly pages: number; readonly savedBytes: number; readonly progressEvents: number; readonly abortedAsCancelled: boolean;}
/** * Opens `bytes` in a worker, saves them, then starts a second save and aborts it. * * @param bytes - any PDF; it is copied to the worker (`transfer: true` would move it). */export async function workerClient(bytes: Uint8Array): Promise<WorkerClientResult> { const client = await connect(); try { await using doc = await client.open(bytes); const pages = await doc.pageCount();
let progressEvents = 0; const saved = await doc.save({ mode: "fullRewrite" }, { onProgress: () => progressEvents++ });
// A cancel button: abort the signal and the save stops at its next step. const controller = new AbortController(); const saving = doc.save({ mode: "fullRewrite" }, { signal: controller.signal }); controller.abort(new Error("the user cancelled")); let abortedAsCancelled = false; try { await saving; } catch (e) { abortedAsCancelled = isLaminaError(e, "cancelled"); }
return { pages, savedBytes: saved.byteLength, progressEvents, abortedAsCancelled }; } finally { client.terminate(); }}- Same names, async. Getters become methods:
await doc.pageCount().doc.page(i)returns a remote page, andpage.text()returns aPageText(callclient.loadExtract()first). - Not served:
transact: its callback can’t cross to the worker, so useapply(ops).saveJob: the remotesaveis already stepped.Builder: build on the main thread or in your own worker.
- Cancellation. Pass
signalto any call. An aborted call rejects withcancelled, anderror.causeis the signal’s reason.- A call that hasn’t started never runs.
savestops at its next step, and the document stays usable.- Any other call runs to the end in the worker, because it is one synchronous call.
client.terminate()is the hard stop.
- Buffers. 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. - Crashes. If the worker dies, pending calls reject with
worker-crashedandclient.on("crashed", …)fires.client.restart()starts a fresh worker, and documents must be opened again. - Version.
connect()checks that the worker comes from the same@lamina/pdfbuild, and rejects withworker-version-mismatchotherwise. - Bundlers and CSP. Vite and webpack bundle the worker with no setup. In Vite, exclude
@lamina/pdffromoptimizeDeps, as for the main entry. To host the worker file yourself, passconnect({ worker: () => new Worker(url, { type: "module" }) }).