- A compromised lock (failed heartbeat; another process may take it over)
aborts the update holding it, download included, before it publishes,
instead of only being logged.
- A failed publish removes files it had just added when the dataset had none
before, as well as restoring replaced ones from backup: never half a set.
- The watcher reloads hand-placed or externally published files only once
they have held still for the settle interval and passed the row's
validator, so a file still being copied never replaces healthy data.
- Back to write-file-atomic 8, with engines aligned to its range
(^24.15.0 || >=26.0.0).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Scheduled and catch-up runs give each dataset 30 minutes, download
included, so a stalled upstream can't hold up the rest or, under croner's
overrun protection, every later run.
- Downloads go through fetchUpstream (project User-Agent, a timeout on the
wait for a response); the body stays bounded by the caller's signal.
- Catch-up asks whether a scheduled run fell between the last check and now,
instead of a 25-hour age that missed a check skipped just after one.
- A remote that names no version (no Last-Modified) is always fetched, not
taken for unchanged; maxmindIdentifier is null when an edition omits it.
- The watcher records a dataset's files as loaded only after the reload
succeeds, so a failed load is retried on their next change.
- write-file-atomic 7, which supports the project's Node >=24 (8 needs 24.15).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxMind and CAIDA each had their own updater with its own lock, state file,
publish step, boot download and timer. One engine (common/dataset-updater.js)
now owns all of that, and each dataset is a row in common/datasets.js that
holds only its own logic: find the newest remote version, fetch or distill
it, validate, reload.
- Locks: proper-lockfile, a heartbeat lock shared by every process, so a
crashed holder's lock goes stale on its own; a booting server waits for a
live holder and loads what it published.
- State: one file per dataset, written with write-file-atomic; the old
.maxmind-update-state.json / .caida-update-state.json are read once, so an
upgrade doesn't re-download.
- Schedule: croner at a fixed time (DATASET_UPDATE_CRON, default 04:30 local)
instead of 24 h from process start, plus a catch-up run at boot for a check
a downtime skipped. DATASET_AUTO_UPDATE (default on) replaces
MAXMIND_AUTO_UPDATE / CAIDA_AUTO_UPDATE, which still apply when it is unset.
- Reload: the engine watches every dataset's files, so a publish by another
process or a hand-placed .mmdb is picked up; MaxMind's own watcher goes.
MaxMind's City and ASN editions now publish together under one version.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Needed to join the DNS resolver's record-type Select and its input into
one bordered control. Added through the shadcn-vue CLI, so the files stay
verbatim upstream output and can be re-synced.
The same run resynced ui/separator, which still carried a hand-written
early-refactor version, to current upstream — data-slot and
data-[orientation] variants instead of a JS ternary, behaviourally the
same for all three existing callers. It also refreshed one transitive
postcss patch in the lockfile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every geo route now returns an IANA zone name, added on the way out by the
withTimeZone() middleware rather than by any handler — including the private
API pass-through, which needs no new upstream field.
The zone is derived from the latitude/longitude the response itself carries,
not from a second lookup of the same IP. Asking another database would
eventually place the address elsewhere and print a zone that contradicts the
city next to it. Coordinate accuracy measured against MaxMind's own time_zone
over 30k sampled addresses: 99.80% exact, 99.98% correct offset; the rest are
neighbouring zones across a land border that share an offset anyway.
Only the name ships. Geo routes sit behind a 24h edge cache, so a cached UTC
offset would be an hour wrong for everyone between a DST switch and the
entry's expiry — the frontend renders the offset per view instead, reusing
the formatter the analytics path already had.
The field travels the full report path: event payload, builder, schema (as a
typed timeZone kind, not a free string), the /r/:id table, the Markdown
export and the JSON download.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Frontend (@sentry/vue, loaded via build-time-gated dynamic import — no
VITE_SENTRY_DSN_FRONTEND, no Sentry in the bundle; init joins the mount
gate so instrumentation precedes the first render):
- uncaught errors, route-change tracing / Web Vitals, error-only masked
Session Replay, console.error capture fingerprinted per message prefix
so each fallback source stays a distinct issue
- explicit ip-source:exhausted signal via the app-events bus
- hidden source maps uploaded (then deleted) by @sentry/vite-plugin,
gated on SENTRY_AUTH_TOKEN
Backend (@sentry/node, bootstrapped by sentry-instrument.js via
node --import so ESM loader hooks land before express; never loaded
without SENTRY_DSN_BACKEND):
- Express error capture + per-route tracing
- logger hook mirrors warn+ lines to Sentry Logs and elevates error+
to grouped, alertable Issues
- Sentry Crons check-ins for the MaxMind / CAIDA / service-status jobs
via the no-op-without-DSN wrapper common/sentry-cron.js
Delivery & ops:
- first-party tunnel /api/monitoring relays envelopes past ad blockers;
DSN-allowlisted (never an open relay), exempt from the shared rate
limiter with its own generous ceiling
- rate-limit ledger file becomes opt-in now that the warn line reaches
Sentry Logs; ecosystem.config.cjs carries the --import flag for pm2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Turn the nav's "Advanced Tools" item into a discoverable menu of its
sub-tools instead of a bare scroll link:
- Desktop: a NavigationMenu (shadcn-vue) opens on hover; clicking the
label keeps the original behavior of scrolling to the section.
- Mobile drawer: an inline Collapsible, expanded by default, so phone
users see the bottom-of-page tools when they open the nav.
- Picking a tool scrolls to the Advanced Tools section, then opens that
tool's in-page drawer (no jump to a standalone page). The list mirrors
Advanced.vue's original-site gating so unavailable tools aren't shown.
Also swap the Preferences icon to Cog (nav button + panel header), and
add the navigation-menu primitive via shadcn-vue.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>