Skip to content

Addressing pre-release checks - #1

Merged
necmes merged 44 commits into
mainfrom
pre-release-checks
Aug 17, 2026
Merged

necmes merged 44 commits into
mainfrom
pre-release-checks

Conversation

@simonc

@simonc simonc commented May 8, 2026 •

Copy link
Copy Markdown
Contributor

Pre-release housekeeping for the pdfmonkey npm package. Brings the repo up to publishable state across packaging, CI, public-facing docs, and SDK surface.

Packaging

  • Add repository, bugs, homepage, and author (tinymonkey@pdfmonkey.io) to package.json
  • Add ./package.json subpath to the exports map
  • Bump tsconfig target to ES2022
  • Remove vestigial pnpm-workspace.yaml
  • Include CHANGELOG.md in the published tarball

CI / release tooling

  • Expand CI matrix to Node 20/22/24/26 across ubuntu, macOS, and Windows
  • Add publint and attw to prepublishOnly and CI
  • Add Dependabot config for npm and Actions updates
  • Add Changesets release workflow with npm provenance
  • Bump biome schema to match installed CLI; enforce LF line endings via .gitattributes
  • Trim biome.json includes by relying on .gitignore

Repo hygiene

  • Add SECURITY.md with disclosure policy (contact: tinymonkey@pdfmonkey.io)
  • Add CONTRIBUTING.md with setup and PR workflow
  • Adopt Contributor Covenant 2.1 as Code of Conduct
  • Add CHANGELOG.md stub
  • Add GitHub issue and pull request templates

SDK surface

  • Fall back to PDFMONKEY_API_KEY env var when apiKey is omitted
  • Add nodejs.util.inspect.custom to error classes
  • Enrich User-Agent with Node version, platform, and arch
  • Thread ResourceRequestOptions through every resource method
  • Extract DEFAULT_SYNC_TIMEOUT constant
  • Add exponential backoff to waitForGeneration
  • Add Page.getPage(n) and Page.pages() helpers
  • Auto-stringify payload object on document mutations; narrow DocumentPayload to drop the array form
  • Add parseMeta helper for the read-side of Document.meta
  • Add discriminated union for WebhookEvent
  • Document why constantTimeEqual uses double-HMAC
  • Add BadGateway, ServiceUnavailable, GatewayTimeout error subclasses
  • Add onRequest / onResponse / onError client hooks
  • Add Documents.download and Documents.downloadStream helpers (resolves URL via document_cards.get)
  • Add APIError.toJSONRedacted for safe log forwarding
  • Expose retryDelay strategy as a ClientOptions hook
  • Extract buildListQuery helper from list endpoints

Docs

  • Add JSDoc @example blocks to top-level public surfaces
  • Refresh README for the new SDK surface (inline recipes replace the examples directory)

