Skip to content
API reference

TypeScript is the reference implementation. Go and Rust are experimental.

Maintainer guide ​

Documentation · Upgrading

This page covers repository validation, documentation maintenance, diagnostic benchmarks, and the existing release process.

Validation ​

Use Node.js 24 and the repository's pinned pnpm through Corepack. Go and Rust checks use their CI-pinned toolchains. Prepare Python 3.11 or later with python3 -m venv python/.venv and python/.venv/bin/python -m pip install -e './python[test,redis]'. Full formal checks also require the pinned Quint executable. Run make help for targets and prerequisites:

bash
corepack pnpm install --frozen-lockfile
make check
make integration

make check runs strict TypeScript checks and coverage, bundles/declarations, packed ESM/CJS consumer checks, Go vet/formatting/race tests, Rust formatting, Clippy and native tests, Python native tests, documentation builds, and evidence inventories. TypeScript, Go, Rust and Python replay the committed Quint smoke fixtures and protocol cases. Integration tests require a Docker-compatible runtime for Redis, Valkey, and Redis Cluster and exercise all four language bindings.

make formal checks the scheduled Quint models, generates the complete corpus, and requires full TypeScript, Go, Rust and Python replay with matching evidence fingerprints. make model-check runs the separate finite symbolic checks; it needs Java 21, tar and a checksummed Apalache release and is the only lane that does. make mutations challenges the tests with the catalogued implementation faults over that generated corpus and shared witness evidence; it does not depend on a port's replay report. Each port's mutation catalog defines its measured scope. make ci NODE22_BIN=/path/to/node22/bin/node runs the complete pipeline, including integrations, mutations and the exact Node 22.15.0 package floor. See the repository's formal guide for setup, reports and single-history reproduction.

Pull requests run fast native/smoke checks and real integrations. Changes to model or generator inputs also trigger fresh Quint artifact recomputation. Complete replay, mutation measurement and exploration run through the manual and weekly formal workflow, using the same Make targets as local runs. Its mode input selects replay (the generated corpus in every port, about 15 minutes), mutations (plus the port mutation campaigns, about 50 minutes) or full (plus the model checks and the model-fault campaign, about three hours). Before merge, run replay for changes to port behavior or a replay driver, and mutations when the change also edits a file a catalogued mutant rewrites. Run full for changes to a model's transitions, invariants or challenges, the kernel library, formal/tools/ or the Quint toolchain, and before a release or before accepting another port. A scheduled result applies to its recorded revision; it does not validate a later PR head. A smoke pass does not establish full conformance.

CI uses Node.js 24 for development and integration, then validates the packed package and zstd at the exact 22.x consumer floor, Node.js 22.15.0. The published engine range is >=22.15.0 <23.0.0 || >=23.8.0.

Match validation to the changed contract. Public API and adapter changes need packed-consumer coverage; Redis behavior needs real standalone/Cluster tests. Pay particular attention to complete-frame writes, atomic tracked reads, conditional refills, logical-age recovery, snapshot ownership, bounded telemetry, and mixed-version transitions. Documentation-only changes need source checks, example validation, and working links.

Maintaining the reference ​

The README is the landing page: keep evaluation, a runnable example and links there. Feature guides have one shared behavioral explanation plus selected native examples and notes. Native language guides explain contexts, ownership, codecs and installation; generated API references follow each port's source.

Follow documentation authoring when changing a guide or adding a language. Displayed runnable snippets import named regions from real native source files. CI compiles and executes those sources with behavioral assertions; API excerpts are explicitly labeled and do not claim the same test coverage.

When behavior changes, update the shared guide and affected native examples in the same PR. Check defaults and bounds against source, and document rollout or compatibility implications in Upgrading. The generated behavior catalogue reuses the formal case inventory; it does not turn a prose claim into proof.

Each implementation owns its native source, tests, examples and package metadata under typescript/, go/ or rust/. Shared guides, formal contracts, and repository validation remain at the root. The private root pnpm package dispatches TypeScript commands into typescript/; installation uses the shared workspace lockfile.

The npm tarball includes typescript/README.md as README.md, but not the site, so that README uses absolute hosted URLs. test:package checks its runnable TypeScript block and the native documentation source against the installed packed package. Keep its first TypeScript block self-contained and preserve its expected output in typescript/scripts/test-package.mjs when editing it. The root README is the repository landing page for every language.

