@graphrefly/graphrefly 0.22.0 → 0.24.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.
Files changed (78) hide show
  1. package/dist/{chunk-QA3RP5NH.js → chunk-5DJTTKX3.js} +111 -17
  2. package/dist/chunk-5DJTTKX3.js.map +1 -0
  3. package/dist/{chunk-BLD3IFYF.js → chunk-5WGT55R4.js} +9 -7
  4. package/dist/{chunk-BLD3IFYF.js.map → chunk-5WGT55R4.js.map} +1 -1
  5. package/dist/{chunk-IR3KMOLX.js → chunk-AOCBDH4T.js} +3 -383
  6. package/dist/chunk-AOCBDH4T.js.map +1 -0
  7. package/dist/{chunk-TH6COGOP.js → chunk-H4RVA4VE.js} +2 -2
  8. package/dist/chunk-HWPIFSW2.js +36 -0
  9. package/dist/chunk-HWPIFSW2.js.map +1 -0
  10. package/dist/{chunk-MQBQOFDS.js → chunk-IPLKX3L2.js} +12 -31
  11. package/dist/chunk-IPLKX3L2.js.map +1 -0
  12. package/dist/{chunk-44HD4BTA.js → chunk-MW4VAKAO.js} +3 -3
  13. package/dist/chunk-PY4XCDLR.js +391 -0
  14. package/dist/chunk-PY4XCDLR.js.map +1 -0
  15. package/dist/{chunk-RHI3GHZW.js → chunk-QOWVNWOC.js} +3 -3
  16. package/dist/{chunk-EQUZ5NLD.js → chunk-TDEXAMGO.js} +11 -16
  17. package/dist/chunk-TDEXAMGO.js.map +1 -0
  18. package/dist/{chunk-NXC35KC5.js → chunk-XOFWRC73.js} +3 -3
  19. package/dist/compat/nestjs/index.cjs +110 -16
  20. package/dist/compat/nestjs/index.cjs.map +1 -1
  21. package/dist/compat/nestjs/index.d.cts +6 -6
  22. package/dist/compat/nestjs/index.d.ts +6 -6
  23. package/dist/compat/nestjs/index.js +9 -7
  24. package/dist/core/index.cjs +110 -16
  25. package/dist/core/index.cjs.map +1 -1
  26. package/dist/core/index.d.cts +3 -3
  27. package/dist/core/index.d.ts +3 -3
  28. package/dist/core/index.js +3 -3
  29. package/dist/extra/index.cjs +110 -16
  30. package/dist/extra/index.cjs.map +1 -1
  31. package/dist/extra/index.d.cts +4 -4
  32. package/dist/extra/index.d.ts +4 -4
  33. package/dist/extra/index.js +9 -7
  34. package/dist/graph/index.cjs +110 -16
  35. package/dist/graph/index.cjs.map +1 -1
  36. package/dist/graph/index.d.cts +5 -5
  37. package/dist/graph/index.d.ts +5 -5
  38. package/dist/graph/index.js +4 -4
  39. package/dist/{graph-DFr0diXB.d.ts → graph-B6NFqv3z.d.ts} +3 -3
  40. package/dist/{graph-ab1yPwIB.d.cts → graph-D-3JIQme.d.cts} +3 -3
  41. package/dist/{index-BvWfZCTt.d.cts → index-1z8vRTCt.d.cts} +3 -3
  42. package/dist/{index-Dy04P4W3.d.cts → index-AMWewNDe.d.cts} +2 -2
  43. package/dist/{index-C9z6rU9P.d.cts → index-BJB7t9gg.d.cts} +19 -15
  44. package/dist/{index-BbYZma8G.d.ts → index-BysCTzJz.d.ts} +3 -3
  45. package/dist/{index-HdJx_BjO.d.ts → index-C-TXEa7C.d.ts} +19 -15
  46. package/dist/{index-DsGxLfwL.d.ts → index-CYkjxu3s.d.ts} +2 -2
  47. package/dist/{index-BHm3Ba5q.d.ts → index-D7XgsUt7.d.ts} +2 -2
  48. package/dist/{index-D36MAQ3f.d.ts → index-DiobMNwE.d.ts} +3 -3
  49. package/dist/{index-DrJq9B1T.d.cts → index-J7Kc0oIQ.d.cts} +3 -3
  50. package/dist/{index-DLE1Sp-L.d.cts → index-b5BYtczN.d.cts} +2 -2
  51. package/dist/index.cjs +129 -46
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.cts +15 -15
  54. package/dist/index.d.ts +15 -15
  55. package/dist/index.js +36 -42
  56. package/dist/index.js.map +1 -1
  57. package/dist/{meta-n3FoVWML.d.ts → meta-CnkLA_43.d.ts} +1 -1
  58. package/dist/{meta--fr9sxRM.d.cts → meta-DWbkoq1s.d.cts} +1 -1
  59. package/dist/{node-C5UD5MGq.d.cts → node-B-f-Lu-k.d.cts} +57 -13
  60. package/dist/{node-C5UD5MGq.d.ts → node-B-f-Lu-k.d.ts} +57 -13
  61. package/dist/{observable-DWydVy5b.d.cts → observable-DBnrwcar.d.cts} +1 -1
  62. package/dist/{observable-CQRBtEbq.d.ts → observable-uP-wy_uK.d.ts} +1 -1
  63. package/dist/patterns/reactive-layout/index.cjs +224 -129
  64. package/dist/patterns/reactive-layout/index.cjs.map +1 -1
  65. package/dist/patterns/reactive-layout/index.d.cts +5 -5
  66. package/dist/patterns/reactive-layout/index.d.ts +5 -5
  67. package/dist/patterns/reactive-layout/index.js +6 -4
  68. package/dist/{storage-Bew05Xy6.d.cts → storage-BuTdpCI1.d.cts} +1 -1
  69. package/dist/{storage-C9fZfMfM.d.ts → storage-F2X1U1x0.d.ts} +1 -1
  70. package/package.json +3 -2
  71. package/dist/chunk-EQUZ5NLD.js.map +0 -1
  72. package/dist/chunk-IR3KMOLX.js.map +0 -1
  73. package/dist/chunk-MQBQOFDS.js.map +0 -1
  74. package/dist/chunk-QA3RP5NH.js.map +0 -1
  75. /package/dist/{chunk-TH6COGOP.js.map → chunk-H4RVA4VE.js.map} +0 -0
  76. /package/dist/{chunk-44HD4BTA.js.map → chunk-MW4VAKAO.js.map} +0 -0
  77. /package/dist/{chunk-RHI3GHZW.js.map → chunk-QOWVNWOC.js.map} +0 -0
  78. /package/dist/{chunk-NXC35KC5.js.map → chunk-XOFWRC73.js.map} +0 -0