simonc added 30 commits May 8, 2026 14:09
Surfaces npm registry links and ecosystem trust signals (provenance,
"Repository" link on npmjs.com) that the placeholder repo metadata blocked.
Some bundlers, tsx, and require-subpath consumers need explicit access to
package.json. Without this entry, modern resolution rejects the lookup.
Aligns with Node 20+ runtime, lib: ES2022, and tsup target node20. The
previous ES2020 target downlevelled features (eg. Error.cause, .at()) the
SDK already relies on for AbortSignal.timeout-style usage.
Node 24 is the current LTS-track release as of 2025; macOS and Windows
exercise platform-specific fetch and crypto.subtle behaviors that have
historically diverged from Linux.
@vitest/coverage-v8 was a dev dep but never executed. Adds test:coverage
script, vitest config block (v8 provider, lcov/html reporters), and a
single-OS CI step that uploads to Codecov on Linux Node 22.
attw catches type-resolution drift but publint catches a separate class of
package.json/exports issues (bin paths, dual-package hazards, missing
engines fields). Both should run before publish.
Weekly schedule, dev deps grouped to reduce PR noise, separate ecosystem
for github-actions to keep workflow runners current.
The only setting (onlyBuiltDependencies) is already declared in
package.json under pnpm.onlyBuiltDependencies. The workspace file with
no actual workspaces was a confusing leftover.
Changesets will overwrite the body on each release; an initial stub
keeps README links and the npm tarball deterministic.
Documents the security@pdfmonkey.io reporting channel, supported version
window, and acknowledgement SLAs. Required for an "Official" SDK to give
researchers a clear path that does not involve a public GitHub issue.
Documents the local toolchain, scripts, Changesets workflow, and the
zero-runtime-dependencies invariant so external contributors do not need
to read the source to onboard.
Standard text with conduct@pdfmonkey.io as the reporting channel.
GitHub surfaces this file in the community profile and exposes it to
contributors during issue/PR creation.
Bug and feature YAML forms with required SDK and Node version fields,
plus a contact-links config that disables blank issues and routes
security and product questions away from the SDK tracker. PR template
codifies the lint/typecheck/test/build/changeset gates already enforced
in CI.
Wires changesets/action to either open a release PR or publish a tagged
version when the PR merges. id-token: write enables OIDC trusted
publishing and NPM_CONFIG_PROVENANCE=true makes npm record build
provenance against the GitHub Actions run.
Standalone files for the most-asked patterns: ESM and CJS quickstart,
generateSync, async-iterating pages, Express raw-body webhook, and a
Next.js App Router route handler. Also excludes examples and the
local-tooling directories (.wolf, .omc) from Biome so the lint scope
stays at src and config files.
Matches the Stripe/OpenAI/Anthropic pattern: callers can do
`new PDFMonkey()` and pick up the credential from the environment, or
`new PDFMonkey({ timeout: 5_000 })` and inherit the key while
overriding other options. Explicit apiKey still wins.
PDFMonkeyError prints as "Name: message"; APIError adds the HTTP status
and request id. Stops console.log/REPL output from dumping the noisy
default Error shape with stack and headers.
Stripe-style UA: "pdfmonkey-node/X.Y.Z node/20.11.0 darwin x64". Lets
PDFMonkey support diagnose runtime-specific reports without a full repro.
Falls back to "pdfmonkey-node/X.Y.Z" alone on edge runtimes where
process is unavailable.
Per-request headers merge on top of defaultHeaders but never replace
Authorization or User-Agent. idempotencyKey is forwarded to the
Idempotency-Key header — the standard way to make POSTs safe to retry
across PDFMonkey's create endpoints.
Adds a ResourceRequestOptions type (signal, timeout, maxRetries,
headers, idempotencyKey) and a trailing options argument on every
public method across documents, document-cards, document-templates,
rest-hooks, snippets, template-folders, workspaces, current-user, and
pdf-engines. Resources never set body or query through this surface —
those remain controlled by each method. waitForGeneration now forwards
its signal to the inner get call so an abort interrupts the polling
fetch instead of waiting for the next sleep window.
Replaces the inline 120_000 magic number in generateSync and exports the
constant from the public surface so callers can reference the same value
when wrapping the SDK in their own retry/timeout policies.
Doubles the poll interval up to maxInterval (default 10s) instead of a
fixed 2s loop. Existing interval semantics are preserved (used as the
starting point); maxInterval === interval disables backoff for callers
that want a constant cadence.
getPage(n) jumps to an arbitrary page number with explicit out-of-range
checks; pages() yields each Page in order, complementing the existing
item-level Symbol.asyncIterator. Useful for bulk imports that want to
process or count pages rather than items.
create/update/generateSync now accept a DocumentPayload (string or any
JSON value) and JSON-serialise objects/arrays before sending. Strings
still pass through verbatim to support callers that already do their
own templating. Removes the boilerplate JSON.stringify on every call
that the previous string-only signature forced.
The asymmetry between input (object or string accepted, auto-stringified)
and output (always a string) forced callers to write JSON.parse on every
read. parseMeta normalises to DocumentMeta | null and tolerates malformed
or non-object payloads instead of throwing.
WebhookEvent now narrows on `type` to DocumentDoneEvent (data has
download_url, filename) or DocumentErrorEvent (data has failure_cause),
with an UnknownWebhookEvent fallback for forward compatibility. Removes
the cast users previously needed to read event.data fields after a
verifyWebhook call.
Captures the design constraint (Web-Crypto-only for edge runtime
support) so a later contributor does not "simplify" the function to
node:crypto.timingSafeEqual or a plain string compare.
Each extends InternalServerError so existing instanceof InternalServerError
checks keep matching. Lets callers distinguish "PDFMonkey edge said
upstream timed out" (504) from a generic 5xx and pick a different retry
strategy.
Lets callers inject signing headers (mutate ctx.headers in onRequest),
ship metrics on every response, or wrap transport errors before they
become APIConnectionError. Each hook is awaited so async work composes
cleanly with retries; the response handed to onResponse is cloned so
the consumer's body stream does not race with the SDK's parser.
download() returns the PDF as Uint8Array; downloadStream() exposes the
underlying ReadableStream for piping to disk or HTTP responses. Both
accept a Document object or a string id (fetches first), throw a
clear error when download_url is null, and accept an options.fetch
override for proxying or custom transports.
simonc added 8 commits May 11, 2026 23:20
The default toJSON() preserves the response body for debugging, but
the body can contain user-supplied payloads or PII. toJSONRedacted()
returns name/message/status/requestId only — safe to ship to
observability pipelines without an extra mask step.
Callers can now plug in their own backoff (linear, fixed, jittered, or
"never sleep" for tests) without wrapping the SDK. The previous fixed
exponential-backoff-with-full-jitter implementation is the default.
Six resource list() implementations were re-implementing the same
"if param defined → q[name] = value" loop with subtle drift potential.
buildListQuery centralises filter, page, and sort handling so adding a
new filter is a one-line entry instead of a copy-paste.
PDFMonkey constructor, Page, Documents.create/generateSync/waitForGeneration,
and verifyWebhook now ship inline usage snippets that surface in the IDE
hover. Targeted at the calls a new user reaches for first; deeper
methods stay TSDoc-light to avoid maintenance churn.
Adds badges, edge-runtime support note, env var fallback, per-request
options block, retryDelay/hooks examples, download/downloadStream,
parseMeta, and discriminated-union webhook narrowing. Drops the manual
JSON.stringify boilerplate from payload examples now that the SDK
auto-serialises objects. Calls out the absence of documents.list (use
documentCards.list) and the PUT-only update semantics.
Drops @vitest/coverage-v8, the test:coverage script, the vitest coverage
block, the CI coverage + Codecov-upload steps, and the .nyc_output
gitignore entry. Coverage was not load-bearing — the value did not justify
the dependency surface and extra CI minutes.
The examples/ directory duplicated the README: quickstart, generateSync,
and pagination snippets were identical, and the CJS variant only differed
by a require() call. The two genuine gotchas — Express needing
express.raw to preserve the webhook payload, and Next.js App Router
needing an explicit runtime — are now folded into a "Framework recipes"
subsection under Webhooks. Single source of truth, less drift surface.
Drops 9 tests that either duplicated existing coverage or tested
trivial branches with no realistic regression path:
- env var fallback: removes "explicit wins" and "throws when env unset"
  cases, kept the positive path