For repository layout changes, build and pack the before and after revisions at the same package version, then compare the actual tarballs:

sh
node scripts/compare-npm-packages.mjs /path/to/before.tgz /path/to/after.tgz

The comparison requires identical package file paths and byte-identical file contents, apart from package.json. All manifest metadata must also match, with one narrow exception for the TypeScript directory move: the five docs:* commands may forward to their matching commands at the workspace root. This preserves the installed JavaScript, declarations, exports, dependencies, README and license while ignoring archive timestamps. Run corepack pnpm test:package as well to exercise the packed package in ESM, CommonJS and TypeScript consumers.

Run the documentation site ​

VitePress renders the Markdown in docs/ with grouped navigation, page outlines, syntax highlighting, and local search. Search runs in the browser using an index built with the site; it needs no external service or credentials.

bash
corepack pnpm docs:dev

To check the production output, run:

bash
corepack pnpm docs:build
corepack pnpm docs:preview

Open the /DialCache/ URL printed by the server. The build writes to docs/.vitepress/dist/ and fails on broken internal page links. Generated output and the local build cache are ignored by Git. When adding a page, include it in docs/index.md and the sidebar in docs/.vitepress/config.mts.

The dependency overrides keep stable VitePress on patched Vite 6.4.x. The config uses a Safari 14.1 target for builds and dependency optimization to remain compatible with the repository's patched esbuild. Revisit this scoped override and the targets when upgrading VitePress.

Publish to GitHub Pages ​

The site is hosted at lan17.github.io/DialCache. In the repository's Settings → Pages, select GitHub Actions as the build source. Keep base: "/DialCache/" in the VitePress config so links and assets work under the project URL.

The Documentation workflow builds every pull request. A push to main builds and publishes the site through the github-pages environment; the workflow can also be run manually from main to republish. Pull requests and manual runs from other branches cannot upload a Pages artifact or deploy. The deployment job uses GitHub's short-lived token and OIDC; no deployment secret is needed.

The published reference follows main independently of registry releases. Use the Markdown at a release tag when reading about an older installed version.

Cache-path benchmark ​

From a repository checkout, run the semantic microbenchmark after installing dependencies:

bash
corepack pnpm benchmark:request-local

The command builds typescript/dist before reporting ten scenarios: sequential request-local hits, sequential process-local hits, enabled bounded fallbacks, request-local coalescing fan-out, process coalescing fan-out, remote-read-deadline coalescing fan-out, tracked Redis hits with shadow omitted, tracked Redis hits deterministically outside a partial shadow ramp, a ramped-down warm-hit confirmation, and a ramped-down semantic-miss fill. Both shadow scenarios prove that the caller completes before detached Redis work. The benchmark is a maintainer tool and is not included in the published package. It asserts fallback counts, Redis behavior, coalescing state, timer cleanup, returned values, exactly-once SoT reuse, and conditional confirmation/fill without applying a timing threshold. Override its work sizes with DIALCACHE_BENCH_ITERATIONS and DIALCACHE_BENCH_FANOUT.

Redis write benchmark ​

