cross-tab-worker-databus 0.21.45 → 0.21.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,45 @@
1
+ ## [0.21.47] - 2026-09-30
2
+
3
+ One new option, one guard that could not pass, and the first browser-benchmark sample in twenty-five tags. No existing behavior changed: the option is additive, and the one case that documents how the replay store behaves today is unchanged.
4
+
5
+ ### Added
6
+
7
+ - **`createIndexedDbReplayPersistence` accepts `clusterKey`, and the IndexedDB store is namespaced by it.** The replay store is the one place in the library where `clusterKey` — documented everywhere else as the boundary for storage keys and the BroadcastChannel namespace — did not apply: its database name defaulted to the package's storage prefix, and the store is keyed by the plaintext Topic, so two clusters in one origin read each other's rows for any Topic they share. Pass the same `clusterKey` the bus gets and the database name is derived exactly as the coordination plane derives its storage keys, so an application running more than one cluster gets one namespace per cluster without naming databases by hand.
8
+ - **Two derivations, both pinned.** The name uses `createOpaqueKey(clusterKey || '__default__')` — the *same* function and the *same* empty-key fallback the coordination plane uses, because `''` and `'__default__'` are one cluster everywhere else in the library; hashing `''` on its own would open a database no bus ever reads, and the symptom would be silently empty replay rather than an error. And an explicit `dbName` still wins over a supplied `clusterKey`, because it is the documented escape hatch for an application that wants its own naming, and silently overriding a name the caller typed would be worse than the inconsistency this option removes.
9
+ - **It is additive, and deliberately not a changed default.** Namespacing by default is the only shape that fixes the hole with no opt-in, and it is the wrong trade: it would move every existing caller's rows into a database no bus opens, surfacing as silently empty replay rather than as an error. Omitting `clusterKey` still shares, so nothing moves for anyone who does not ask for it.
10
+
11
+ ### Fixed
12
+
13
+ - **The browser benchmark's host-load guard was reading the host _after_ the benchmark had consumed the CPU, so it measured itself and could never pass.** Introduced in `0.21.45`, it compares a sample's load against a ceiling. The reading was taken at archive time — after Chrome launched, the demo server spawned and the whole mode matrix ran — and on an 8-core host that self-inflicted cost exceeds the ceiling: a host idle long enough to read **7.23** was at **11.95** by the time a sample completed, and a second run traced 11.6 rising to 13.0 mid-run and decaying to 8.96. So the guard would have refused a genuinely idle machine on every attempt, turning "wait for a quiet window" into "impossible by construction", with no error anywhere to say so. The reading is now taken before any of the run's own work, which is the only reading that answers the question the guard asks: was this machine busy for something *other* than being benchmarked.
14
+
15
+ ### Verification
16
+
17
+ - `pnpm check` — 39 test files, **1019 tests**, typecheck, build, 5 perf gates; `pnpm lint`; the bench and documentation gates.
18
+ - Three mutants on the new option, each applied alone and restored, with the applied-line count printed and asserted before the run: accepting and ignoring `clusterKey`, hashing the empty key on its own, and letting `clusterKey` override an explicit `dbName`. Each dies to a distinct case.
19
+ - Two mutants on the guard's reading placement, pinned as a text gate over the script because the call sits inside `main()` behind a server spawn and a browser launch: moving the reading back to archive time, and deleting it.
20
+ - **The first browser-benchmark sample in 25 tags**, and the first of 92 to record host state: `host: { loadavg1m: 7.88, cpus: 8 }` on `darwin`. `pnpm bench:compare --fail-above-pct 50` exits 0 — `publish/dedicated` +11.4 %, `publish/shared` +11.5 %, `wildcardDispatch1000Ms` +13.8 %, `publishBatch1000Ms` +16.3 %, `dedup1000Ms` +0.5 %, `traceAndPublish1000Ms` −8.1 % — and `docs/benchmarks.md` plus its zh mirror regenerate from 92 reports. Those deltas span 25 releases against a single prior sample rather than any change in this release, which is why the gate requires beating the *highest* of the five baseline samples and not merely the median.
21
+ - `pnpm audit` on the public registry, clean and independently re-derived from the installed version against the published advisory ranges rather than from the tool's exit code.
22
+
23
+ ## [0.21.46] - 2026-09-30
24
+
25
+ `0.21.45` shipped a guard whose own release note explained why it did not yet work in the case that mattered. This is that case, fixed, plus the dependency the release's audit should have caught and did not. No library behavior, export, option, default, frame or storage key changed; the coverage floors and the zero-count arm ledger are `0.21.45`'s.
26
+
27
+ ### Fixed
28
+
29
+ - **The host-load guard refused a loaded sample *after* writing it, and declined to judge the only case the archive actually presents.** Both halves had to close, because either alone leaves the gap open. `bench-compare` exits non-zero on a loaded sample, but the report is already on disk — and a report on disk **is** the baseline the next five comparisons read, so a verdict printed about a file that already exists decides nothing. The decision and the write are now one call (`archiveReport`), so a refusal is a file that does not exist. And the no-baseline leg returned "nothing to compare against", which on this archive — no report records a reading at all — meant the guard was weakest exactly where the archive is weakest, admitting the first sample taken on a busy host and making it the reference for everything after. That leg now falls back to an absolute, machine-relative ceiling: a one-minute load average above the host's **core count** is oversubscribed by definition and needs no history to establish.
30
+ - **A missing archive directory is an empty archive, not a crash.** The reader that now gates the write listed its directory unguarded, so the first `pnpm bench:browser` on a clean checkout — the run that is meant to *create* `bench-results/` — threw. It also broke `pnpm bench:compare` on a fresh clone, whose "need two reports" guard runs *after* the listing. Found by a test asserting the directory gets created, which failed for this reason rather than for the reason it was written.
31
+ - **`brace-expansion` 5.0.9 → 5.0.12**, carrying three advisories (two high: stack exhaustion via nested brace groups and via `parseCommaParts`; one moderate: quadratic-time `{a},b}` expansion) reached only through `eslint > minimatch`, so no consumer of this package was ever exposed. Pinned by an override in `pnpm-workspace.yaml`, inside `minimatch`'s own declared `^5.0.8` range rather than forcing a major.
32
+
33
+ ### Corrected
34
+
35
+ - **`0.21.45`'s claim that the dependency audit found no known vulnerabilities was wrong**, and the `0.21.45` section now says so. The two high advisories were published at `2026-09-29T23:45Z` and the audit that reported a clean tree ran about three hours later against `registry.npmjs.org`, so neither publication timing nor a mirror explains it — the advisories existed and were findable. What is established is the version: the lockfile held `5.0.9`, inside all three vulnerable ranges. `pnpm audit` is the one gate in the release checklist whose signal is entirely a third party's document and the only one with no corroborating local reading, so a green local audit means *"the document I was served did not list my versions"* — a statement about a cache rather than about the tree. The clean result above is re-derived from the installed version against the published ranges rather than from the tool's exit code.
36
+
37
+ ### Verification
38
+
39
+ - `pnpm check` — 39 test files, **1015 tests**, typecheck, build, 5 perf gates; `pnpm lint`; `pnpm test:coverage` at 99.03 / 97.60 / 99.27 / 99.69; `pnpm bench`; `verify:compat`, `verify:types`, `verify:pack` all against `v0.21.45`; `pnpm audit` on the public registry, clean and independently re-derived; `git diff --check` clean.
40
+ - Four mutants across the guard, each applied alone and restored, with the applied-line count printed and asserted **before** each run — keyed on changed lines rather than added ones, because every one of these is a substitution that adds nothing and a `>`-only check reports them all as unapplied.
41
+ - **The deferral was produced by the tool for the first time in 25 tags.** `pnpm bench:browser` ran to completion, printed its report, and then refused to archive it and exited non-zero: *"host load 24.67 exceeds the host's 8 core(s) and no archived report records a reading to compare against"*. The archive held 91 reports before and after. The standing rule in both release checklists — defer the sample and record its load numbers — is now a gate that runs rather than a judgment someone makes, and this release is the first to demonstrate it on the real script rather than in a test.
42
+
1
43
  ## [0.21.45] - 2026-09-30
