Release Next v3.0.0-next.8
- #783 by @bobsingor – Use the document-scoped access endpoint for unlock requests. Add an opt-in
docAffinityHeaderoption for routing document requests and bounded retries for serverEngineBusyandEngineRestartingresponses, includingRetry-Afterhandling and anonRetrycallback.
-
#783 by @bobsingor – Add a production Helm chart with validated SQLite and Postgres profiles, safety gates, smoke and crash drills, and OCI publishing tied to the server package version. Add Prometheus metrics, drain-aware bounded shutdown and readiness, serialized Postgres migrations, and fail-fast worker supervision.
Add opt-in supervised engine-host process isolation with generation-fenced recovery, crash journaling, document quarantine enforcement, engine health reporting, and audited quarantine CLI commands. Repeated engine crashers can be observed or rejected with
DocumentQuarantined, while native host crashes restart the engine without terminating the API server.Encode page renders, annotation appearances, and warm thumbnails inside engine workers by default so compressed images cross the engine boundary, with a temporary API-side encoding fallback. Add bounded interactive and background scheduling, per-host memory telemetry, controlled engine recycling, and deterministic engine sharding with per-shard readiness and metrics.
Add
POST /v1/docs/:docId/accessas the document-scoped access endpoint while retainingPOST /v1/accessas a transitional alias. Head responses advertise the scoped endpoint, and path/body document ID mismatches are rejected. Allow the optionalX-CloudPDF-Docaffinity header through CORS and expose retry and image metadata response headers to browser clients.
-
#783 by @bobsingor – Add the
*.renderEncodedwire kinds (pages.renderEncoded,document.renderPageFileEncoded,annotations.renderAppearancesEncoded) plus theirRenderEncode/EncodedImageWireshapes — cloud-server surface (types only): the raster is encoded where it is produced and only the compressed image crosses the engine boundary.Make document access endpoints document-scoped by changing
wirePaths.accessto awirePaths.access(docId)builder. KeepwirePaths.accessLegacyfor transitional clients and allow the scoped endpoint to omitdocIdfrom the request body.
- #783 by @bobsingor – Add a public
@embedpdf/engine-runtime/build-idsubpath exposing the runtime's build identity (ENGINE_RUNTIME_VERSION,engineRuntimeTarget(), andengineRuntimeBuildId()) as a side-effect-free Node module. Supervisors and diagnostics can identify the version and resolved native target without loading the native addon.
- #783 by @bobsingor –
WorkerHostaccepts an optional injectedWorkerImageEncoder(third constructor argument) and dispatches the new*.renderEncodedkinds through it on a narrowly-scoped async path. No new dependencies: the native encoder stays in the injecting package. Hosts without an encoder (browser/local workers) reject those kinds withNotImplemented; existing two-argument construction is unchanged.
- #779 by @bobsingor – Bind Stage surface measurement before browser paint so the initial viewport and camera placement settle before page surfaces become visible. React viewers no longer show a transient incorrectly positioned page while a document opens.
- #779 by @bobsingor – Keep page and scrollbar screen geometry hidden until initial viewport placement commits. Stage consumers no longer receive origin-based placeholder geometry while viewport, responsive settings, and camera state are being initialized, preventing pages from rendering at the top-left before their final placement.
Release Next v3.0.0-next.7
- #776 by @bobsingor – Add
textQuadEdgeandtextQuadEqualshelpers for orientation-aware glyph edges and corner-wise text-quad change detection.
- #775 by @bobsingor – Add optional hints to capability tokens so missing-dependency errors can tell integrators which plugin to register.
- #775 by @bobsingor – Move Angular Stage input handling to the shared web surface controller, with lens-scoped interaction and native touch pan, pinch, fling, double-tap, and long-press gestures. Interaction now defaults on when the hub is present and can be disabled for secondary lenses.
- #775 by @bobsingor – Move React Stage input handling to the shared web surface controller, key page surfaces by durable page identity, and add draggable touch selection handles. Interaction now defaults on when the hub is present and can be disabled for secondary lenses.
-
#776 by @bobsingor – Refactor
SelectionHandlesto use the shared selection and web primitives so handles align correctly with rotated text and rotated pages. -
#777 by @bobsingor – Give every Stage lens and standalone
PageViewa stable view identity and use its scoped tile handle. Thumbnail and secondary views can no longer clear the main view's high-resolution tiles.
-
#775 by @bobsingor – Add shared browser Stage surface and touch gesture controllers with lens-scoped input, pan, pinch, fling, double-tap, long-press, and wheel handling. Export vibration and native-shell feedback providers.
-
#776 by @bobsingor – Add a shared native DOM binding for selection-handle drags that shields Stage gestures and handles pointer capture and client-delta tracking.
- #775 by @bobsingor – Add touch-aware tool consent, hit targets, drag handling, and cancellation so annotation editing cooperates with navigation gestures. Text-edit operations now report whether they handled a gesture, and the annotation capability token includes a missing-plugin hint.
- #775 by @bobsingor – Add pointer modality, cancellation, long-press, source metadata, touch-claim preflight, and source-scoped handlers to interaction routing. Introduce the PlatformFeedback capability and feedback plugin, and add a missing-plugin hint to the interaction capability token.
- #777 by @bobsingor – Scope tile state by view and page so multiple views of the same page can plan rasters independently without invalidating each other. Replace the flat tile methods with a reference-stable
render.tilesFor(view)handle that binds the view identity for planning, paint reporting, and release.
- #776 by @bobsingor – Add framework-independent selection-handle geometry and drag policies that follow rotated text, rotated pages, and RTL selection boundaries.
- #775 by @bobsingor – Route touch long-press selection through explicit gesture metadata, report whether word and line selection succeeded, and support optional selection feedback without swallowing unhandled gestures.
- #775 by @bobsingor – Add responsive container-query settings and named active rules. Add gesture lifecycle, elastic overscroll, fling, anchored double-tap zoom, and lens identity APIs, while removing the deprecated interaction settings and moving wheel classification to
@embedpdf/web.
- #777 by @bobsingor – Double-tap from a pinched-in zoom returns to fit-width instead of climbing (the iOS rule). The zoom ladder previously picked "the first posture meaningfully above the current zoom," so a pinch to a level between fit-width and detail made a double-tap zoom IN further. The rule is now: the ladder ascends only from ON a rung (within ±10% of a posture) — a tap at a posture moves to the next, wrapping past the top — while a pinch to any other level is leaving the ladder, and a double-tap there RESETS to the base fit ("take me back to reading"), never a further zoom-in. Everything that already felt right is unchanged: zoomed-out → fit-width, fit-width → detail, detail → fit-width.
- #775 by @bobsingor – Enable draggable touch selection handles and register vibration feedback by default when the platform supports it.
- #771 by @bobsingor – Accepts bodyless requests that carry
Content-Type: application/jsoninstead of failing them with an unhandled 500.- An empty JSON body now parses as no body rather than
FST_ERR_CTP_EMPTY_JSON_BODY— the shape the generated PHP, Go, and Ruby SDKs (and clients with default JSON headers) send for bodyless calls such as document delete. Non-empty bodies still go through Fastify's default parser, keeping its prototype-poisoning protection, and routes that require a body still reject its absence with a 400. - The error handler now honors Fastify's
statusCodeon framework errors, so parser and payload rejections (empty or malformed JSON, body limits) surface as the 4xx client errors they are instead of being logged and returned as "unhandled error" 500s.
- An empty JSON body now parses as no body rather than
- #775 by @bobsingor – Update the generated default WASM URL to use the current engine runtime release instead of the previous prerelease.
Release Next v3.0.0-next.6
-
#766 by @bobsingor – Extend
documents.importFromwithmode: "async". Async requests accept operator-registered connection sources and return 202 withtag: "accepted"and a pending document that callers can poll withGET /documents/:id; presigned URL sources remain synchronous. -
#766 by @bobsingor – Add the provider-neutral
connectionsource todocuments.importFrom. Requests identify an operator-registered connection and object key, plus an optional opaque provider-specific revision, without exposing storage-provider configuration or credentials in the public wire contract. -
#766 by @bobsingor – Add the
documents.importFromoperation for importing a PDF from a caller-supplied URL. The request supports optional size and SHA-256 integrity pins, metadata, deduplication, and idempotency fields, while responses distinguish completed imports from validation, authorization, conflict, and upstream transport failures. -
#766 by @bobsingor – Rename the contract operation from
documents.importtodocuments.importFromso generated Java, Python, and Ruby SDKs expose a consistent method name. The HTTP path remainsPOST /v1/tenants/{tenantId}/documents/import. The OpenAPI emitter now rejects group or method segments that collide with reserved words in those target languages.
- #766 by @bobsingor – Add the generated
documents.importFromclient method and request and response types. The SDK accepts URL or operator-registered connection sources, supports synchronous and asynchronous import modes, exposes integrity, deduplication, metadata, and idempotency options, and maps upstream transport failures toBadGatewayError.
-
#766 by @bobsingor – Add durable asynchronous document imports backed by the
document_importsjob table and an in-process worker with one claim loop per replica. Document and job creation is atomic, lease-token-fenced transitions prevent stale workers from overwriting replacements, reconcile-on-claim avoids duplicate transfers after crashes, and exhausted retries fail the document and clean destination bytes. Retries stay pinned to one content identity, filesystem sources requireexpected.sha256, and queued or running imports are protected from the stale-pending sweeper. Migration 027 stores the re-drivable source descriptor insource_json. -
#766 by @bobsingor – Add operator-registered import connections through
CLOUDPDF_IMPORT_CONNECTIONSfor S3 and S3-compatible stores, GCS, Azure Blob, and filesystem roots. Connections enforce credential classes, tenant allowlists, scoped prefixes or tenant-bound key templates, provider-specific revision pinning, and fail-closed authorization. Canonical backend fingerprints reject self-imports, and each import records sanitized source provenance and outcome in the newdocument_importstable from migration 026. A shared conformance suite keeps URL and connection adapters aligned on source-opening behavior. -
#766 by @bobsingor – Implement server-side PDF imports from caller-supplied URLs through the existing document lifecycle. Deployment policy controls size, timeout, concurrency, HTTPS, and public-network requirements; URL handling blocks private and metadata addresses with DNS pinning, rejects redirects, and requires
Content-Length. Imports enforce optional size and SHA-256 pins, sanitize failures so URL secrets do not leak, leave documents pending after retryable transport failures, and add thepullupload kind in migration 025.
-
#766 by @bobsingor – Mount the document import handler under the renamed
documents.importFromcontract operation and align the server's import policy and lifecycle terminology. The HTTP route and runtime behavior remain unchanged. -
#766 by @bobsingor – Cloud ObjectStore adapters (S3, GCS, Azure Blob) now truly stream
putbodies: aReadableis hashed and length-enforced as it flows (constant memory) instead of being buffered whole. Under- or over-delivery aborts before a visible object can appear, any prior object at the key survives a failed attempt, and the SHA-256 metadata is attached post-stream (S3 via a same-key server-side copy). FsObjectStore now cleans up its.partialfile when the source stream errors mid-put.
-
#768 by @bobsingor – Consolidate base-page and deep-zoom tile painting into
RenderLayer, with atilesoption for lenses that explicitly disable tiling. The separateTileLayersurface is removed because tile engagement is now render-policy arithmetic owned byRenderLayer.Tiles are positioned directly in view-pixel space and use the shared painted-image lifecycle, keeping retained coverage until replacements have a presentation opportunity and avoiding deep-zoom rounding drift, incomplete-image outlines, and transient seams.
- #768 by @bobsingor – Add
bindPaintedImage, a framework-neutral browser adapter for binding object-URL raster sources to image elements. It hides incomplete images, owns abort and URL-revocation cleanup, and reports painted and unpainted state around the image's presented lifetime so React, Vue, Svelte, and Angular adapters can share the same minimal lifecycle.
-
#768 by @bobsingor – Add a configurable render strategy for exact and lattice-backed deployments, with separate full-page and tile-plane budgets, format conformance, settled level selection, and public paint settings.
Deep-zoom tiling now uses bled overlap, presentation-aware generation retention, bounded fetch backpressure and raster residency, stage-less demand limits, stronger raster identities, failure isolation, and optional diagnostics. These changes keep tile memory bounded while preventing stale reuse, visible seams, and quality regressions during zoom and pan transitions.
- #768 by @bobsingor – Expose transient
cameraRestingstate and defer page-origin device snapping while zoom is moving. Pages retain fractional placement through continuous zoom and snap once the camera settles, preventing anchor jitter and per-step content movement without sacrificing crisp resting placement.
- #768 by @bobsingor – Fix the default inline image-encoder worker path so it creates the bundled blob worker instead of attempting to fetch
/inlineand silently falling back to main-thread encoding. Tile rendering now keeps encoding work off the main thread under the default configuration.
- #768 by @bobsingor – Bound individual annotation-appearance raster allocations at deep zoom by reducing the effective appearance scale while preserving the original placement rectangle. Oversized page-spanning appearances now degrade softly instead of exhausting the wasm heap with multi-gigabyte bitmap requests.
- #768 by @bobsingor – Adopt the unified
RenderLayerpage composition so the full viewer gets policy-driven deep-zoom tiling without mounting a separate tile layer. Base and sharp tile pixels now follow one rendering lifecycle through zoom, pan, annotation, and page-view surfaces.
Release Next v3.0.0-next.5
- #759 by @bobsingor – Support full-fidelity page text snapshots and character-space search ranges through the cloud engine transport. Remote text extraction now preserves the same character-to-text mapping and search-to-selection semantics as the local engine.
- #759 by @bobsingor – Add an explicit character-to-text map to page text snapshots, with shared helpers for translating boundaries, converting text offsets to character ranges, slicing selected text, and validating the wire representation. Search hits are now defined in character space, and reusable conformance coverage verifies non-printing characters, supplementary-plane text, and exact search-to-selection round trips.
- #759 by @bobsingor – Return full-fidelity page text snapshots and character-space search ranges from the local engine. Non-printing and supplementary-plane text now round-trip consistently through extraction, search geometry, selection ranges, and text slicing.
- #759 by @bobsingor – Expose full-fidelity UTF-16 page-text extraction and character-to-text mapping through the EmbedPDF PDFium runtime. Supplementary-plane characters are preserved, while non-printing character slots are represented explicitly instead of silently shifting selection and search offsets.
- #759 by @bobsingor – Build validated full-fidelity page text snapshots from the runtime's text and character-map calls. Search now converts matched string offsets back into character-space ranges before producing hit geometry, so search, selection, and copied text remain aligned when extracted text diverges from PDF character slots.
- #759 by @bobsingor – Add a shared
Anchoredoverlay primitive with same-commit Stage projection and measured PageView support. Annotation menus now use this common surface-aware path, replacing the separate PageView menu components, and newSelectionMenuandSelectionClipboardcomponents provide settled text-selection actions and clipboard integration.
- #759 by @bobsingor – Add framework-neutral anchored-overlay projection and placement utilities for Stage and standalone page surfaces. Add browser clipboard helpers for selected-text prefetch, native copy events, keyboard fallback, and user-initiated clipboard writes while keeping selection plugins DOM-free.
- #759 by @bobsingor – Add
markupFromSelection()for creating one oriented text-markup annotation per selected page and clearing the consumed selection. Multi-click draft finish and cancel actions are also available on the public annotation capability for composable menu controls.
- #759 by @bobsingor – Expand the public selection capability with permission checks, programmatic character ranges, select-all, settled gesture state, menu anchors, range snapshots, and full selected-text extraction. Host-only gesture and geometry plumbing now lives behind the
/internalexport, while clipboard access remains DOM-free and outside the plugin.
- #759 by @bobsingor – Add a floating text-selection strip with permission-aware Copy, native keyboard clipboard wiring, localized labels, and shared contextual-strip rendering. A successful menu copy clears the unchanged selection so both its highlight and menu dismiss, while failed or superseded copies preserve the current selection.
- #760 by @bobsingor – Keep connected licenses usable during Keygen's three-day
EXPIRINGwindow by relying on the signed validation decision instead of the informational status label. Licenses whose expiry has elapsed remain denied.
- #759 by @bobsingor – Keep search result ranges aligned with selection character space when extracted text contains non-printing or supplementary-plane characters. A result's
charStartandcharCountcan now be passed to selection and markup flows without offset drift.
Release Next v3.0.0-next.4
- #749 by @bobsingor –
open({ kind: 'share' }): the engine resolves public share tokens itself.- The share arm exchanges
shr_…for a session JWT on the engine's own transport (baseUrl+ configuredfetch) and delegates to thetokenarm, so the handle binds to a self-renewing source — revoking or editing the share retargets the open at the next renewal. Works on an engine constructed with no engine-level token at all. - Exchange failures surface as
EngineErrors — onopen()and on every later renewal (RPCs, SSE reconnects) — never as rawShareExchangeErrors. Protected grants reject with the newEngineErrorCode.SharePasswordRequired; the wire code and HTTP status ride indetails, the original error incause. The mapping is exported asengineErrorFromShareExchange. shareSessionSourcenow declares its concrete return type (() => Promise<string>) instead of theTokenSourceunion;HttpClientexposesbaseUrlandfetchImplgetters.
- The share arm exchanges
- #749 by @bobsingor – Share sources pass through to the engine; the viewer-only share vocabulary is retired.
{ kind: 'share' }is a standardOpenInputkind now, resolved byengine.open()itself —resolveCloudConfigno longer lowers share entries into token sources, and no longer re-threadsbaseUrl/fetchinto the exchange.- BREAKING (prerelease line): on share sources the grant passphrase field is
sharePassword(waspassword, which now means the PDF's own encryption password — the same slot every other kind uses). The top-levelshareToken/sharePasswordshorthands are unchanged. CloudShareSourceandCloudInitialDocumentremain as deprecated aliases ofOpenInputShareandInitialDocument.
-
#755 by @bobsingor – Model carets anchored to rotated text as oriented box geometry.
- Add
caretGeomFromAnchor, which places the caret at the trailing glyph edge and derives its authoring rotation from the text baseline while preserving the previous byte-identical upright geometry. - Carry optional rotation on caret geometry, apply it to local-frame hit testing, and expose an oriented selection outline without enabling caret rotate or resize gestures.
- Add
-
#755 by @bobsingor – Models text-markup annotations with semantic text quads and adds oriented caret anchors, allowing highlight, underline, strikeout, squiggly, caret, and replace-text geometry to follow the selected text frame.
- #755 by @bobsingor – Adds semantic
TextQuadgeometry with corner-named transforms, bounds, rectangle conversion, positional PDF quad conversion, and resilient normalization for imported/QuadPoints.
-
#755 by @bobsingor – Add the canonical affine-aware text layout engine under
text/layout:buildPageTextLayout,textGlyphAt,expandTextRangeToWord/Line,textGlyphQuad, andtextSegmentsForRange, producingPdfTextSegment { quad, rect, advance }.Orientation frames are derived from the semantic edges of glyph quads and keyed by baseline direction and ascent handedness. Rotated and mirrored text become upright inside their frame, while shear remains an in-frame variation so mixed roman and italic text stays in one segment. Every run in a cluster uses the same canonical frame, and upright documents retain a byte-identical fast path.
-
#755 by @bobsingor – Extend the caret annotation contract with box-family rotation metadata.
Caret DTOs, drafts, patches, and schemas now carry optional
rotationandunrotatedRectfields with the same tri-state semantics as other box-family annotations. Rotation-stripped appearance documentation now includes carets. -
#749 by @bobsingor – Adds
shareto theOpenInputunion and aSharePasswordRequiredengine error code.OpenInputShare({ kind: 'share', shareToken, sharePassword?, password? }) is the third cloud reference form, alongsideidandtoken: a public share token from the dashboard's embed snippet, resolved by the cloud engine itself. Rejected by@embedpdf/engine, like the other cloud kinds.sharePasswordis the grant's passphrase (checked at exchange);passwordstays the PDF's own encryption password, same slot as every other kind.EngineErrorCode.SharePasswordRequiredis the prompt-and-retry signal for protected grants — the share sibling ofDocPasswordRequired.
-
#755 by @bobsingor – Extends page geometry snapshots with an upright/rotated run union, oriented glyph cells, rotation and ascent-flip metadata, uniform quad and bounds helpers, wire schemas, and orientation conformance coverage.
- #755 by @bobsingor – Returns orientation-aware page text geometry from the local engine so consumers can select and annotate rotated, sheared, and mirrored text without collapsing glyph cells into axis-aligned boxes.
- #751 by @bobsingor – Export the shared document types (
OpenInput,OpenOptions,DocumentHandle,PageHandle,DocumentCapabilities,TokenSource, …) from the package root, mirroring@cloudpdf/engine, so code driving the engine directly can name them without importing from the transitive@embedpdf/engine-coredependency.
-
#755 by @bobsingor – Generate rotated caret appearances in the EmbedPDF PDFium runtime.
The caret appearance generator now consumes the shared rotation metadata pair, draws in the logical unrotated box, and emits the form transform needed for the baked caret to follow its text baseline.
-
#755 by @bobsingor – Updates the EmbedPDF PDFium runtime with oriented per-character geometry and orientation-aware text-markup appearance generation for rotated, sheared, and mirrored text, while retaining safe fallbacks for malformed quads.
-
#755 by @bobsingor – Replace the parallel
rects[]andquads[]geometry onSearchMatchwith canonicalsegments: PdfTextSegment[], validated byPdfTextSegmentSchema.Search tokens now always encode
format=segments1, preventing newer clients from consuming stale CDN-cached responses with the old geometry shape; old tokens fail decoding instead. -
#755 by @bobsingor – Persist and render caret rotation through the engine annotation services.
- Read and write the caret
rotationandunrotatedRectmetadata pair during create, patch, and list operations. - Treat caret subtype 14 as box-family when rendering annotation appearances, returning a rotation-stripped raster placed by the logical box so consumers do not double-rotate it after reload.
- Read and write the caret
-
#755 by @bobsingor – Reads boxes, oriented cells, flags, and text orientation through the new runtime geometry call, preserving the compact upright wire shape while emitting rotated runs for non-upright glyphs. Native page-redaction failures are now reported instead of being mistaken for pages without redaction annotations.
-
#755 by @bobsingor – Render search and selection highlights from canonical text segments. Axis-aligned lines retain their classic appearance, while rotated lines render their true oriented cells.
-
#755 by @bobsingor – Render live caret annotations with their text-baseline rotation.
The React annotation painter now treats caret SVGs as box-family visuals, applying the caret's authoring rotation about its centre while continuing to leave vertex-geometry rotation advisory.
- #755 by @bobsingor – Renders text-selection highlights from oriented segment polygons so the React selection layer follows rotated, sheared, and mirrored text.
-
#755 by @bobsingor – Round-trip rotated caret geometry through the annotation repository.
Rotated carets now lower their logical box and content-space tilt into
/Rect,rotation, andunrotatedRect, and reconstruct that geometry when engine annotations are ingested. Upright writes explicitly clear stale transform metadata. -
#755 by @bobsingor – Creates, previews, imports, and persists text-markup annotations with oriented quads and places caret and replace-text annotations at the selected glyph's trailing edge.
- #755 by @bobsingor – Represent search-hit geometry as canonical
segments: TextSegment[]with a precomputedboundsenvelope. Search reveal now passes that envelope directly tostage.reveal(hit.pageIndex, { rect: hit.bounds })instead of manually folding rectangles.
-
#755 by @bobsingor – Use the engine's canonical text segmentation while keeping selection gestures and state in the plugin coordinate seam.
SelectionSnapshot.pagesnow carries segments only, with boxes exposed as derived views throughsegment.rectandrectsForPage(). Public geometry exports are nowbuildSelectionPageGeometry,contentPointToPdf,toContentSegment, andtoContentTextQuad. -
#755 by @bobsingor – Builds selections as oriented line segments, exposes their semantic quads and reading direction, and anchors selection endpoints to glyph cells while retaining AABB access for scrolling and conservative regions.
- #750 by @bobsingor – Preserves configured CORS response headers on the hijacked server-sent events stream, allowing browser clients on permitted origins to subscribe to document layer events.
- #755 by @bobsingor – Preserves oriented selection quads through text-redaction marks, previews, and native apply so rotated, sheared, and mirrored text can be redacted without expanding the mark to its axis-aligned bounds.
Release Next v3.0.0-next.3
-
#748 by @bobsingor – Derives the connected usage-reporting credential from the license key, so a connected deployment is configured with
CLOUDPDF_LICENSE_KEYalone.- Computes the reporting credential as
cpr_v1_+ base64url(HMAC-SHA256) over a domain-separated message that binds the signedcloudpdfLicenseIdlicense metadata, so the wire credential is one-way (it can never reveal the license key) and never authenticates another license record. - Retires
CLOUDPDF_LICENSE_REPORTING_TOKEN. A deployment that still sets it boots normally; the variable is ignored and the server logs a warning asking for its removal. - Existing connected deployments upgrade by removing the retired variable. During the coordinated verifier cutover on the CloudPDF side a usage report may answer 401; reports retry every five minutes with cumulative counters, so no usage is lost and license validation is unaffected.
- Air-gapped deployments are unchanged and continue to send no telemetry.
- Pins fixed cross-runtime derivation test vectors shared with the CloudPDF control plane.
- Computes the reporting credential as
-
#746 by @bobsingor – Fixes presigned-upload materialization and makes commit-time sha verification single-read and constant-memory.
- Fixes the range materializer crashing with
EBADFwhenever an object carried no SHA metadata — the shape of every presigned browser upload. The failure was silent: commits still reachedreadywhile the security probe recordedunknownand thumbnail warming recordedfailed. The hash fallback now closes the write-only handle and streams the finished partial from disk, guards against short positional writes, and rejects a metadata/expected-sha disagreement before paying for the download. - Replaces the S3 and FS
getSha256fallbacks that buffered whole objects in RAM with streaming hashes — constant memory regardless of document size. - Commit now verifies uploaded bytes with a single object-store read when a base-file cache is wired (
DocumentLifecycleOptions.fileCache): the upload is materialized into the cache, hashed on the way down, and reused by the security probe instead of being downloaded a second time.LocalFileHandle.sourceKeyreports which object key materialized a content-addressed entry, so a cross-key cache hit still triggers a direct verification of the committing document's own object. - Adds a typed
ShaMismatchError(exported) thrown by allmaterializeLocalimplementations, letting callers distinguish declared-hash mismatches from retryable transport failures. - Surfaces previously swallowed failures:
DocumentSecurityProbeOptions.onError,DerivedRenderServiceOptions.onWarmError, and base-file-cachematerialize-errorevents are now wired to the server log.
- Fixes the range materializer crashing with
Release Next v3.0.0-next.2
-
#730 by @bobsingor – Adds the share-grant contract: standing, revocable authorization decisions that let a document be embedded with no backend.
- Defines
shares.create,shares.list,shares.get,shares.update, andshares.deleteunder/v1/tenants/:tenantId/shares, governed by the newshares.managetenant scope. - Defines
shares.exchangeatPOST /v1/share-sessions, the contract's only unauthenticated operation: the grant row is the authorization, so a public share token trades for a short-lived document session JWT. The registry test now pins that surface, making any future credential-less operation an explicit decision. - Adds an optional
originsallowlist to document-token issuance, so a minted token can be restricted to named web origins. - Adds
tenants.usagefor per-tenant usage facts, plustenants.suspendandtenants.resumefor operator-controlled tenant suspension. - Reports tenant
statuson tenant records and regeneratesopenapi.json, which now carries 44 operations.
- Defines
-
#734 by @bobsingor – Adds the integrity-pinned
init → transfer → commitdocument upload protocol, including presigned PUT and policy-controlled multipart proxy transfer modes.
- #730 by @bobsingor – Adds share-session support, the client half of the no-backend embed flow.
- Adds
exchangeShareToken, which trades a public share token for a short-lived document session, andShareExchangeError, whosecodenames the outcome (SharePasswordRequired,OriginNotAllowed,ShareExpired,NotFound). - Adds
shareSessionSource, a caching token source that re-exchanges shortly before expiry and shares one in-flight exchange between concurrent callers. Because the transport resolves its token source on every request and on stream reconnect, renewal needs no timers and no listeners. - Requires no change to
open(): an exchanged session is an ordinary document-scoped JWT, so a share source feedsopen({ kind: 'token' })unchanged, and each open keeps its own credential.
- Adds
- #734 by @bobsingor – Adds the generated TypeScript SDK and its high-level
uploads.createworkflow. It hashes browser and Node.js upload sources, negotiates presigned or proxy transfer, uploads the bytes, and commits only after integrity verification.
-
#730 by @bobsingor – Implements share grants, origin locking, per-tenant usage, and tenant suspension.
- Stores share grants whose row id is the public share token, carrying document capabilities, an optional origin allowlist, an optional scrypt-hashed passphrase, a session TTL, and an optional expiry. Editing or deleting a grant retargets every embedded copy of its token at the next exchange.
- Serves the public
POST /v1/share-sessionsexchange, which validates origin, passphrase, expiry, disablement, and tenant suspension before minting a document session JWT. Unknown, revoked, disabled, and suspended grants answer alike so the existence of a grant is never disclosed, and the route carries its own per-IP and per-grant limiters rather than the authentication-failure budget. - Enforces an optional
originsclaim on document tokens for every request that arrives with a browserOriginheader, covering both share sessions and backend-minted tokens. Requests without the header are governed by the token itself. - Adds CORS through
CLOUDPDF_CORS_ORIGINS(*to reflect, or a comma-separated allowlist), which browser-direct deployments need. Bearer tokens remain the security boundary; per-credential origin locks carry the origin policy a server-wide list cannot express. - Records per-tenant usage facts for views, uploads, and stored bytes, readable at
GET /v1/tenants/:tenantId/usage. A view is a share exchange or an authorized/v1/accessgrant, counted once across the two. These counters hold no limits and are separate from license metering. - Adds
tenants.suspendandtenants.resume, which fail every tenant JWT, document JWT, and share exchange closed while leaving the root API token free to inspect, resume, or delete the tenant. - Mounts token revocation from the CLI through
CLOUDPDF_ENABLE_REVOCATION. - Records share and suspension lifecycle events in the security-event trail, and adds matching SQLite and PostgreSQL migrations plus origin, passphrase, and end-to-end share coverage.
-
#734 by @bobsingor – Adds integrity-pinned uploads with presigned storage transfer preferred and a policy-controlled multipart proxy fallback.
Hardens filesystem-backed storage against path traversal, storage-root deletion, and recursive deletion through symbolic links.
- #730 by @bobsingor – Accepts public share tokens, so a viewer can be embedded with a dashboard-generated snippet and no backend.
- Adds the
shareTokenandsharePasswordoptions for opening a single shared document. - Adds a cloud
{ kind: 'share' }document source fordocuments, so a multi-tab viewer can mix share tokens, document tokens, and document ids. Each entry exchanges and renews independently, and revoking one share leaves the others untouched. The source is lowered to an ordinary token source before the engine-agnostic viewer core sees it. - Re-exports
exchangeShareToken,shareSessionSource, andShareExchangeErrorso CDN-only consumers can build custom flows, such as prompting for a passphrase before mounting.
- Adds the
- #730 by @bobsingor – Accepts public share tokens on
CloudPDFViewer, inherited from the cloud vocabulary it already shares with the snippet.- Adds the
shareTokenandsharePasswordprops for rendering a shared document without a backend. - Accepts cloud
{ kind: 'share' }entries indocuments, so a multi-tab viewer can mix share tokens, document tokens, and document ids.
- Adds the
Release Next v3.0.0-next.1
- #720 by @bobsingor – Reworks the backend administration SDK around explicit tenant addressing and deployment API-token authentication.
- Replaces the flat document client with
cloud.tenant(tenantId), accepting exactly one rootapiTokenor delegatedtenantTokencredential. - Adds tenant lifecycle APIs for creating, listing, iterating, retrieving, and deleting tenants.
- Adds tenant token APIs for issuing document or tenant JWTs and revoking them by
jti. - Adds keyset-paginated document listing, lifecycle-state filtering, and an async document iterator.
- Moves the SDK's shared schemas and route definitions to
@cloudpdf/contract.
- Replaces the flat document client with
- #720 by @bobsingor – Introduces the complete CloudPDF backend HTTP contract, replacing the narrower
@cloudpdf/admin-apipackage.- Defines a typed operation registry and Zod request/response schemas for tenant administration, document lifecycle, token delegation, deployment status, and backend-callable document-plane operations.
- Exposes tenant-aware route builders and operation metadata shared by the admin SDK and server.
- Adds an OpenAPI 3.1 emitter, a packaged
openapientry point, and the generatedopenapi.jsonartifact. - Validates operation IDs, route coverage, schema references, security declarations, and generated OpenAPI output with contract tests.
- #720 by @bobsingor – Adds the tenant-scoped backend API and root API-token workflow to the self-hosted CloudPDF server.
- Replaces the legacy flat admin routes with contract-backed
/v1/tenants/:tenantIddocument, tenant, token, and deployment operations. - Adds constant-time root API-token authentication alongside delegated tenant JWT authorization.
- Adds tenant lifecycle and provenance tracking, keyset pagination and state filtering, and cascade deletion for tenant-owned data.
- Adds document and tenant token issuance, revocation, and durable security-event auditing.
- Allows API tokens on document-plane routes and supports per-request
X-Document-Passwordauthorization through HMAC proofs or non-mutating checks against the canonical PDFium session, including credential-safe open singleflight behavior. - Adds matching SQLite and PostgreSQL migrations plus expanded registry, authorization, password, and end-to-end coverage.
- Replaces the legacy flat admin routes with contract-backed
- #720 by @bobsingor – Exports
wireTemplates, the canonical Fastify-style path templates for backend-callable, unversioned document-plane routes. The templates let@cloudpdf/contractand server route-conformance checks share one source of truth without exposing the viewer-only immutable URL variants.
Release Next v3.0.0-next.0
- #711 by @bobsingor – Introduces the rebuilt Node.js administration SDK for CloudPDF. It lets trusted backends upload and manage documents, inspect deployment state, and mint scoped viewer tokens without exposing administrative credentials to the browser.
- #711 by @bobsingor – Introduces the shared CloudPDF administration contract package. It provides the HTTP route definitions and Zod schemas used by both
@cloudpdf/adminand@cloudpdf/serverso client and server stay wire-compatible.
- #711 by @bobsingor – Introduces the CloudPDF implementation of the Engine v3 interface. It gives browser applications the same document API as the local engine while executing PDF operations remotely through CloudPDF over HTTPS.
- #711 by @bobsingor – Introduces the self-hostable CloudPDF document server. The Fastify service combines authentication, durable storage, native PDF processing, realtime document events, and commercial license enforcement behind the Engine v3 HTTP API.
- #711 by @bobsingor – Introduces the complete EmbedPDF viewer preconfigured for CloudPDF. It ships as a single CDN-ready
cloudpdf.jsartifact with no browser-side WASM or workers because document processing happens on the CloudPDF server.
- #711 by @bobsingor – Introduces the React wrapper for the CloudPDF-powered full viewer.
<CloudPDFViewer>accepts CloudPDF document tokens while retaining React-owned slots, context, and styling around the shared viewer component.
- #711 by @bobsingor – Introduces the pure Acrobat JavaScript compatibility core. It defines deterministic document-script contracts, security policy, and the VM prelude used to execute supported PDF form scripts consistently.
- #711 by @bobsingor – Introduces the rebuilt annotation domain core. It contains the framework-free annotation model, update/effect logic, content-space geometry, hit testing, and drawing-tool behavior shared by every UI integration.
- #711 by @bobsingor – Introduces the shared 2D geometry foundation for EmbedPDF. It provides points, sizes, page rotation, and coordinate transforms used across the stage, annotations, selection, and framework adapters.
- #711 by @bobsingor – Introduces the QuickJS-backed sandbox for PDF JavaScript. It provides deterministic, resource-bounded script execution for interactive forms without running document code directly in the host JavaScript context.
- #711 by @bobsingor – Introduces the rebuilt framework-independent EmbedPDF kernel. It owns serializable state, typed plugin capabilities, effects, scopes, and plugin lifecycle without depending on the DOM or a rendering engine.
- #711 by @bobsingor – Introduces the pure spatial model behind the EmbedPDF stage. It models scenes, layout strategies, camera state, anchors, framing, and coordinate conversion without framework or DOM dependencies.
- #711 by @bobsingor – Introduces the framework-free core for full-viewer chrome. It defines structural UI schemas, toolbar measurement and overflow solving, menu projection, and keyboard shortcut parsing without owning rendered components.
- #711 by @bobsingor – Introduces the transport-independent Engine v3 contract. It includes engine and document interfaces, DTOs, wire schemas, error handling, abortable operations, and a conformance harness shared by local and cloud implementations.
- #711 by @bobsingor – Introduces the rebuilt local Engine v3 implementation. It runs the EmbedPDF Runtime in a Web Worker for browsers or inline for Node.js and exposes the same abortable document interface as the CloudPDF engine.
- #711 by @bobsingor – Introduces the low-level EmbedPDF execution runtime backed by the EmbedPDF PDFium fork. It selects and exposes the appropriate WASM or native platform build used by higher-level engine services.
- #711 by @bobsingor – Introduces the runtime-independent Engine v3 service implementations. The same document, page, annotation, form, search, and mutation logic runs over both the local WASM runtime and CloudPDF's native worker runtime.
- #711 by @bobsingor – Introduces the rebuilt Angular adapter for EmbedPDF v3. It provides reactive bindings, structural viewer and stage components, injectable capabilities, and headless feature layers through focused secondary entry points.
- #711 by @bobsingor – Introduces the rebuilt React adapter for EmbedPDF v3. It provides generic reactive bindings, structural viewer and stage components, hooks, and headless feature layers while leaving application UI composition fully under React's control.
- #711 by @bobsingor – Introduces framework-independent browser adapters for EmbedPDF v3. It centralizes DOM-facing services such as file selection, clipboard access, printing, downloads, and external navigation so core packages remain platform-neutral.
- #711 by @bobsingor – Introduces the completely rebuilt annotation plugin for EmbedPDF v3. It connects the pure annotation model to engine-backed create, update, and delete operations while contributing editing and drawing tools through the interaction system.
- #711 by @bobsingor – Introduces the command registry used as the viewer's shared vocabulary of actions. Commands derive reactive labels, icons, enabled state, visibility, and active state from capabilities so toolbars, menus, and shortcuts use one source of truth.
- #711 by @bobsingor – Introduces the rebuilt interactive-form plugin. It provides reactive field state, fill-mode controls, form-data import and export, and sandboxed Acrobat JavaScript execution while keeping widgets integrated with the annotation plane.
- #711 by @bobsingor – Introduces workspace-scoped internationalization for EmbedPDF. It provides reactive locale state, BCP-47 negotiation, pure translation lookup, and lazy locale-pack loading before or after a document engine is available.
- #711 by @bobsingor – Introduces the framework-independent interaction hub. It coordinates the active tool, cursor, pointer routing, and contributed handlers without embedding feature-specific knowledge about annotations, selection, forms, or links.
- #711 by @bobsingor – Introduces the rebuilt PDF link plugin. It exposes clickable link regions and a single activation path for document destinations and external URIs while cooperating with the annotation editor when link editing is enabled.
- #711 by @bobsingor – Introduces reactive document metadata for EmbedPDF v3. It reads and updates the PDF information dictionary and stays synchronized with both local edits and remote document events.
- #711 by @bobsingor – Introduces the document page-edit capability. It provides stable page-addressed rotation, movement, and deletion operations together with the pending, gating, and state logic needed by framework integrations.
- #711 by @bobsingor – Introduces the rebuilt redaction workflow. It manages pending redaction marks, estimates affected content, and applies destructive redaction through the document engine while leaving mark creation to the annotation system.
- #711 by @bobsingor – Introduces the document-scoped rendering capability for EmbedPDF v3. It conforms page requests to the engine's render policy, manages abortable raster loading, and provides the tile planning and retention system used for sharp deep zoom.
- #711 by @bobsingor – Introduces the rebuilt document-search plugin. It drives budgeted engine search, converts result geometry into viewer coordinates, prioritizes visible pages, and reveals the active result through the stage.
- #711 by @bobsingor – Introduces framework-independent text selection. It reads engine text geometry, maps PDF coordinates into viewer content space, hit-tests glyphs, and exposes highlight geometry through the shared interaction system.
- #711 by @bobsingor – Introduces serializable workbench state for panels, modals, overlays, menus, and other named viewer surfaces. Applications retain ownership of rendered UI while commands and persistence operate on one consistent shell model.
- #711 by @bobsingor – Introduces the rebuilt document stage plugin. It combines scrolling, viewport measurement, zoom, pan, spread layouts, navigation, and coordinate conversion through pure intents and selectors built on
@embedpdf/core-stage.
- #711 by @bobsingor – Introduces reusable PDF stamp libraries. It imports PDF pages as vector stamp assets, generates previews, supports opt-in dynamic stamps, and places selected stamps through the annotation plugin's tool flow.
- #711 by @bobsingor – Introduces workspace-level document view management. It arranges open documents into reorderable panes while keeping each view associated with its own document scope and remaining independent of any UI framework.
- #711 by @bobsingor – Introduces the shared implementation package for the full viewer interface. It contains the measured toolbar, menus, panels, responsive layout, and default feature composition consumed by the distributable viewer packages.
- #711 by @bobsingor – Introduces the complete EmbedPDF viewer as a custom element and CDN-ready artifact. It exposes
<embedpdf-viewer>andEmbedPDF.init(), bundles the shared viewer chrome, and wires in the local WASM engine by default.
- #711 by @bobsingor – Introduces the React wrapper for the complete EmbedPDF viewer.
<PDFViewer>configures the custom element while React children remain in the application's React tree and project into named viewer slots.
Release v2.15.0
-
#685 by @simonmysun – Add
setSelection(range, documentId?)to the selection capability and document scope for programmatically applying or restoring a text selection.It accepts the same
SelectionRangeX({ start, end }glyph pointers) shape emitted byonSelectionChange, so a saved selection can be passed straight back in to restore it; passingnullclears the selection. Page geometry is loaded on demand, so the returned task resolves only once the highlight rects are computed. The range is normalized (start/end may be given in any order), invalid input (malformed range, non-integer/negative indices, out-of-bounds pages) is rejected, glyph indices are clamped to the available page geometry, and previously highlighted pages are repainted so switching to a disjoint selection no longer leaves stale highlights behind.
-
#703 by @berkayozdin – Make the precompiled Svelte output work on every Svelte 5 runtime
Svelte 5.56 changed the
excludeargument of the privaterest_propsruntime helper from an array (exclude.includes(key)) to aSet(exclude.has(key)). The*/svelteentry points ship precompiled component code, so output built against one side of that change throws on the other: the currently published packages fail withTypeError: exclude.has is not a functionon Svelte >= 5.56, which aborts the render of every EmbedPDF Svelte component.The Svelte build now routes those calls through a wrapper that hands the runtime an
excludevalue satisfying both contracts, so one published build stays valid across the wholesvelte: ">=5 <6"peer range.