cross-tab-worker-databus 0.20.97 → 0.21.0

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,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.21.0] - 2026-09-22
4
+
5
+ ### Breaking
6
+ - `subscribe("")`, `publish("")` and `publishBatch("")` now throw a `TypeError` instead of being accepted. This is the removal step of the cycle opened in `0.20.96`, which warned once per instance and changed nothing else; it follows the pre-1.0 policy, and no other behavior of those three calls moved. An empty topic routes as a literal channel that no transport can address, so the subscription it created could never receive anything and a publication to it was dropped without a trace — the call now fails where the mistake is made, the same way an invalid `replay.maxPerTopic` does. The guard runs as the first statement of each method, so a rejected call also starts no transport, registers no handler and writes no route record. `publishBatch("", [])` throws too: the argument is checked ahead of the documented empty-array no-op. **Migration:** pass a real channel name. Where the topic comes from user input or a config field, validate or fall back before calling — `input.trim() || 'demo.flow'`, which is what the bundled example pages do.
7
+
8
+ ### Changed
9
+ - `examples/react` and `examples/vue` now resolve their topic as `topicInput.trim() || <default>`, matching what `examples/demo` already did, because their boxes feed a reactive subscription and would otherwise hand `""` straight to the bus. The failure mode the fallback prevents is measured rather than assumed: with it removed, the Vue page keeps rendering its *previous* topic while a `TypeError` escapes to `pageerror`, and the demo page lands on 错误 with an error-feed row that never mentions the field the user just emptied. Both pages are now driven in real browser tabs (`e2e/adapters.spec.ts`, `e2e/demo.spec.ts`), each arm of the guard killing a different mutant.
10
+ - `docs/getting-started.md` (en + zh) claimed the browser suite covers both adapter pages. It covers the Vue page only — the React page loads React from `esm.sh`, so it cannot run where CI has no network, and it hand-wires the demo page's pattern rather than using the shipped `cross-tab-worker-databus/react` adapter (covered in jsdom by `tests/hooks.test.tsx`). The paragraph now says exactly that.
11
+
3
12
  ## [0.20.97] - 2026-09-22
4
13
 
5
14
  ### Added
@@ -5,7 +5,7 @@ import {
5
5
  parseDataBusPublication,
6
6
  publicationMetadata,
7
7
  selectWorkerBackend
8
- } from "./chunk-IVTDQ2KD.js";
8
+ } from "./chunk-EJDXU4DK.js";
9
9
  import {
10
10
  CENTRIFUGE_INPUT_TYPE,
11
11
  CENTRIFUGE_OUTPUT_TYPE,
@@ -403,6 +403,12 @@ function assertHeartbeatInterval(value) {
403
403
  `Centrifuge heartbeatIntervalMs must be a positive number or Infinity, got ${String(value)}.`
404
404
  );
405
405
  }