@@ -1,9 +1,9 @@
1
- export { T as ToObservableOptions, t as toObservable } from '../../observable-DWydVy5b.cjs';
2
- export { A as ACTOR_KEY, a as ActorExtractor, C as COMMAND_HANDLERS, b as CQRS_EVENT_HANDLERS, d as CRON_HANDLERS, e as CommandHandler, f as CommandHandlerMeta, E as EVENT_HANDLERS, g as EventHandler, h as EventHandlerMeta, G as GRAPHREFLY_REQUEST_GRAPH, j as GRAPHREFLY_ROOT_GRAPH, k as GraphCron, l as GraphCronMeta, m as GraphInterval, n as GraphIntervalMeta, o as GraphReflyCqrsOptions, p as GraphReflyEventExplorer, q as GraphReflyFeatureOptions, r as GraphReflyGuard, s as GraphReflyGuardImpl, t as GraphReflyModule, u as GraphReflyRootOptions, I as INTERVAL_HANDLERS, v as InjectCqrsGraph, w as InjectGraph, x as InjectNode, O as ObserveGateway, y as ObserveGatewayOptions, z as ObserveSSEOptions, B as ObserveSubscriptionOptions, D as ObserveWsCommand, F as ObserveWsMessage, H as OnGraphEvent, J as OnGraphEventMeta, Q as QUERY_HANDLERS, K as QueryHandler, L as QueryHandlerMeta, S as SAGA_HANDLERS, M as SagaHandler, N as SagaHandlerMeta, P as fromHeader, R as fromJwtPayload, T as getActor, U as getGraphToken, V as getNodeToken, W as observeSSE, X as observeSubscription } from '../../index-DrJq9B1T.cjs';
1
+ export { T as ToObservableOptions, t as toObservable } from '../../observable-DBnrwcar.cjs';
2
+ export { A as ACTOR_KEY, a as ActorExtractor, C as COMMAND_HANDLERS, b as CQRS_EVENT_HANDLERS, d as CRON_HANDLERS, e as CommandHandler, f as CommandHandlerMeta, E as EVENT_HANDLERS, g as EventHandler, h as EventHandlerMeta, G as GRAPHREFLY_REQUEST_GRAPH, j as GRAPHREFLY_ROOT_GRAPH, k as GraphCron, l as GraphCronMeta, m as GraphInterval, n as GraphIntervalMeta, o as GraphReflyCqrsOptions, p as GraphReflyEventExplorer, q as GraphReflyFeatureOptions, r as GraphReflyGuard, s as GraphReflyGuardImpl, t as GraphReflyModule, u as GraphReflyRootOptions, I as INTERVAL_HANDLERS, v as InjectCqrsGraph, w as InjectGraph, x as InjectNode, O as ObserveGateway, y as ObserveGatewayOptions, z as ObserveSSEOptions, B as ObserveSubscriptionOptions, D as ObserveWsCommand, F as ObserveWsMessage, H as OnGraphEvent, J as OnGraphEventMeta, Q as QUERY_HANDLERS, K as QueryHandler, L as QueryHandlerMeta, S as SAGA_HANDLERS, M as SagaHandler, N as SagaHandlerMeta, P as fromHeader, R as fromJwtPayload, T as getActor, U as getGraphToken, V as getNodeToken, W as observeSSE, X as observeSubscription } from '../../index-J7Kc0oIQ.cjs';
3
3
  import 'rxjs';
