Thanks for your interest in partial-content. Issues and PRs are welcome; this page tells you what to expect and what we expect back.
- Zero runtime dependencies is a hard invariant. PRs that add a runtime dependency will be declined regardless of merit; devDependencies are fine.
- Correctness over convenience. Behavior follows the RFCs (9110, 9530,
6266/8187). When a change touches protocol behavior, cite the
section that justifies it in the PR description; where the ecosystem and the
spec disagree, the deviations are documented in
docs/DESIGN.md. - Small surface, deep implementation. New features must serve the core job (range + conditional serving for storage backends). If in doubt, open an issue first so we can talk scope before you write code.
- Docs describe the latest state only. No changelog-style "now supports"
language in README or docs; history lives in
CHANGELOG.md.
Use the issue tracker. The single most useful thing you can include is a minimal reproduction: request headers in, response status/headers out, and what you expected instead (with the RFC section if you know it).
Security reports: do not open a public issue; see SECURITY.md.
bun install # dependencies (Bun is the dev runtime)
bun test # full unit + property suite
bun run build # tsc -> dist/
bun run typecheck # tsc --noEmit over the shipped source
bun run typecheck:tests # tsc --noEmit over the suite
bun run check:doc-types # compile the TypeScript in README.md and docs/*.md
bun run lint # bans Bun-only builtins and monorepo imports in src/
npm run bench # e2e benchmark suite (optional, takes minutes)The library is TypeScript, ESM-only, built with plain tsc. Tests use
bun:test; kernel invariants also have property-based coverage
(fast-check) in src/__tests__/kernel.property.test.ts. Prefer a property
(round-trip, bounds, idempotence) over more example cases when a function
has an algebraic contract.
Two gates cover ground the build tsconfig does not:
typecheck:testscoverssrc/__tests__undertsconfig.tests.json. The build config excludes those files so they can never reachdist/, which left the suite typechecked by nothing: a test double could omit a field its interface requires, and the only symptom was a puzzling assertion failure, or none at all. It emits nothing, so the tarball is unaffected.check:doc-typesextracts every fenced```typescriptblock inREADME.mdanddocs/*.mdinto its own module and compiles it against the real source. A reference snippet that does not compile is the same defect as a function that does not, and it is worse for the consumer, who copies it before finding out. A block is skipped when it is not meant to stand alone: it opens with// doc-check: fragment(a deliberate excerpt), or it imports a package other than this one. Both gates run on pre-commit.
- Every behavior change needs a test that fails without the change.
- Run
bun test,bun run build,bun run typecheck:testsandbun run check:doc-typesbefore pushing; CI also verifies the packed exports map (attw), consumer type-resolution, per-export size budgets (.size-limit.json), and smoke-tests the built package on Node, Bun, and Deno. - Keep diffs reviewable: one concern per PR.
CI enforces per-module mutation-score floors nightly (Stryker; see
scripts/mutation-ci.mjs). For a module you changed:
bun run test:mutation -- --mutate src/<file>.tsSurviving mutants are a review signal, not automatically a defect: confirm a survivor represents a real coverage gap before adding a test for it, and never weaken a guard just to kill a mutant. If your change raises a module's baseline, raise its floor in the same PR; floors are never lowered to go green.
A run builds sandboxes under .stryker-tmp/, each a whole copy of the
package with its tests. A copy left in the tree is the trap: bun test
discovers the sandbox's tests and runs them against MUTATED source, so the
next plain test run reports failures that read exactly like a regression in
your change. Both test:mutation and test:mutation:ci route through
scripts/mutation-sandbox.mjs, which clears the directory on entry, on
SIGINT/SIGTERM, and on the way out, so interrupting a run is safe. A run
that finds the directory locked refuses and says so: another mutation run
owns it, and two runs sharing one temp directory corrupt each other's
results. The lint config also ignores the directory: a sandbox present
mid-run is a whole second copy of the package, a build artifact of the
mutation run rather than source anyone should lint.
Publishing is CI-only via npm Trusted Publishing: bump the version and
CHANGELOG.md, then push a v<version> tag matching package.json. The
release workflow builds, tests and smoke-tests the packed dist/ on Node 24,
Bun and Deno with read-only permissions, publishes that exact tarball with
provenance from a job holding only the OIDC permission, and then creates the
GitHub Release. There is no manual npm publish path.