406
+ function assertPublicTopic(operation, topic) {
407
+ if (topic !== "") return;
408
+ throw new TypeError(
409
+ `CrossTabDataBus.${operation}("") addresses a channel no transport can route; use a non-empty topic.`
410
+ );
411
+ }
406
412
  function assertStructuredCloneable(value) {
407
413
  if (typeof structuredClone !== "function") return;
408
414
  try {
@@ -2340,7 +2346,7 @@ var DedupManager = class {
2340
2346
  };
2341
2347
 
2342
2348
  // src/core/version.ts
2343
- var SDK_VERSION = true ? "0.20.97" : "";
2349
+ var SDK_VERSION = true ? "0.21.0" : "";
2344
2350
 
2345
2351
  // src/core/data-bus.ts
2346
2352
  var DEFAULT_REPLAY_MAX_PER_TOPIC = 100;
@@ -2431,10 +2437,6 @@ var CrossTabDataBus = class {
2431
2437
  // themselves demand: the failure path starts an on-demand reopen instead of
2432
2438
  // stranding them until some unrelated future operation arrives.
2433
2439
  recoveryWaiters = 0;
2434
- // Latch for the empty-topic deprecation warning so a hot publish path cannot
2435
- // fill the console. It is per-bus and never reset: the warning is about the
2436
- // caller's code, not about a transient runtime condition.
2437
- emptyTopicWarned = false;
2438
2440
  /** Monotonic generation incremented on every successful transport open.
2439
2441
  * Stays in lockstep with `lastSuccessAt` so callers can detect that the
2440
2442
  * transport has been reopened even if the timestamp window is short. */
@@ -2829,8 +2831,11 @@ var CrossTabDataBus = class {
2829
2831
  * unsubscribe function for convenience. During an explicit stop() the
2830
2832
  * registration is rejected through onError and a no-op cleanup is returned,
2831
2833
  * so a late subscriber cannot leak into a future restart.
2834
+ * @throws {TypeError} when `topic` is `''` — no transport can address such a
2835
+ * channel, so the subscription could never receive anything.
2832
2836
  */
2833
2837
  subscribe(topic, handler, options) {
2838
+ assertPublicTopic("subscribe", topic);
2834
2839
  if (this.stopping) {
2835
2840
  this.reportError(new Error(
2836
2841
  "CrossTabDataBus is stopping; subscribe() was not registered. Wait for stop() to resolve, then call start() before subscribing again."
@@ -2839,7 +2844,6 @@ var CrossTabDataBus = class {
2839
2844
  };
2840
2845
  }
2841
2846
  this.ensureStarted();
2842
- if (topic === "") this.warnEmptyTopic("subscribe");
2843
2847
  const handlers = this.topicHandlers.get(topic) ?? /* @__PURE__ */ new Set();
2844
2848
  const wasUnused = handlers.size === 0;
2845
2849
  handlers.add(handler);
@@ -2889,20 +2893,10 @@ var CrossTabDataBus = class {
2889
2893
  resetDedup() {
2890
2894
  this.dedupManager.reset();
2891
2895
  }
2892
- /** Warn (once per bus) that an empty topic is deprecated, without changing
2893
- * behavior yet. `''` flows through routing as a literal channel, so the
2894
- * subscription it creates can never be addressed by a transport: the message
2895
- * silently goes nowhere. A future minor rejects it at this boundary. */
2896
- warnEmptyTopic(operation) {
2897
- if (this.emptyTopicWarned) return;
2898
- this.emptyTopicWarned = true;
2899
- console.warn(
2900
- `cross-tab-worker-databus: ${operation}("") addresses a channel no transport can route. Use a non-empty topic; passing "" is planned to throw in a future minor.`
2901
- );
2902
- }
2903
- /** Publish a message to `topic`. The owning Worker delivers it to the transport. */
2896
+ /** Publish a message to `topic`. The owning Worker delivers it to the transport.
2897
+ * @throws {TypeError} when `topic` is `''`; see `subscribe()`. */
2904
2898
  publish(topic, data, options) {
2905
- if (topic === "") this.warnEmptyTopic("publish");
2899
+ assertPublicTopic("publish", topic);
2906
2900
  this.ensureStarted();
2907
2901
  if (this.rejectPublishDuringStop("publish")) return;
2908
2902
  if (!this.cluster.publish(topic, data, options)) {
@@ -2917,11 +2911,14 @@ var CrossTabDataBus = class {
2917
2911
  * in one tick. Per-item dedup / replay / ordering is preserved; each item
2918
2912
  * may carry its own `messageId` / `timestamp` via `options`. Empty array is
2919
2913
  * a no-op; single-item array delegates to `publish()`.
2914
+ * @throws {TypeError} when `topic` is `''`; see `subscribe()`. Checked before
2915
+ * the empty-array no-op, so `publishBatch('', [])` throws too — the topic is
2916
+ * the caller's bug regardless of whether anything is carried.
2920
2917
  */
2921
2918
  publishBatch(topic, items) {
2919
+ assertPublicTopic("publishBatch", topic);
2922
2920
  this.ensureStarted();
2923
2921
  if (items.length === 0) return;
2924
- if (topic === "") this.warnEmptyTopic("publishBatch");
2925
2922
  if (this.rejectPublishDuringStop("publishBatch")) return;
2926
2923
  if (items.length === 1) {
2927
2924
  const first = items[0];
@@ -3550,4 +3547,4 @@ export {
3550
3547
  parseDataBusPublication,
3551
3548
  selectWorkerBackend
3552
3549
  };
3553
- //# sourceMappingURL=chunk-IVTDQ2KD.js.map
3550
+ //# sourceMappingURL=chunk-EJDXU4DK.js.map