4
- import '../../node-C5UD5MGq.cjs';
4
+ import '../../node-B-f-Lu-k.cjs';
5
5
  import '@nestjs/common';
6
6
  import '@nestjs/core';
7
- import '../../graph-ab1yPwIB.cjs';
8
- import '../../meta--fr9sxRM.cjs';
9
- import '../../storage-Bew05Xy6.cjs';
7
+ import '../../graph-D-3JIQme.cjs';
8
+ import '../../meta-DWbkoq1s.cjs';
9
+ import '../../storage-BuTdpCI1.cjs';
@@ -1,9 +1,9 @@
1
- export { T as ToObservableOptions, t as toObservable } from '../../observable-CQRBtEbq.js';
2
- export { A as ACTOR_KEY, a as ActorExtractor, C as COMMAND_HANDLERS, b as CQRS_EVENT_HANDLERS, d as CRON_HANDLERS, e as CommandHandler, f as CommandHandlerMeta, E as EVENT_HANDLERS, g as EventHandler, h as EventHandlerMeta, G as GRAPHREFLY_REQUEST_GRAPH, j as GRAPHREFLY_ROOT_GRAPH, k as GraphCron, l as GraphCronMeta, m as GraphInterval, n as GraphIntervalMeta, o as GraphReflyCqrsOptions, p as GraphReflyEventExplorer, q as GraphReflyFeatureOptions, r as GraphReflyGuard, s as GraphReflyGuardImpl, t as GraphReflyModule, u as GraphReflyRootOptions, I as INTERVAL_HANDLERS, v as InjectCqrsGraph, w as InjectGraph, x as InjectNode, O as ObserveGateway, y as ObserveGatewayOptions, z as ObserveSSEOptions, B as ObserveSubscriptionOptions, D as ObserveWsCommand, F as ObserveWsMessage, H as OnGraphEvent, J as OnGraphEventMeta, Q as QUERY_HANDLERS, K as QueryHandler, L as QueryHandlerMeta, S as SAGA_HANDLERS, M as SagaHandler, N as SagaHandlerMeta, P as fromHeader, R as fromJwtPayload, T as getActor, U as getGraphToken, V as getNodeToken, W as observeSSE, X as observeSubscription } from '../../index-D36MAQ3f.js';
1
+ export { T as ToObservableOptions, t as toObservable } from '../../observable-uP-wy_uK.js';
2
+ export { A as ACTOR_KEY, a as ActorExtractor, C as COMMAND_HANDLERS, b as CQRS_EVENT_HANDLERS, d as CRON_HANDLERS, e as CommandHandler, f as CommandHandlerMeta, E as EVENT_HANDLERS, g as EventHandler, h as EventHandlerMeta, G as GRAPHREFLY_REQUEST_GRAPH, j as GRAPHREFLY_ROOT_GRAPH, k as GraphCron, l as GraphCronMeta, m as GraphInterval, n as GraphIntervalMeta, o as GraphReflyCqrsOptions, p as GraphReflyEventExplorer, q as GraphReflyFeatureOptions, r as GraphReflyGuard, s as GraphReflyGuardImpl, t as GraphReflyModule, u as GraphReflyRootOptions, I as INTERVAL_HANDLERS, v as InjectCqrsGraph, w as InjectGraph, x as InjectNode, O as ObserveGateway, y as ObserveGatewayOptions, z as ObserveSSEOptions, B as ObserveSubscriptionOptions, D as ObserveWsCommand, F as ObserveWsMessage, H as OnGraphEvent, J as OnGraphEventMeta, Q as QUERY_HANDLERS, K as QueryHandler, L as QueryHandlerMeta, S as SAGA_HANDLERS, M as SagaHandler, N as SagaHandlerMeta, P as fromHeader, R as fromJwtPayload, T as getActor, U as getGraphToken, V as getNodeToken, W as observeSSE, X as observeSubscription } from '../../index-DiobMNwE.js';
3
3
  import 'rxjs';