2
44
 
3
45
  A release ten cycles in the making, and the reason it was late is now part of the tooling: the browser benchmark's deferral rule was enforced by a human remembering it, so it slipped. No library behavior, export, option, default, frame or storage key changed; the coverage floors and the zero-count arm ledger are `0.21.44`'s.
@@ -20,7 +62,7 @@ A release ten cycles in the making, and the reason it was late is now part of th
20
62
 
21
63
  - `pnpm check` — 39 test files, **999 tests**, typecheck, build, 5 perf gates; `pnpm lint`; the documentation gate at 25.
22
64
  - Twenty-four mutants across the changes above, each applied alone and restored, each with its own named failure recorded in `docs/progress.md`.
23
- - `pnpm audit --registry=https://registry.npmjs.org` — no known vulnerabilities.
65
+ - `pnpm audit --registry=https://registry.npmjs.org` — reported no known vulnerabilities, and that report was **wrong**: the tree carried `brace-expansion@5.0.9` with three advisories (two high, one moderate) through `eslint > minimatch`. The advisories were published about three hours *before* the audit ran, so this is not publication timing, and the registry named was the right one. Fixed in `0.21.46` by an override, with the finding recorded there rather than softened here.
24
66
  - `docs/benchmarks.md` and its zh mirror regenerate byte-identical from the 91-report archive, so the generated tables and their committed copies have not drifted; the archive itself is untracked, which is why that is a run rather than a gate.
