@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,3 +1,3 @@
1
- export { A as Actor, C as COMPLETE, c as COMPLETE_MSG, d as COMPLETE_ONLY_BATCH, D as DATA, e as DEFAULT_ACTOR, f as DIRTY, g as DIRTY_MSG, h as DIRTY_ONLY_BATCH, i as DepRecord, E as ERROR, F as FnCtx, G as GlobalInspectorEvent, j as GlobalInspectorHook, k as GraphReFlyConfig, l as GuardAction, m as GuardDenied, n as GuardDeniedDetails, H as HashFn, I as INVALIDATE, o as INVALIDATE_MSG, p as INVALIDATE_ONLY_BATCH, M as Message, q as MessageContext, r as MessageTypeRegistration, s as MessageTypeRegistrationInput, t as Messages, N as Node, b as NodeActions, u as NodeCtx, v as NodeDescribeKind, w as NodeFn, x as NodeFnCleanup, y as NodeGuard, z as NodeImpl, B as NodeInspectorHook, J as NodeInspectorHookEvent, a as NodeOptions, K as NodeSink, L as NodeStatus, O as NodeTransportOptions, P as NodeVersionInfo, Q as OnMessageHandler, R as OnSubscribeHandler, S as PAUSE, T as PolicyAllow, U as PolicyDeny, V as PolicyRuleData, W as RESOLVED, X as RESOLVED_MSG, Y as RESOLVED_ONLY_BATCH, Z as RESUME, _ as START, $ as START_MSG, a0 as SubscribeContext, a1 as TEARDOWN, a2 as TEARDOWN_MSG, a3 as TEARDOWN_ONLY_BATCH, a4 as V0, a5 as V1, a6 as VersioningLevel, a7 as VersioningOptions, a8 as accessHintForGuard, a9 as advanceVersion, aa as configure, ab as createVersioning, ac as defaultConfig, ad as defaultHash, ae as isV1, af as node, ag as normalizeActor, ah as policy, ai as policyFromRules, aj as registerBuiltins } from '../node-C5UD5MGq.cjs';
2
- export { A as AutoTrackOptions, D as DerivedFn, a as DynamicFn, E as EffectFn, P as PipeOperator, b as ProducerFn, T as TrackFn, c as autoTrackNode, d as batch, e as derived, f as downWithBatch, g as dynamicNode, h as effect, j as isBatching, m as monotonicNs, p as pipe, k as producer, s as state, w as wallClockNs } from '../index-C9z6rU9P.cjs';
3
- export { D as DescribeDetail, a as DescribeField, b as DescribeNodeOutput, r as resolveDescribeFields } from '../meta--fr9sxRM.cjs';
1
+ export { A as Actor, C as COMPLETE, c as COMPLETE_MSG, d as COMPLETE_ONLY_BATCH, D as DATA, e as DEFAULT_ACTOR, f as DIRTY, g as DIRTY_MSG, h as DIRTY_ONLY_BATCH, i as DepRecord, E as ERROR, F as FnCtx, G as GlobalInspectorEvent, j as GlobalInspectorHook, k as GraphReFlyConfig, l as GuardAction, m as GuardDenied, n as GuardDeniedDetails, H as HashFn, I as INVALIDATE, o as INVALIDATE_MSG, p as INVALIDATE_ONLY_BATCH, M as Message, q as MessageContext, r as MessageTypeRegistration, s as MessageTypeRegistrationInput, t as Messages, N as Node, b as NodeActions, u as NodeCtx, v as NodeDescribeKind, w as NodeFn, x as NodeFnCleanup, y as NodeGuard, z as NodeImpl, B as NodeInspectorHook, J as NodeInspectorHookEvent, a as NodeOptions, K as NodeSink, L as NodeStatus, O as NodeTransportOptions, P as NodeVersionInfo, Q as OnMessageHandler, R as OnSubscribeHandler, S as PAUSE, T as PolicyAllow, U as PolicyDeny, V as PolicyRuleData, W as RESOLVED, X as RESOLVED_MSG, Y as RESOLVED_ONLY_BATCH, Z as RESUME, _ as START, $ as START_MSG, a0 as SubscribeContext, a1 as TEARDOWN, a2 as TEARDOWN_MSG, a3 as TEARDOWN_ONLY_BATCH, a4 as V0, a5 as V1, a6 as VersioningLevel, a7 as VersioningOptions, a8 as accessHintForGuard, a9 as advanceVersion, aa as configure, ab as createVersioning, ac as defaultConfig, ad as defaultHash, ae as isV1, af as node, ag as normalizeActor, ah as policy, ai as policyFromRules, aj as registerBuiltins } from '../node-B-f-Lu-k.cjs';
2
+ export { A as AutoTrackOptions, D as DerivedFn, a as DynamicFn, E as EffectFn, P as PipeOperator, b as ProducerFn, T as TrackFn, c as autoTrackNode, d as batch, e as derived, f as downWithBatch, g as dynamicNode, h as effect, j as isBatching, m as monotonicNs, p as pipe, k as producer, s as state, w as wallClockNs } from '../index-BJB7t9gg.cjs';
3
+ export { D as DescribeDetail, a as DescribeField, b as DescribeNodeOutput, r as resolveDescribeFields } from '../meta-DWbkoq1s.cjs';
@@ -1,3 +1,3 @@
1
- export { A as Actor, C as COMPLETE, c as COMPLETE_MSG, d as COMPLETE_ONLY_BATCH, D as DATA, e as DEFAULT_ACTOR, f as DIRTY, g as DIRTY_MSG, h as DIRTY_ONLY_BATCH, i as DepRecord, E as ERROR, F as FnCtx, G as GlobalInspectorEvent, j as GlobalInspectorHook, k as GraphReFlyConfig, l as GuardAction, m as GuardDenied, n as GuardDeniedDetails, H as HashFn, I as INVALIDATE, o as INVALIDATE_MSG, p as INVALIDATE_ONLY_BATCH, M as Message, q as MessageContext, r as MessageTypeRegistration, s as MessageTypeRegistrationInput, t as Messages, N as Node, b as NodeActions, u as NodeCtx, v as NodeDescribeKind, w as NodeFn, x as NodeFnCleanup, y as NodeGuard, z as NodeImpl, B as NodeInspectorHook, J as NodeInspectorHookEvent, a as NodeOptions, K as NodeSink, L as NodeStatus, O as NodeTransportOptions, P as NodeVersionInfo, Q as OnMessageHandler, R as OnSubscribeHandler, S as PAUSE, T as PolicyAllow, U as PolicyDeny, V as PolicyRuleData, W as RESOLVED, X as RESOLVED_MSG, Y as RESOLVED_ONLY_BATCH, Z as RESUME, _ as START, $ as START_MSG, a0 as SubscribeContext, a1 as TEARDOWN, a2 as TEARDOWN_MSG, a3 as TEARDOWN_ONLY_BATCH, a4 as V0, a5 as V1, a6 as VersioningLevel, a7 as VersioningOptions, a8 as accessHintForGuard, a9 as advanceVersion, aa as configure, ab as createVersioning, ac as defaultConfig, ad as defaultHash, ae as isV1, af as node, ag as normalizeActor, ah as policy, ai as policyFromRules, aj as registerBuiltins } from '../node-C5UD5MGq.js';
2
- export { A as AutoTrackOptions, D as DerivedFn, a as DynamicFn, E as EffectFn, P as PipeOperator, b as ProducerFn, T as TrackFn, c as autoTrackNode, d as batch, e as derived, f as downWithBatch, g as dynamicNode, h as effect, j as isBatching, m as monotonicNs, p as pipe, k as producer, s as state, w as wallClockNs } from '../index-HdJx_BjO.js';
3
- export { D as DescribeDetail, a as DescribeField, b as DescribeNodeOutput, r as resolveDescribeFields } from '../meta-n3FoVWML.js';
1
+ export { A as Actor, C as COMPLETE, c as COMPLETE_MSG, d as COMPLETE_ONLY_BATCH, D as DATA, e as DEFAULT_ACTOR, f as DIRTY, g as DIRTY_MSG, h as DIRTY_ONLY_BATCH, i as DepRecord, E as ERROR, F as FnCtx, G as GlobalInspectorEvent, j as GlobalInspectorHook, k as GraphReFlyConfig, l as GuardAction, m as GuardDenied, n as GuardDeniedDetails, H as HashFn, I as INVALIDATE, o as INVALIDATE_MSG, p as INVALIDATE_ONLY_BATCH, M as Message, q as MessageContext, r as MessageTypeRegistration, s as MessageTypeRegistrationInput, t as Messages, N as Node, b as NodeActions, u as NodeCtx, v as NodeDescribeKind, w as NodeFn, x as NodeFnCleanup, y as NodeGuard, z as NodeImpl, B as NodeInspectorHook, J as NodeInspectorHookEvent, a as NodeOptions, K as NodeSink, L as NodeStatus, O as NodeTransportOptions, P as NodeVersionInfo, Q as OnMessageHandler, R as OnSubscribeHandler, S as PAUSE, T as PolicyAllow, U as PolicyDeny, V as PolicyRuleData, W as RESOLVED, X as RESOLVED_MSG, Y as RESOLVED_ONLY_BATCH, Z as RESUME, _ as START, $ as START_MSG, a0 as SubscribeContext, a1 as TEARDOWN, a2 as TEARDOWN_MSG, a3 as TEARDOWN_ONLY_BATCH, a4 as V0, a5 as V1, a6 as VersioningLevel, a7 as VersioningOptions, a8 as accessHintForGuard, a9 as advanceVersion, aa as configure, ab as createVersioning, ac as defaultConfig, ad as defaultHash, ae as isV1, af as node, ag as normalizeActor, ah as policy, ai as policyFromRules, aj as registerBuiltins } from '../node-B-f-Lu-k.js';
2
+ export { A as AutoTrackOptions, D as DerivedFn, a as DynamicFn, E as EffectFn, P as PipeOperator, b as ProducerFn, T as TrackFn, c as autoTrackNode, d as batch, e as derived, f as downWithBatch, g as dynamicNode, h as effect, j as isBatching, m as monotonicNs, p as pipe, k as producer, s as state, w as wallClockNs } from '../index-C-TXEa7C.js';
3
+ export { D as DescribeDetail, a as DescribeField, b as DescribeNodeOutput, r as resolveDescribeFields } from '../meta-CnkLA_43.js';
@@ -1,7 +1,7 @@
1
- import "../chunk-RHI3GHZW.js";
1
+ import "../chunk-QOWVNWOC.js";
2
2
  import {
3
3
  resolveDescribeFields
4
- } from "../chunk-TH6COGOP.js";
4
+ } from "../chunk-H4RVA4VE.js";
5
5
  import {
6
6
  COMPLETE,
7
7
  COMPLETE_MSG,
@@ -52,7 +52,7 @@ import {
52
52
  registerBuiltins,
53
53
  state,
54
54
  wallClockNs
55
- } from "../chunk-QA3RP5NH.js";
55
+ } from "../chunk-5DJTTKX3.js";
56
56
  export {
57
57
  COMPLETE,
58
58
  COMPLETE_MSG,
@@ -210,9 +210,20 @@ var flushInProgress = false;
210
210
  var drainPhase2 = [];
211
211
  var drainPhase3 = [];
212
212
  var drainPhase4 = [];
213
+ var flushHooks = [];
213
214
  function isBatching() {
214
215
  return batchDepth > 0 || flushInProgress;
215
216
  }
217
+ function isExplicitlyBatching() {
218
+ return batchDepth > 0;
219
+ }
220
+ function registerBatchFlushHook(hook) {
221
+ if (batchDepth > 0) {
222
+ flushHooks.push(hook);
223
+ } else {
224
+ hook();
225
+ }
226
+ }
216
227
  function batch(fn) {
217
228
  batchDepth += 1;
218
229
  let threw = false;
@@ -226,6 +237,13 @@ function batch(fn) {
226
237
  if (batchDepth === 0) {
227
238
  if (threw) {
228
239
  if (!flushInProgress) {
240
+ const hooks = flushHooks.splice(0);
241
+ for (const h of hooks) {
242
+ try {
243
+ h();
244
+ } catch {
245
+ }
246
+ }
229
247
  drainPhase2.length = 0;
230
248
  drainPhase3.length = 0;
231
249
  drainPhase4.length = 0;
@@ -242,7 +260,18 @@ function drainPending() {
242
260
  const errors = [];
243
261
  let iterations = 0;
244
262
  try {
245
- while (drainPhase2.length > 0 || drainPhase3.length > 0 || drainPhase4.length > 0) {
263
+ while (drainPhase2.length > 0 || drainPhase3.length > 0 || drainPhase4.length > 0 || ownsFlush && flushHooks.length > 0) {
264
+ if (ownsFlush && flushHooks.length > 0) {
265
+ const hooks = flushHooks.splice(0);
266
+ for (const h of hooks) {
267
+ try {
268
+ h();
269
+ } catch (e) {
270
+ errors.push(e);
271
+ }
272
+ }
273
+ continue;
274
+ }
246
275
  iterations += 1;
247
276
  if (iterations > MAX_DRAIN_ITERATIONS) {
248
277
  drainPhase2.length = 0;
@@ -900,6 +929,22 @@ var NodeImpl = class _NodeImpl {
900
929
  * treats `0` as "wave settled" — O(1) check for full dep settlement.
901
930
  */
902
931
  _dirtyDepCount = 0;
932
+ // --- Per-batch emit accumulator (Bug 2: K+1 fan-in fix) ---
933
+ /**
934
+ * Inside an explicit `batch(() => ...)` scope, every `_emit` accumulates
935
+ * its already-framed messages here instead of dispatching synchronously.
936
+ * At batch end, `_flushBatchPending` runs (registered via
937
+ * `registerBatchFlushHook`) and delivers the whole accumulated batch as
938
+ * one `downWithBatch` call — collapsing what would otherwise be K
939
+ * separate sink invocations into one. This is the fix for the diamond
940
+ * fan-in K+1 over-fire.
941
+ *
942
+ * `null` outside batch (or after flush). Only ever appended to within
943
+ * a single explicit batch lifetime; reset to `null` on flush. State
944
+ * updates (cache, version, status) still happen per-emit via
945
+ * `_updateState` — only the downstream delivery is coalesced.
946
+ */
947
+ _batchPendingMessages = null;
903
948
  // --- PAUSE/RESUME lock tracking (C0) ---
904
949
  /**
905
950
  * Set of active pause locks held against this node. Every `[PAUSE, lockId]`
@@ -1277,7 +1322,10 @@ var NodeImpl = class _NodeImpl {
1277
1322
  dep.unsub = noopUnsub;
1278
1323
  dep.unsub = dep.node.subscribe((msgs) => {
1279
1324
  if (dep.unsub === null) return;
1325
+ const tierOf = this._config.tierOf;
1326
+ let sawSettlement = false;
1280
1327
  for (const m of msgs) {
1328
+ if (tierOf(m[0]) >= 3) sawSettlement = true;
1281
1329
  this._config.onMessage(
1282
1330
  this,
1283
1331
  m,
@@ -1285,6 +1333,7 @@ var NodeImpl = class _NodeImpl {
1285
1333
  this._actions
1286
1334
  );
1287
1335
  }
1336
+ if (sawSettlement) this._maybeRunFnOnSettlement();
1288
1337
  });
1289
1338
  subscribedCount++;
1290
1339
  }
@@ -1339,7 +1388,10 @@ var NodeImpl = class _NodeImpl {
1339
1388
  try {
1340
1389
  record.unsub = depNode.subscribe((msgs) => {
1341
1390
  if (record.unsub === null) return;
1391
+ const tierOf = this._config.tierOf;
1392
+ let sawSettlement = false;
1342
1393
  for (const m of msgs) {
1394
+ if (tierOf(m[0]) >= 3) sawSettlement = true;
1343
1395
  this._config.onMessage(
1344
1396
  this,
1345
1397
  m,
@@ -1347,6 +1399,7 @@ var NodeImpl = class _NodeImpl {
1347
1399
  this._actions
1348
1400
  );
1349
1401
  }
1402
+ if (sawSettlement) this._maybeRunFnOnSettlement();
1350
1403
  });
1351
1404
  } catch (err) {
1352
1405
  record.unsub = null;
@@ -1468,7 +1521,6 @@ var NodeImpl = class _NodeImpl {
1468
1521
  }
1469
1522
  return;
1470
1523
  }
1471
- this._maybeRunFnOnSettlement();
1472
1524
  }
1473
1525
  // --- Centralized dep-state transitions (A3 settlement counters) ---
1474
1526
  //
@@ -1714,37 +1766,35 @@ var NodeImpl = class _NodeImpl {
1714
1766
  // --- Emit pipeline ---
1715
1767
  /**
1716
1768
  * @internal The unified dispatch waist — one call = one wave.
1769
+ * See `GRAPHREFLY-SPEC.md` §1.3.1 for protocol context — the stages
1770
+ * below are the implementation order.
1717
1771
  *
1718
1772
  * Pipeline stages, in order:
1719
1773
  *
1720
- * 1. Early-return on empty batch.
1721
- * 2. Terminal filter — post-COMPLETE/ERROR only TEARDOWN/INVALIDATE
1774
+ * 1. Terminal filter — post-COMPLETE/ERROR only TEARDOWN/INVALIDATE
1722
1775
  * still propagate so graph teardown and cache-clear still work.
1723
- * 3. Tier sort (stable) — the batch can be in any order when it
1776
+ * 2. Tier sort (stable) — the batch can be in any order when it
1724
1777
  * arrives; the walker downstream (`downWithBatch`) assumes
1725
1778
  * ascending tier monotone, and so does `_updateState`'s tier-3
1726
1779
  * slice walk. This is the single source of truth for ordering.
1727
- * 4. Synthetic DIRTY prefix — if a tier-3 payload is present, no
1780
+ * 3. Synthetic DIRTY prefix — if a tier-3 payload is present, no
1728
1781
  * DIRTY is already in the batch, and the node isn't already in
1729
1782
  * `"dirty"` status, prepend `[DIRTY]` after any tier-0 START
1730
1783
  * entries. Guarantees spec §1.3.1 (DIRTY precedes DATA within
1731
1784
  * the same batch) uniformly across every entry point.
1732
- * 5. PAUSE/RESUME lock bookkeeping (C0) — update `_pauseLocks`,
1785
+ * 4. PAUSE/RESUME lock bookkeeping (C0) — update `_pauseLocks`,
1733
1786
  * derive `_paused`, filter unknown-lockId RESUME, replay
1734
1787
  * bufferAll buffer on final lock release.
1735
- * 6. Meta TEARDOWN fan-out — notify meta children before
1788
+ * 5. Meta TEARDOWN fan-out — notify meta children before
1736
1789
  * `_updateState`'s TEARDOWN branch calls `_deactivate`. Hoisted
1737
1790
  * out of the walk to keep `_updateState` re-entrance-free.
1738
- * 7. `_updateState` — walk the batch in tier order, advancing
1791
+ * 6. `_updateState` — walk the batch in tier order, advancing
1739
1792
  * `_cached` / `_status` / `_versioning` and running equals
1740
1793
  * substitution on tier-3 DATA (§3.5.1). Returns
1741
1794
  * `{finalMessages, equalsError?}`.
1742
- * 8. `downWithBatch` dispatch (or bufferAll capture if paused with
1795
+ * 7. `downWithBatch` dispatch (or bufferAll capture if paused with
1743
1796
  * `pausable: "resumeAll"`).
1744
- * 9. Recursive ERROR emission if equals threw mid-walk.
1745
- *
1746
- * `node.down` / `node.emit` / `actions.down` / `actions.emit` all
1747
- * converge here — the unified `_emit` waist (spec §1.3.1).
1797
+ * 8. Recursive ERROR emission if equals threw mid-walk.
1748
1798
  */
1749
1799
  _emit(messages) {
1750
1800
  if (messages.length === 0) return;
@@ -1840,10 +1890,10 @@ var NodeImpl = class _NodeImpl {
1840
1890
  }
1841
1891
  }
1842
1892
  if (immediate.length > 0) {
1843
- downWithBatch(this._deliverToSinks, immediate, tierOf);
1893
+ this._dispatchOrAccumulate(immediate);
1844
1894
  }
1845
1895
  } else {
1846
- downWithBatch(this._deliverToSinks, finalMessages, this._config.tierOf);
1896
+ this._dispatchOrAccumulate(finalMessages);
1847
1897
  }
1848
1898
  }
1849
1899
  if (equalsError != null) {
@@ -1966,6 +2016,50 @@ var NodeImpl = class _NodeImpl {
1966
2016
  const snapshot = [...this._sinks];
1967
2017
  for (const sink of snapshot) sink(messages);
1968
2018
  };
2019
+ /**
2020
+ * @internal Dispatch entry point that respects the per-batch emit
2021
+ * accumulator (Bug 2). Inside an explicit `batch()` scope, append to
2022
+ * `_batchPendingMessages` and register a flush hook on first append.
2023
+ * Outside batch — or during a drain (where `flushInProgress` is true
2024
+ * but `batchDepth` is 0) — dispatch synchronously through `downWithBatch`.
2025
+ *
2026
+ * Per-emit state updates (`_frameBatch`, `_updateState`) have already
2027
+ * happened by the time we reach here; only the **downstream delivery**
2028
+ * is coalesced. Cache, version, and status are visible mid-batch on
2029
+ * the emitting node itself.
2030
+ */
2031
+ _dispatchOrAccumulate(messages) {
2032
+ if (isExplicitlyBatching()) {
2033
+ if (this._batchPendingMessages === null) {
2034
+ this._batchPendingMessages = [];
2035
+ registerBatchFlushHook(() => this._flushBatchPending());
2036
+ }
2037
+ for (const m of messages) this._batchPendingMessages.push(m);
2038
+ return;
2039
+ }
2040
+ downWithBatch(this._deliverToSinks, messages, this._config.tierOf);
2041
+ }
2042
+ /**
2043
+ * @internal Flushes the accumulated batch through `downWithBatch` and
2044
+ * clears the pending state. Idempotent — safe to call when pending is
2045
+ * already null or empty (e.g. on a `batch()` throw, where the hook
2046
+ * fires for cleanup but the drainPhase queues are wiped after).
2047
+ *
2048
+ * Critical: the accumulated batch is interleaved per-emit framings like
2049
+ * `[DIRTY, DATA(1), DIRTY, DATA(2)]` — non-monotone tier order. We must
2050
+ * re-frame to sort by tier before handing to `downWithBatch`, which
2051
+ * assumes pre-sorted input. `_frameBatch` also handles the synthetic
2052
+ * DIRTY prepend rule (no-op here — `hasDirty` is true since each
2053
+ * accumulated emit already carries its own DIRTY prefix).
2054
+ */
2055
+ _flushBatchPending() {
2056
+ const pending = this._batchPendingMessages;
2057
+ if (pending === null) return;
2058
+ this._batchPendingMessages = null;
2059
+ if (pending.length === 0) return;
2060
+ const framed = this._frameBatch(pending);
2061
+ downWithBatch(this._deliverToSinks, framed, this._config.tierOf);
2062
+ }
1969
2063
  };
1970
2064
  var isNodeArray = (value) => Array.isArray(value);
1971
2065
  var isNodeOptionsObject = (value) => typeof value === "object" && value != null && !Array.isArray(value);