4
- import '../../node-C5UD5MGq.js';
4
+ import '../../node-B-f-Lu-k.js';
5
5
  import '@nestjs/common';
6
6
  import '@nestjs/core';
7
- import '../../graph-DFr0diXB.js';
8
- import '../../meta-n3FoVWML.js';
9
- import '../../storage-C9fZfMfM.js';
7
+ import '../../graph-B6NFqv3z.js';
8
+ import '../../meta-CnkLA_43.js';
9
+ import '../../storage-F2X1U1x0.js';
@@ -31,16 +31,18 @@ import {
31
31
  getNodeToken,
32
32
  observeSSE,
33
33
  observeSubscription
34
- } from "../../chunk-MQBQOFDS.js";
35
- import "../../chunk-RHI3GHZW.js";
36
- import "../../chunk-44HD4BTA.js";
34
+ } from "../../chunk-IPLKX3L2.js";
35
+ import "../../chunk-QOWVNWOC.js";
37
36
  import {
38
37
  toObservable
39
- } from "../../chunk-IR3KMOLX.js";
40
- import "../../chunk-NXC35KC5.js";
41
- import "../../chunk-TH6COGOP.js";
38
+ } from "../../chunk-PY4XCDLR.js";
39
+ import "../../chunk-MW4VAKAO.js";
40
+ import "../../chunk-HWPIFSW2.js";
41
+ import "../../chunk-AOCBDH4T.js";
42
+ import "../../chunk-XOFWRC73.js";
43
+ import "../../chunk-H4RVA4VE.js";
42
44
  import "../../chunk-7TAQJHQV.js";