25
67
  - **The browser benchmark was deferred again, with its load numbers, as the checklist's standing decision requires** — 79.78 / 120.85 / 129.80 / 145.31 across six readings on 8 cores at the end of this cycle. The archive's newest report is still `browser-2026-09-24T13-40-52-413Z.json`, and 24 tags have now shipped without a sample. What this release changes is that a loaded host can no longer *silently* produce the sample that ends that streak: the next run either produces a clean sample or is refused with its numbers.
26
68
 
@@ -7,7 +7,7 @@ import {
7
7
  publicationMetadata,
8
8
  selectWorkerBackend,
9
9
  serializeError
10
- } from "./chunk-36TEEZQ5.js";
10
+ } from "./chunk-ZBA6SUFN.js";
11
11
  import {
12
12
  CENTRIFUGE_INPUT_TYPE,
13
13
  CENTRIFUGE_OUTPUT_TYPE,
@@ -2540,7 +2540,7 @@ var SDK_VERSION = (
2540
2540
  // declaration's type is `string | undefined` and the export's is `string`
2541
2541
  // (dropping the conditional is TS2322, measured). Recorded here so the leg reads
2542
2542
  // as a shape the harness cannot reach rather than as a case nobody exercised.
2543
- true ? "0.21.45" : ""
2543
+ true ? "0.21.47" : ""
2544
2544
  );
2545
2545
 
2546
2546
  // src/core/data-bus.ts
@@ -3793,4 +3793,4 @@ export {
3793
3793
  parseDataBusPublication,
3794
3794
  selectWorkerBackend
3795
3795
  };
3796
- //# sourceMappingURL=chunk-36TEEZQ5.js.map
3796
+ //# sourceMappingURL=chunk-ZBA6SUFN.js.map
@@ -2669,7 +2669,7 @@ var SDK_VERSION = (
2669
2669
  // declaration's type is `string | undefined` and the export's is `string`
2670
2670
  // (dropping the conditional is TS2322, measured). Recorded here so the leg reads
2671
2671
  // as a shape the harness cannot reach rather than as a case nobody exercised.
2672
- true ? "0.21.45" : ""
2672
+ true ? "0.21.47" : ""
2673
2673
  );
2674
2674
 
2675
2675
  // src/core/data-bus.ts
@@ -2635,7 +2635,7 @@ var SDK_VERSION = (
2635
2635
  // declaration's type is `string | undefined` and the export's is `string`
2636
2636
  // (dropping the conditional is TS2322, measured). Recorded here so the leg reads
2637
2637
  // as a shape the harness cannot reach rather than as a case nobody exercised.
2638
- true ? "0.21.45" : ""
2638
+ true ? "0.21.47" : ""
2639
2639
  );
2640
2640
 
2641
2641
  // src/core/data-bus.ts
@@ -3829,10 +3829,15 @@ function holdLastFinite(source) {
3829
3829
  }
3830
3830
 
3831
3831
  // src/core/replay-persistence.ts
3832
+ function resolveDbName(options) {
3833
+ if (options.dbName !== void 0) return options.dbName;
3834
+ if (options.clusterKey === void 0) return DEFAULT_STORAGE_PREFIX;
3835
+ return `${DEFAULT_STORAGE_PREFIX}:${createOpaqueKey(options.clusterKey || "__default__")}`;
3836
+ }
3832
3837
  function createIndexedDbReplayPersistence(options) {
3833
3838
  const indexedDb = globalThis.indexedDB;
3834
3839
  if (!indexedDb) throw new Error("IndexedDB is unavailable in this environment.");
3835
- const dbName = options.dbName ?? DEFAULT_STORAGE_PREFIX;
3840
+ const dbName = resolveDbName(options);
3836
3841
  const storeName = "replay";
3837
3842
  const maxPerTopic = options.maxPerTopic;
3838
3843
  const pruneStrategy = options.pruneStrategy ?? PRUNE_STRATEGY.COUNT;