With an otherwise idle Redis reachable at REDIS_URL (default redis://127.0.0.1:6379, e.g. docker run --rm -p 6379:6379 redis:6.2), measure the local build's write path. The benchmark resets global command statistics between cases, so use a disposable or dedicated instance:

bash
corepack pnpm benchmark:redis-write

The command builds typescript/dist, then runs sequential native writes at 100 B, 10 KiB, 100 KiB, and 1 MiB payloads. It reports SET, script, and TIME calls per operation, server-side SET cost from INFO commandstats, and client-side p50/p95 latency. Semantic assertions require exactly one SET, zero scripts, and zero TIME calls per write. Because operations are sequential, the benchmark validates command shape and single-operation latency; it does not measure saturated concurrent throughput or maximum write capacity. Like the cache-path benchmark it is a maintainer tool, is not part of the published package, and applies no timing threshold — absolute numbers depend on the machine, engine, and load, so compare runs only within one environment. Scale iteration counts with DIALCACHE_BENCH_WRITE_SCALE.

Stale-on-error benchmark ​

With Redis reachable at REDIS_URL, exercise the native-read design and a representative compressible payload:

bash
corepack pnpm benchmark:stale-on-error

The benchmark warms isolated keys, verifies that physical retention uses M, and reports fresh end-to-end hits, native reads of a retained frame, end-to-end stale recovery, and same-key coalesced recovery. It asserts exactly one adapter read per stale-recovery flight. A separate high-cardinality scenario holds delayed source calls open with distinct incompressible raw payloads, then reports process memory before retention, while every candidate is retained, and after recovery. Run the built script with node --expose-gc typescript/scripts/benchmark-stale-on-error.mjs for less noisy memory snapshots. It snapshots INFO commandstats and network byte counters around each scenario without resetting shared server statistics, and reports command, server-CPU, network, and client-throughput signals per operation. Semantic assertions cover compression, exact source/recovery/read counts, and returned values; timing and memory remain informational with no pass/fail threshold. Override work sizes with DIALCACHE_BENCH_STALE_ITERATIONS, DIALCACHE_BENCH_STALE_FANOUT, DIALCACHE_BENCH_STALE_PAYLOAD_BYTES, DIALCACHE_BENCH_STALE_MEMORY_KEYS, DIALCACHE_BENCH_STALE_MEMORY_PAYLOAD_BYTES, and DIALCACHE_BENCH_STALE_MEMORY_SOURCE_DELAY_MS.

Releasing ​

Publishing starts by manually running the Release workflow from current main.

After the package checks pass, Semantic Release selects the next version from Conventional Commits since the highest stable vX.Y.Z tag:

  • while the package is pre-1.0, breaking changes bump minor and retain their BREAKING CHANGE: footers for full release notes;
  • feat bumps minor; and
  • every other normal PR-title type bumps patch.

Patch types are fix, perf, docs, style, refactor, test, build, chore, ci, and revert. The highest required bump wins.

Major bumps resume when the project cuts 1.0.0. release.config.mjs implements this policy; change it and this guide together so the documented release table cannot drift from automation.

The workflow opens a release: <version> pull request containing only the matching versions in typescript/package.json, rust/Cargo.toml, the root crate entry in rust/Cargo.lock, and python/pyproject.toml. The release helper preserves dependency versions and rejects any other content or file changes. It uses Python 3.11 or later, included on GitHub's Ubuntu runners. release is a reserved Conventional Commit type configured not to request another release, so the version-control commit does not cause an extra bump. Before opening or reusing the PR, Cargo packages the committed candidate and builds the extracted archive with every feature enabled. Python builds a source distribution and then a wheel from that archive, checks their metadata with Twine, and installs the wheel with its Redis extra in a fresh environment to exercise the installed public API.

GitHub marks workflow runs for a pull request opened with GITHUB_TOKEN as approval-required. Approve those runs, review the pull request, and squash-merge it normally through the protected branch.

The merge triggers the publish job. Before any release side effect, it verifies:

  • current main;
  • the release commit subject;
  • the exact version-only changes in all four version files;
  • the matching npm, Cargo manifest, Cargo lockfile and Python versions;
  • the absent npm and Go tags;
  • the Go module path for the release major; and
  • Semantic Release's independently calculated version and commit.

It then reruns the npm checks and Rust/Python package verification, retaining the verified Python distributions for 90 days, before asking Semantic Release to:

  1. create the matching Git tag;
  2. publish the public npm package with provenance; and
  3. publish the GitHub release.

The workflow tags the same commit go/vX.Y.Z for the Go module, then separate publish-rust and publish-python jobs publish dialcache version X.Y.Z to crates.io and PyPI. Both jobs check out the captured release commit and verify both tags point to it. Python uploads the original verified wheel and source distribution from that workflow run. TypeScript, Go, Rust and Python therefore use one selected version and release commit; a port's first registry release need not start at 0.1.0.

Go has no package registry. A module version is a Git tag that go get resolves through the public module proxy, and a module in a subdirectory takes that directory as its tag prefix, so go get github.com/lan17/DialCache/go@vX.Y.Z resolves the tag go/vX.Y.Z. The TypeScript and Go ports share releases from the first Go tag onward; earlier npm versions have no Go tag. After pushing the tag, the workflow requests the version from proxy.golang.org and sum.golang.org so the proxy and the checksum database record it, and pkg.go.dev follows the proxy index. A fetched version is immutable: never move or delete a tag. To withdraw a bad version, add a retract directive to go/go.mod and publish a newer version that carries it; the retraction takes effect only once that newer version exists. From 2.0.0 the module path must gain a /v2 suffix before the release, and the pre-flight check rejects a release whose major does not match go/go.mod. If the tag step fails after the npm publication, create the tag by hand:

sh
git tag go/vX.Y.Z vX.Y.Z
git push origin go/vX.Y.Z

Set up crates.io publishing once ​

Before the first Rust release, sign in to crates.io with the owning GitHub account, verify its email, and create an API token allowed to publish the new dialcache crate. Add it as the repository secret CARGO_REGISTRY_TOKEN:

sh
gh secret set CARGO_REGISTRY_TOKEN --repo lan17/DialCache

Enter the token at the prompt. The first publication can run entirely in the workflow; no local cargo publish is needed. The package check does not verify registry credentials or ownership. Until the first publication succeeds, use a checkout dependency as described in the Rust guide.

After the crate exists, configure its Settings → Trusted Publishing for GitHub owner lan17, repository DialCache, workflow release.yaml, with no environment restriction (the job does not use a GitHub environment). Remove the repository's CARGO_REGISTRY_TOKEN secret after configuring that trust. Later runs use GitHub's short-lived identity through the pinned official rust-lang/crates-io-auth-action; the action revokes its temporary token when the job finishes. If the secret remains, it takes precedence over trusted publishing. See the crates.io setup guide.

docs.rs automatically queues a documentation build after publication. The crate metadata enables the Redis and Prometheus API documentation. The repository site still follows main independently of registry releases.

Retry a Rust publication ​

Registry publication is not atomic across npm, Go tags and crates.io. If only publish-rust fails, fix its credentials or transient failure and choose Re-run failed jobs in the original workflow run. Do not rerun all jobs: the npm/Go publisher intentionally rejects existing release tags. The Rust job uses the original release commit even if main has since advanced.

The Rust job builds the archive and checks the registry index before uploading. If an earlier attempt uploaded successfully but lost its acknowledgement, a retry accepts the existing version only when its SHA-256 checksum matches that exact archive and it is not yanked. Conflicting bytes fail the job; published versions are never overwritten. Resolve a content conflict with a new release.

Set up PyPI publishing once ​

Sign in to PyPI with a verified email and two-factor authentication. Under Account settings → Publishing, add a pending trusted publisher for project dialcache, GitHub owner lan17, repository DialCache, workflow filename release.yaml, and environment pypi. Create the matching pypi GitHub environment and restrict its deployment branches to main. Existing projects configure the same publisher under their project's publishing settings.

The pinned official pypa/gh-action-pypi-publish action uses the publishing job's id-token: write permission for short-lived credentials and generates publication attestations. No PyPI API token or bootstrap upload is needed. A pending publisher creates the project on first successful upload, then becomes a normal publisher. It does not reserve the name before that upload. See PyPI's pending publisher guide.

The package checks do not verify PyPI account ownership or the publisher configuration. Until the first successful publication, install from a checkout as described in the Python guide. Afterwards use python -m pip install 'dialcache[redis]' (omit the extra for local-only use).

Retry a Python publication ​

If only publish-python fails, fix the publisher configuration or transient failure and choose Re-run failed jobs in the original workflow run. Do not rerun all jobs: the npm/Go publisher intentionally rejects existing tags. The Python job uses the original release commit and retained distributions, even when main has advanced. Artifact retention is 90 days; recover those exact verified files if retention has expired instead of silently rebuilding.

Before uploading, the job compares PyPI filenames and SHA-256 checksums with both original distributions. Identical existing files are accepted, missing files are uploaded, and conflicting, unexpected or yanked files fail the job. This handles a partial wheel/source upload or an upload whose acknowledgement was lost. It verifies both files through PyPI's API afterwards. Published artifacts are immutable; resolve content conflicts with a new release.

The repository must enable Allow GitHub Actions to create and approve pull requests under Actions workflow permissions.

The workflow uses that capability only to create the version pull request. It never approves or merges one, and no ruleset bypass actor is required. Once crates.io trusted publishing is configured, none of the registries needs a persistent publishing credential in this workflow.