43
- import "../../chunk-QA3RP5NH.js";
45
+ import "../../chunk-5DJTTKX3.js";
44
46
  export {
45
47
  ACTOR_KEY,
46
48
  COMMAND_HANDLERS,
@@ -92,9 +92,20 @@ var flushInProgress = false;
92
92
  var drainPhase2 = [];
93
93
  var drainPhase3 = [];
94
94
  var drainPhase4 = [];
95
+ var flushHooks = [];
95
96
  function isBatching() {
96
97
  return batchDepth > 0 || flushInProgress;
97
98
  }
99
+ function isExplicitlyBatching() {
100
+ return batchDepth > 0;
101
+ }
102
+ function registerBatchFlushHook(hook) {
103
+ if (batchDepth > 0) {
104
+ flushHooks.push(hook);
105
+ } else {
106
+ hook();
107
+ }
108
+ }
98
109
  function batch(fn) {
99
110
  batchDepth += 1;
100
111
  let threw = false;
@@ -108,6 +119,13 @@ function batch(fn) {
108
119
  if (batchDepth === 0) {
109
120
  if (threw) {
110
121
  if (!flushInProgress) {
122
+ const hooks = flushHooks.splice(0);
123
+ for (const h of hooks) {
124
+ try {
125
+ h();
126
+ } catch {
127
+ }
128
+ }
111
129
  drainPhase2.length = 0;
112
130
  drainPhase3.length = 0;
113
131
  drainPhase4.length = 0;
@@ -124,7 +142,18 @@ function drainPending() {
124
142
  const errors = [];
125
143
  let iterations = 0;
126
144
  try {
127
- while (drainPhase2.length > 0 || drainPhase3.length > 0 || drainPhase4.length > 0) {
145
+ while (drainPhase2.length > 0 || drainPhase3.length > 0 || drainPhase4.length > 0 || ownsFlush && flushHooks.length > 0) {
146
+ if (ownsFlush && flushHooks.length > 0) {
147
+ const hooks = flushHooks.splice(0);
148
+ for (const h of hooks) {
149
+ try {
150
+ h();
151
+ } catch (e) {
152
+ errors.push(e);
153
+ }
154
+ }
155
+ continue;
156
+ }
128
157
  iterations += 1;
129
158
  if (iterations > MAX_DRAIN_ITERATIONS) {
130
159
  drainPhase2.length = 0;
@@ -855,6 +884,22 @@ var NodeImpl = class _NodeImpl {
855
884
  * treats `0` as "wave settled" — O(1) check for full dep settlement.
856
885
  */
857
886
  _dirtyDepCount = 0;
887
+ // --- Per-batch emit accumulator (Bug 2: K+1 fan-in fix) ---
888
+ /**
889
+ * Inside an explicit `batch(() => ...)` scope, every `_emit` accumulates
890
+ * its already-framed messages here instead of dispatching synchronously.
891
+ * At batch end, `_flushBatchPending` runs (registered via
892
+ * `registerBatchFlushHook`) and delivers the whole accumulated batch as
893
+ * one `downWithBatch` call — collapsing what would otherwise be K
894
+ * separate sink invocations into one. This is the fix for the diamond
895
+ * fan-in K+1 over-fire.
896
+ *
897
+ * `null` outside batch (or after flush). Only ever appended to within
898
+ * a single explicit batch lifetime; reset to `null` on flush. State
899
+ * updates (cache, version, status) still happen per-emit via
900
+ * `_updateState` — only the downstream delivery is coalesced.
901
+ */
902
+ _batchPendingMessages = null;
858
903
  // --- PAUSE/RESUME lock tracking (C0) ---
859
904
  /**
860
905
  * Set of active pause locks held against this node. Every `[PAUSE, lockId]`
@@ -1232,7 +1277,10 @@ var NodeImpl = class _NodeImpl {
1232
1277
  dep.unsub = noopUnsub;
1233
1278
  dep.unsub = dep.node.subscribe((msgs) => {
1234
1279
  if (dep.unsub === null) return;
1280
+ const tierOf = this._config.tierOf;
1281
+ let sawSettlement = false;
1235
1282
  for (const m of msgs) {
1283
+ if (tierOf(m[0]) >= 3) sawSettlement = true;
1236
1284
  this._config.onMessage(
1237
1285
  this,
1238
1286
  m,
@@ -1240,6 +1288,7 @@ var NodeImpl = class _NodeImpl {
1240
1288
  this._actions
1241
1289
  );
1242
1290
  }
1291
+ if (sawSettlement) this._maybeRunFnOnSettlement();
1243
1292
  });
1244
1293
  subscribedCount++;
1245
1294
  }
@@ -1294,7 +1343,10 @@ var NodeImpl = class _NodeImpl {
1294
1343
  try {
1295
1344
  record.unsub = depNode.subscribe((msgs) => {
1296
1345
  if (record.unsub === null) return;
1346
+ const tierOf = this._config.tierOf;
1347
+ let sawSettlement = false;
1297
1348
  for (const m of msgs) {
1349
+ if (tierOf(m[0]) >= 3) sawSettlement = true;
1298
1350
  this._config.onMessage(
1299
1351
  this,
1300
1352
  m,
@@ -1302,6 +1354,7 @@ var NodeImpl = class _NodeImpl {
1302
1354
  this._actions
1303
1355
  );
1304
1356
  }
1357
+ if (sawSettlement) this._maybeRunFnOnSettlement();
1305
1358
  });
1306
1359
  } catch (err) {
1307
1360
  record.unsub = null;
@@ -1423,7 +1476,6 @@ var NodeImpl = class _NodeImpl {
1423
1476
  }
1424
1477
  return;
1425
1478
  }
1426
- this._maybeRunFnOnSettlement();
1427
1479
  }
1428
1480
  // --- Centralized dep-state transitions (A3 settlement counters) ---
1429
1481
  //
@@ -1669,37 +1721,35 @@ var NodeImpl = class _NodeImpl {
1669
1721
  // --- Emit pipeline ---
1670
1722
  /**
1671
1723
  * @internal The unified dispatch waist — one call = one wave.
1724
+ * See `GRAPHREFLY-SPEC.md` §1.3.1 for protocol context — the stages
1725
+ * below are the implementation order.
1672
1726
  *
1673
1727
  * Pipeline stages, in order:
1674
1728
  *
1675
- * 1. Early-return on empty batch.
1676
- * 2. Terminal filter — post-COMPLETE/ERROR only TEARDOWN/INVALIDATE
1729
+ * 1. Terminal filter — post-COMPLETE/ERROR only TEARDOWN/INVALIDATE
1677
1730
  * still propagate so graph teardown and cache-clear still work.
1678
- * 3. Tier sort (stable) — the batch can be in any order when it
1731
+ * 2. Tier sort (stable) — the batch can be in any order when it
1679
1732
  * arrives; the walker downstream (`downWithBatch`) assumes
1680
1733
  * ascending tier monotone, and so does `_updateState`'s tier-3
1681
1734
  * slice walk. This is the single source of truth for ordering.
1682
- * 4. Synthetic DIRTY prefix — if a tier-3 payload is present, no
1735
+ * 3. Synthetic DIRTY prefix — if a tier-3 payload is present, no
1683
1736
  * DIRTY is already in the batch, and the node isn't already in
1684
1737
  * `"dirty"` status, prepend `[DIRTY]` after any tier-0 START
1685
1738
  * entries. Guarantees spec §1.3.1 (DIRTY precedes DATA within
1686
1739
  * the same batch) uniformly across every entry point.
1687
- * 5. PAUSE/RESUME lock bookkeeping (C0) — update `_pauseLocks`,
1740
+ * 4. PAUSE/RESUME lock bookkeeping (C0) — update `_pauseLocks`,
1688
1741
  * derive `_paused`, filter unknown-lockId RESUME, replay
1689
1742
  * bufferAll buffer on final lock release.
1690
- * 6. Meta TEARDOWN fan-out — notify meta children before
1743
+ * 5. Meta TEARDOWN fan-out — notify meta children before
1691
1744
  * `_updateState`'s TEARDOWN branch calls `_deactivate`. Hoisted
1692
1745
  * out of the walk to keep `_updateState` re-entrance-free.
1693
- * 7. `_updateState` — walk the batch in tier order, advancing
1746
+ * 6. `_updateState` — walk the batch in tier order, advancing
1694
1747
  * `_cached` / `_status` / `_versioning` and running equals
1695
1748
  * substitution on tier-3 DATA (§3.5.1). Returns
1696
1749
  * `{finalMessages, equalsError?}`.
1697
- * 8. `downWithBatch` dispatch (or bufferAll capture if paused with
1750
+ * 7. `downWithBatch` dispatch (or bufferAll capture if paused with
1698
1751
  * `pausable: "resumeAll"`).
1699
- * 9. Recursive ERROR emission if equals threw mid-walk.
1700
- *
1701
- * `node.down` / `node.emit` / `actions.down` / `actions.emit` all
1702
- * converge here — the unified `_emit` waist (spec §1.3.1).
1752
+ * 8. Recursive ERROR emission if equals threw mid-walk.
1703
1753
  */
1704
1754
  _emit(messages) {
1705
1755
  if (messages.length === 0) return;
@@ -1795,10 +1845,10 @@ var NodeImpl = class _NodeImpl {
1795
1845
  }
1796
1846
  }
1797
1847
  if (immediate.length > 0) {
1798
- downWithBatch(this._deliverToSinks, immediate, tierOf);
1848
+ this._dispatchOrAccumulate(immediate);
1799
1849
  }
1800
1850
  } else {
1801
- downWithBatch(this._deliverToSinks, finalMessages, this._config.tierOf);
1851
+ this._dispatchOrAccumulate(finalMessages);
1802
1852
  }
1803
1853
  }
1804
1854
  if (equalsError != null) {
@@ -1921,6 +1971,50 @@ var NodeImpl = class _NodeImpl {
1921
1971
  const snapshot = [...this._sinks];
1922
1972
  for (const sink of snapshot) sink(messages);
1923
1973
  };
1974
+ /**
1975
+ * @internal Dispatch entry point that respects the per-batch emit
1976
+ * accumulator (Bug 2). Inside an explicit `batch()` scope, append to
1977
+ * `_batchPendingMessages` and register a flush hook on first append.
1978
+ * Outside batch — or during a drain (where `flushInProgress` is true
1979
+ * but `batchDepth` is 0) — dispatch synchronously through `downWithBatch`.
1980
+ *
1981
+ * Per-emit state updates (`_frameBatch`, `_updateState`) have already
1982
+ * happened by the time we reach here; only the **downstream delivery**
1983
+ * is coalesced. Cache, version, and status are visible mid-batch on
1984
+ * the emitting node itself.
1985
+ */
1986
+ _dispatchOrAccumulate(messages) {
1987
+ if (isExplicitlyBatching()) {
1988
+ if (this._batchPendingMessages === null) {
1989
+ this._batchPendingMessages = [];
1990
+ registerBatchFlushHook(() => this._flushBatchPending());
1991
+ }
1992
+ for (const m of messages) this._batchPendingMessages.push(m);
1993
+ return;
1994
+ }
1995
+ downWithBatch(this._deliverToSinks, messages, this._config.tierOf);
1996
+ }
1997
+ /**
1998
+ * @internal Flushes the accumulated batch through `downWithBatch` and
1999
+ * clears the pending state. Idempotent — safe to call when pending is
2000
+ * already null or empty (e.g. on a `batch()` throw, where the hook
2001
+ * fires for cleanup but the drainPhase queues are wiped after).
2002
+ *
2003
+ * Critical: the accumulated batch is interleaved per-emit framings like
2004
+ * `[DIRTY, DATA(1), DIRTY, DATA(2)]` — non-monotone tier order. We must
2005
+ * re-frame to sort by tier before handing to `downWithBatch`, which
2006
+ * assumes pre-sorted input. `_frameBatch` also handles the synthetic
2007
+ * DIRTY prepend rule (no-op here — `hasDirty` is true since each
2008
+ * accumulated emit already carries its own DIRTY prefix).
2009
+ */
2010
+ _flushBatchPending() {
2011
+ const pending = this._batchPendingMessages;
2012
+ if (pending === null) return;
2013
+ this._batchPendingMessages = null;
2014
+ if (pending.length === 0) return;
2015
+ const framed = this._frameBatch(pending);
2016
+ downWithBatch(this._deliverToSinks, framed, this._config.tierOf);
2017
+ }
1924
2018
  };
1925
2019
  var isNodeArray = (value) => Array.isArray(value);
1926
2020
  var isNodeOptionsObject = (value) => typeof value === "object" && value != null && !Array.isArray(value);