@webjsdev/core 0.7.56 → 0.7.57

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/core",
3
- "version": "0.7.56",
3
+ "version": "0.7.57",
4
4
  "type": "module",
5
5
  "description": "The runtime for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Ships the html and css template tags, the WebComponent base class, signals, directives, and the isomorphic renderers.",
6
6
  "types": "./index.d.ts",
package/src/signal.js CHANGED
@@ -81,7 +81,7 @@ let batchDepth = 0;
81
81
  * @property {boolean} dirty
82
82
  * @property {ReactiveNode[]} producers
83
83
  * @property {number[]} producerLastReadVersions
84
- * @property {Set<ReactiveNode>=} liveConsumers
84
+ * @property {Map<ReactiveNode, number>=} liveConsumers live consumer -> how many of its producer slots read this node
85
85
  * @property {boolean} consumerAllowSignalWrites
86
86
  * @property {boolean} consumerIsAlwaysLive
87
87
  * @property {(this: ReactiveNode) => boolean} producerMustRecompute
@@ -105,11 +105,18 @@ const FALSE_FN = () => false;
105
105
  * This is how source `set()` calls reach watchers across computed
106
106
  * chains: the push side of the graph propagates upward only when
107
107
  * something downstream is actually live.
108
+ *
109
+ * The edge is REFERENCE COUNTED, one count per producer slot (#1508). A
110
+ * consumer that reads the same signal twice in one run holds it in two slots,
111
+ * and every slot is added and removed on its own as runs shrink, grow or
112
+ * reorder. With a plain set the first removal dropped the edge outright, so a
113
+ * render that went from reading a signal twice to once (or swapped the order
114
+ * of two reads) stopped hearing about that signal while still depending on it.
108
115
  */
109
116
  function producerAddLiveConsumer(node, consumer) {
110
- if (!node.liveConsumers) node.liveConsumers = new Set();
117
+ if (!node.liveConsumers) node.liveConsumers = new Map();
111
118
  const wasEmpty = node.liveConsumers.size === 0;
112
- node.liveConsumers.add(consumer);
119
+ node.liveConsumers.set(consumer, (node.liveConsumers.get(consumer) || 0) + 1);
113
120
  if (wasEmpty) {
114
121
  if (node.producers && node.producers.length > 0) {
115
122
  for (const p of node.producers) producerAddLiveConsumer(p, node);
@@ -120,9 +127,12 @@ function producerAddLiveConsumer(node, consumer) {
120
127
  }
121
128
  }
122
129
 
123
- /** Inverse of producerAddLiveConsumer. Cascades teardown upward. */
130
+ /** Inverse of producerAddLiveConsumer: drops one slot's count, and the edge with the last one. Cascades teardown upward. */
124
131
  function producerRemoveLiveConsumer(node, consumer) {
125
132
  if (!node.liveConsumers) return;
133
+ const n = node.liveConsumers.get(consumer);
134
+ if (n === undefined) return;
135
+ if (n > 1) { node.liveConsumers.set(consumer, n - 1); return; }
126
136
  node.liveConsumers.delete(consumer);
127
137
  if (node.liveConsumers.size === 0) {
128
138
  if (node.producers && node.producers.length > 0) {
@@ -181,7 +191,7 @@ function producerNotifyConsumers(node) {
181
191
  const prev = inNotificationPhase;
182
192
  inNotificationPhase = true;
183
193
  try {
184
- for (const consumer of node.liveConsumers) {
194
+ for (const consumer of node.liveConsumers.keys()) {
185
195
  if (!consumer.dirty) {
186
196
  consumerMarkDirty(consumer);
187
197
  }
@@ -316,7 +326,7 @@ function collectBatched(node) {
316
326
  inNotificationPhase = true;
317
327
  try {
318
328
  if (!node.liveConsumers) return;
319
- for (const consumer of node.liveConsumers) {
329
+ for (const consumer of node.liveConsumers.keys()) {
320
330
  if (consumer.dirty) continue;
321
331
  consumer.dirty = true;
322
332
  if (consumer.__isWatcher) {
@@ -550,7 +560,7 @@ function introspectSources(sink) {
550
560
 
551
561
  /** @param {SignalState | SignalComputed} producer */
552
562
  function introspectSinks(producer) {
553
- return producer.liveConsumers ? [...producer.liveConsumers] : [];
563
+ return producer.liveConsumers ? [...producer.liveConsumers.keys()] : [];
554
564
  }
555
565
 
556
566
  /** @param {SignalState | SignalComputed} producer */
@@ -146,6 +146,14 @@ export interface WebjsDevTasks {
146
146
  * one run. Ignored by `webjs start`.
147
147
  */
148
148
  sourceLocations?: boolean;
149
+ /**
150
+ * Live-reload idle close (#1507), DEV-ONLY, off by default. Seconds with no
151
+ * edit and no interaction after which the dev live-reload stream closes, so a
152
+ * host that counts an open request as activity can sleep with a preview tab
153
+ * open; interaction, a tab showing, or an embed-bridge host command reopens
154
+ * it. `WEBJS_DEV_RELOAD_IDLE` (seconds) overrides it. Ignored by `webjs start`.
155
+ */
156
+ reloadIdle?: number;
149
157
  }
150
158
 
151
159
  /** Start task orchestration in `webjs.start` (#550). Read by the CLI, not the server. */