@marianmeres/version-monitor
v1.1.0
Published
[](https://www.npmjs.com/package/@marianmeres/version-monitor) [](https://jsr.io/@marianmeres/version-monitor) [
Readme
@marianmeres/version-monitor
Detect, in a running client, that the deployed app has changed underneath it — and react (prompt + reload, or reload silently). Framework-agnostic and browser-safe.
It covers two complementary vectors:
- Proactive —
createVersionMonitorpolls a version manifest (/version.jsonby default) and compares it to the build's baked version. - Reactive —
guardChunkErrors/lazyImportcatch the stale-chunk error from a lazyimport()whose hashed chunk a deploy has removed, and reload once.
The version source of truth is the version field of package.json /
deno.json (deliberately not a git SHA — Docker images don't ship .git).
Install
deno add jsr:@marianmeres/version-monitornpm install @marianmeres/version-monitorUsage
Poll + prompt
import { createVersionMonitor, fromJson } from "@marianmeres/version-monitor";
const vm = createVersionMonitor({
current: window.APP_CONFIG?.APP_VERSION, // your baked build version
url: "/version.json",
extract: fromJson("version"),
interval: 60_000,
onUpdateAvailable: ({ latest }) => {
if (confirm(`New version ${latest} available. Reload now?`)) {
location.reload();
}
},
}).start();Reactive status (Svelte-store compatible)
<script>
import { createVersionMonitor } from "@marianmeres/version-monitor";
import { onMount } from "svelte";
const vm = createVersionMonitor({ url: "/version.json" });
onMount(() => {
vm.setCurrent(window.APP_CONFIG.APP_VERSION);
vm.start();
return () => vm.destroy();
});
</script>
{#if $vm.updateAvailable}
<button onclick={() => location.reload()}>Update to {$vm.latest}</button>
{/if}Stale-chunk guard
Install this alongside the monitor — it's the safety net for a user who dismisses the update prompt and keeps the tab open (the monitor prompts once, then stays out of the way until the next navigation).
import { guardChunkErrors, lazyImport } from "@marianmeres/version-monitor";
guardChunkErrors(); // global net: reload-once on a stale-chunk error
const Heavy = await lazyImport(() => import("./Heavy.js")); // targeted: retry + reload-onceAuthenticated endpoint
createVersionMonitor({
current: window.APP_CONFIG?.APP_VERSION,
url: "/health",
extract: fromHeader("x-version"),
credentials: "include",
headers: () => ({ authorization: `Bearer ${getToken()}` }), // getter ⇒ fresh per check
});Reuse an existing poll (no second request)
The monitor compares two inputs — current (your build) and latest (what's
deployed) — each of which you can set manually or let the built-in poll fill. So
if you already run a periodic signal whose endpoint can carry the version (e.g. an
x-version header on the /ping of
@marianmeres/connection-monitor),
feed it with setLatest() instead of polling a second time — and don't call
start():
import { createConnectionMonitor } from "@marianmeres/connection-monitor";
import { createVersionMonitor } from "@marianmeres/version-monitor";
const vm = createVersionMonitor({
current: window.APP_CONFIG?.APP_VERSION,
onUpdateAvailable: ({ latest }) =>
confirm(`Update to ${latest}?`) && location.reload(),
}); // no .start() — fed externally
createConnectionMonitor({
url: "/ping",
fetcher: async (input, init) => {
const res = await fetch(input, init);
const v = res.headers.get("x-version");
if (v) vm.setLatest(v); // reuse the probe as the version signal
return res; // connection-monitor only times it
},
}).start();Read the header synchronously — don't await res.text(), it would consume the
body and skew connection-monitor's latency measurement. The latch dedups repeated
probes, so you're notified once. The same pattern works for any push source (SSE,
WebSocket, a <meta> refresh).
The version contract
This package consumes a manifest; it does not produce one. You own both ends:
- Serve a manifest (
/version.json, anx-versionheader, orversion.txt) from your release flow, built from the singleversionfield. - Supply
currentyourself (recommended:window.APP_CONFIG.APP_VERSION; a bundlerdefine/import.meta.env/<meta>tag work equally well). The library never reads it on its own. - Bump the version on every deploy you want announced. The proactive poll
fires only when the
versionfield changes; a redeploy at the same version is invisible to vector 1 (vector 2 still catches it reactively).
API
See API.md for complete API documentation.