- error inspect: removes the no-requestId variant, kept the with-id case
- Idempotency-Key: removes the absence-check, kept the present-check
- documents.get signal abort: removed (duplicates client-level test)
- parseMeta non-object cases: removed (basic + null + invalid already
  cover the branch logic)
- 5xx subclasses: kept the 502 mapping as the representative test,
  dropped the 503/504/500-not-BadGateway repeats
Net 203 tests, all passing.
@simonc
simonc force-pushed the pre-release-checks branch from 5db77ee to 2f333c6 Compare May 11, 2026 21:21
simonc added 3 commits May 11, 2026 23:54
PDFMonkey API does not honour the Idempotency-Key header, so exposing
the option implied a retry-safety contract that does not exist —
strictly worse than nothing. Per-request `headers` overlapped with the
onRequest hook (the canonical place to inject dynamic headers like
trace IDs), so it was a second way to do the same thing.

ResourceRequestOptions now shrinks to signal/timeout/maxRetries — the
options that actually map to client behaviour. README updated to point
header-injection use cases at the onRequest hook.
PDFMonkey templates bind named keys (e.g. {{ name }}, {{ items }}), so
the top-level payload must be an object. Allowing `unknown[]` advertised
a contract the API rejects.
#resolveDownloadUrl previously fetched the full Document just to read
download_url + status + id — fields a DocumentCard already exposes. The
card endpoint returns a much smaller payload. Public download() and
downloadStream() now also accept a DocumentCard so callers who already
have one from a list iteration skip the fetch entirely.

No other internal hot paths fall in the "fetch full doc, read few
fields" pattern (waitForGeneration returns the full Document to the
caller and stays as-is).
Comment thread package.json Outdated
Comment thread .github/workflows/ci.yml Outdated
Comment thread SECURITY.md Outdated
simonc added 3 commits May 12, 2026 14:06
- Bump biome.json schema to 2.4.13 to match installed CLI
- Apply biome auto-fix on three test files (trailing blank lines, array reflow)
- Switch author/security contact to tinymonkey@pdfmonkey.io
- Add Node 26 to CI matrix
Windows runners checked out CRLF, biome formatter requires LF.
biome's useIgnoreFile reads only the project .gitignore (not the
global one), so .wolf and .omc need to live in the local file.
With them there, the redundant !!**/dist, !!**/node_modules,
!!**/coverage, !!**/.wolf and !!**/.omc entries can go.
@simonc
simonc marked this pull request as ready for review May 12, 2026 12:23
@simonc
simonc requested a review from necmes May 12, 2026 12:24
@necmes
necmes merged commit eed440b into main Aug 17, 2026
6 checks passed
@necmes
necmes deleted the pre-release-checks branch August 17, 2026 09:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants