@solidjs/signals 0.9.4 → 0.9.6

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/dist/dev.js CHANGED
@@ -25,12 +25,14 @@ const REACTIVE_IN_HEAP = 1 << 3;
25
25
  const REACTIVE_IN_HEAP_HEIGHT = 1 << 4;
26
26
  const REACTIVE_ZOMBIE = 1 << 5;
27
27
  const REACTIVE_DISPOSED = 1 << 6;
28
+ const REACTIVE_OPTIMISTIC_DIRTY = 1 << 7;
28
29
  const STATUS_NONE = 0;
29
30
  const STATUS_PENDING = 1 << 0;
30
31
  const STATUS_ERROR = 1 << 1;
31
32
  const STATUS_UNINITIALIZED = 1 << 2;
32
33
  const EFFECT_RENDER = 1;
33
34
  const EFFECT_USER = 2;
35
+ const EFFECT_TRACKED = 3;
34
36
  const NOT_PENDING = {};
35
37
  const SUPPORTS_PROXY = typeof Proxy === "function";
36
38
  const defaultContext = {};
@@ -133,20 +135,28 @@ function adjustHeight(el, heap) {
133
135
  }
134
136
  }
135
137
  const transitions = new Set();
136
- let optimisticRun = false;
137
138
  const dirtyQueue = { _heap: new Array(2e3).fill(undefined), _marked: false, _min: 0, _max: 0 };
138
139
  const zombieQueue = { _heap: new Array(2e3).fill(undefined), _marked: false, _min: 0, _max: 0 };
139
140
  let clock = 0;
140
141
  let activeTransition = null;
141
142
  let scheduled = false;
143
+ let optimisticReadActive = false;
144
+ let projectionWriteActive = false;
145
+ function setOptimisticReadActive(value) {
146
+ optimisticReadActive = value;
147
+ }
148
+ function setProjectionWriteActive(value) {
149
+ projectionWriteActive = value;
150
+ }
142
151
  function schedule() {
143
152
  if (scheduled) return;
144
153
  scheduled = true;
145
- if (!globalQueue._running) Promise.resolve().then(() => queueMicrotask(flush));
154
+ if (!globalQueue._running) queueMicrotask(flush);
146
155
  }
147
156
  class Queue {
148
157
  _parent = null;
149
158
  _queues = [[], []];
159
+ _optimisticQueues = [[], []];
150
160
  _children = [];
151
161
  created = clock;
152
162
  addChild(child) {
@@ -164,18 +174,27 @@ class Queue {
164
174
  if (this._parent) return this._parent.notify(node, mask, flags);
165
175
  return false;
166
176
  }
167
- run(type) {
168
- if (this._queues[type - 1].length) {
169
- const effects = this._queues[type - 1];
170
- this._queues[type - 1] = [];
177
+ _runQueue(type, queues, method) {
178
+ if (queues[type - 1].length) {
179
+ const effects = queues[type - 1];
180
+ queues[type - 1] = [];
171
181
  runQueue(effects, type);
172
182
  }
173
183
  for (let i = 0; i < this._children.length; i++) {
174
- this._children[i].run(type);
184
+ this._children[i][method]?.(type);
175
185
  }
176
186
  }
187
+ run(type) {
188
+ this._runQueue(type, this._queues, "run");
189
+ }
190
+ runOptimistic(type) {
191
+ this._runQueue(type, this._optimisticQueues, "runOptimistic");
192
+ }
177
193
  enqueue(type, fn) {
178
- if (type) this._queues[type - 1].push(fn);
194
+ if (type) {
195
+ const queue = optimisticReadActive ? this._optimisticQueues : this._queues;
196
+ queue[type - 1].push(fn);
197
+ }
179
198
  schedule();
180
199
  }
181
200
  stashQueues(stub) {
@@ -206,38 +225,50 @@ class GlobalQueue extends Queue {
206
225
  _running = false;
207
226
  _pendingNodes = [];
208
227
  _optimisticNodes = [];
228
+ _optimisticStores = new Set();
209
229
  static _update;
210
230
  static _dispose;
231
+ static _clearOptimisticStore = null;
211
232
  flush() {
212
233
  if (this._running) return;
213
234
  this._running = true;
214
235
  try {
215
236
  runHeap(dirtyQueue, GlobalQueue._update);
216
237
  if (activeTransition) {
217
- if (!transitionComplete(activeTransition)) {
238
+ const isComplete = transitionComplete(activeTransition);
239
+ if (!isComplete) {
218
240
  let t = activeTransition;
219
241
  runHeap(zombieQueue, GlobalQueue._update);
220
242
  this._pendingNodes = [];
243
+ this._optimisticNodes = [];
244
+ this._optimisticStores = new Set();
245
+ this.runOptimistic(EFFECT_RENDER);
246
+ this.runOptimistic(EFFECT_USER);
221
247
  this.stashQueues(activeTransition.queueStash);
222
248
  clock++;
223
249
  scheduled = false;
224
- runTransitionPending(activeTransition.pendingNodes, true);
250
+ runTransitionPending(activeTransition.pendingNodes);
225
251
  activeTransition = null;
226
- runOptimistic(t);
252
+ finalizePureQueue(null, true);
227
253
  return;
228
254
  }
229
- this._pendingNodes.push(...activeTransition.pendingNodes);
230
- this._optimisticNodes !== activeTransition.optimisticNodes &&
231
- this._optimisticNodes.push(...activeTransition.optimisticNodes);
255
+ this._pendingNodes !== activeTransition.pendingNodes &&
256
+ this._pendingNodes.push(...activeTransition.pendingNodes);
232
257
  this.restoreQueues(activeTransition.queueStash);
233
258
  transitions.delete(activeTransition);
259
+ const completingTransition = activeTransition;
234
260
  activeTransition = null;
235
- runTransitionPending(this._pendingNodes, false);
236
- } else if (transitions.size) runHeap(zombieQueue, GlobalQueue._update);
237
- runOptimistic();
261
+ runTransitionPending(this._pendingNodes);
262
+ finalizePureQueue(completingTransition);
263
+ } else {
264
+ if (transitions.size) runHeap(zombieQueue, GlobalQueue._update);
265
+ finalizePureQueue();
266
+ }
238
267
  clock++;
239
- scheduled = false;
268
+ scheduled = dirtyQueue._max >= dirtyQueue._min;
269
+ this.runOptimistic(EFFECT_RENDER);
240
270
  this.run(EFFECT_RENDER);
271
+ this.runOptimistic(EFFECT_USER);
241
272
  this.run(EFFECT_USER);
242
273
  } finally {
243
274
  this._running = false;
@@ -246,7 +277,11 @@ class GlobalQueue extends Queue {
246
277
  notify(node, mask, flags) {
247
278
  if (mask & STATUS_PENDING) {
248
279
  if (flags & STATUS_PENDING) {
249
- if (activeTransition && !activeTransition.asyncNodes.includes(node._error.cause)) {
280
+ if (
281
+ activeTransition &&
282
+ node._error &&
283
+ !activeTransition.asyncNodes.includes(node._error.cause)
284
+ ) {
250
285
  activeTransition.asyncNodes.push(node._error.cause);
251
286
  schedule();
252
287
  }
@@ -255,18 +290,26 @@ class GlobalQueue extends Queue {
255
290
  }
256
291
  return false;
257
292
  }
258
- initTransition(node) {
259
- if (activeTransition && activeTransition.time === clock) return;
293
+ initTransition(transition) {
294
+ if (transition) transition = currentTransition(transition);
295
+ if (transition && transition === activeTransition) return;
296
+ if (!transition && activeTransition && activeTransition.time === clock) return;
260
297
  if (!activeTransition) {
261
- activeTransition = node?._transition ?? {
298
+ activeTransition = transition ?? {
262
299
  time: clock,
263
300
  pendingNodes: [],
264
301
  asyncNodes: [],
265
302
  optimisticNodes: [],
303
+ optimisticStores: new Set(),
266
304
  actions: [],
267
305
  queueStash: { _queues: [[], []], _children: [] },
268
306
  done: false
269
307
  };
308
+ } else if (transition) {
309
+ activeTransition.done = transition;
310
+ transition.actions.push(...activeTransition.actions);
311
+ transitions.delete(activeTransition);
312
+ activeTransition = transition;
270
313
  }
271
314
  transitions.add(activeTransition);
272
315
  activeTransition.time = clock;
@@ -275,65 +318,86 @@ class GlobalQueue extends Queue {
275
318
  n._transition = activeTransition;
276
319
  activeTransition.pendingNodes.push(n);
277
320
  }
321
+ this._pendingNodes = activeTransition.pendingNodes;
278
322
  for (let i = 0; i < this._optimisticNodes.length; i++) {
279
- const n = this._optimisticNodes[i];
280
- n._transition = activeTransition;
281
- activeTransition.optimisticNodes.push(n);
323
+ const node = this._optimisticNodes[i];
324
+ node._transition = activeTransition;
325
+ activeTransition.optimisticNodes.push(node);
282
326
  }
283
- this._pendingNodes = activeTransition.pendingNodes;
284
327
  this._optimisticNodes = activeTransition.optimisticNodes;
328
+ for (const store of this._optimisticStores) {
329
+ activeTransition.optimisticStores.add(store);
330
+ }
331
+ this._optimisticStores = activeTransition.optimisticStores;
285
332
  }
286
333
  }
287
- function notifySubs(node) {
334
+ function insertSubs(node, optimistic = false) {
288
335
  for (let s = node._subs; s !== null; s = s._nextSub) {
336
+ if (optimistic) s._sub._flags |= REACTIVE_OPTIMISTIC_DIRTY;
337
+ const sub = s._sub;
338
+ if (sub._type === EFFECT_TRACKED) {
339
+ if (!sub._modified) {
340
+ sub._modified = true;
341
+ sub._queue.enqueue(EFFECT_USER, sub._run);
342
+ }
343
+ continue;
344
+ }
289
345
  const queue = s._sub._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue;
290
346
  if (queue._min > s._sub._height) queue._min = s._sub._height;
291
347
  insertIntoHeap(s._sub, queue);
292
348
  }
293
349
  }
294
- function runOptimistic(activeTransition = null) {
295
- let resolvePending = !activeTransition;
296
- const optimisticNodes = globalQueue._optimisticNodes;
297
- optimisticRun = true;
298
- for (let i = 0; i < optimisticNodes.length; i++) {
299
- const n = optimisticNodes[i];
300
- if (
301
- !activeTransition &&
302
- (!n._transition || n._transition.done) &&
303
- n._pendingValue !== NOT_PENDING
304
- ) {
305
- n._value = n._pendingValue;
306
- n._pendingValue = NOT_PENDING;
307
- }
308
- n._transition = activeTransition;
309
- notifySubs(n);
310
- }
311
- globalQueue._optimisticNodes = [];
350
+ function finalizePureQueue(completingTransition = null, incomplete = false) {
351
+ let resolvePending = !incomplete;
312
352
  if (dirtyQueue._max >= dirtyQueue._min) {
313
353
  resolvePending = true;
314
354
  runHeap(dirtyQueue, GlobalQueue._update);
315
355
  }
316
- optimisticRun = false;
317
- resolvePending && runPending(globalQueue._pendingNodes);
318
- }
319
- function runPending(pendingNodes) {
320
- for (let i = 0; i < pendingNodes.length; i++) {
321
- const n = pendingNodes[i];
322
- if (n._pendingValue !== NOT_PENDING) {
323
- n._value = n._pendingValue;
356
+ if (resolvePending) {
357
+ const pendingNodes = globalQueue._pendingNodes;
358
+ for (let i = 0; i < pendingNodes.length; i++) {
359
+ const n = pendingNodes[i];
360
+ if (n._pendingValue !== NOT_PENDING) {
361
+ n._value = n._pendingValue;
362
+ n._pendingValue = NOT_PENDING;
363
+ if (n._type && n._type !== EFFECT_TRACKED) n._modified = true;
364
+ }
365
+ if (n._fn) GlobalQueue._dispose(n, false, true);
366
+ }
367
+ pendingNodes.length = 0;
368
+ const optimisticNodes = completingTransition
369
+ ? completingTransition.optimisticNodes
370
+ : globalQueue._optimisticNodes;
371
+ for (let i = 0; i < optimisticNodes.length; i++) {
372
+ const n = optimisticNodes[i];
373
+ const original = n._pendingValue;
374
+ if (original !== NOT_PENDING && n._value !== original) {
375
+ n._value = original;
376
+ insertSubs(n, true);
377
+ }
324
378
  n._pendingValue = NOT_PENDING;
325
- if (n._type) n._modified = true;
379
+ n._transition = null;
380
+ }
381
+ optimisticNodes.length = 0;
382
+ const optimisticStores = completingTransition
383
+ ? completingTransition.optimisticStores
384
+ : globalQueue._optimisticStores;
385
+ if (GlobalQueue._clearOptimisticStore && optimisticStores.size) {
386
+ for (const store of optimisticStores) {
387
+ GlobalQueue._clearOptimisticStore(store);
388
+ }
389
+ optimisticStores.clear();
390
+ schedule();
326
391
  }
327
- if (n._fn) GlobalQueue._dispose(n, false, true);
328
392
  }
329
- pendingNodes.length = 0;
330
393
  }
331
- function runTransitionPending(pendingNodes, value) {
332
- const p = pendingNodes.slice();
333
- for (let i = 0; i < p.length; i++) {
334
- const n = p[i];
335
- n._transition = activeTransition;
336
- if (n._pendingCheck) n._pendingCheck._set(value);
394
+ function trackOptimisticStore(store) {
395
+ globalQueue._optimisticStores.add(store);
396
+ schedule();
397
+ }
398
+ function runTransitionPending(pendingNodes) {
399
+ for (let i = 0; i < pendingNodes.length; i++) {
400
+ pendingNodes[i]._transition = activeTransition;
337
401
  }
338
402
  }
339
403
  const globalQueue = new GlobalQueue();
@@ -360,54 +424,81 @@ function transitionComplete(transition) {
360
424
  done && (transition.done = true);
361
425
  return done;
362
426
  }
427
+ function currentTransition(transition) {
428
+ while (transition.done && typeof transition.done === "object") transition = transition.done;
429
+ return transition;
430
+ }
363
431
  function runInTransition(transition, fn) {
364
432
  const prevTransition = activeTransition;
365
433
  try {
366
- activeTransition = transition;
434
+ activeTransition = currentTransition(transition);
367
435
  return fn();
368
436
  } finally {
369
437
  activeTransition = prevTransition;
370
438
  }
371
439
  }
440
+ function restoreTransition(transition, fn) {
441
+ globalQueue.initTransition(transition);
442
+ const result = fn();
443
+ flush();
444
+ return result;
445
+ }
372
446
  function action(genFn) {
373
- return (...args) => {
374
- const iterator = genFn(...args);
375
- globalQueue.initTransition();
376
- let ctx = activeTransition;
377
- ctx.actions.push(iterator);
378
- const step = input => {
379
- let nextValue = iterator.next(input);
380
- if (nextValue instanceof Promise) return nextValue.then(process);
381
- process(nextValue);
382
- };
383
- const process = result => {
384
- if (result.done) {
385
- ctx.actions.splice(ctx.actions.indexOf(iterator), 1);
447
+ return (...args) =>
448
+ new Promise((resolve, reject) => {
449
+ const it = genFn(...args);
450
+ globalQueue.initTransition();
451
+ let ctx = activeTransition;
452
+ ctx.actions.push(it);
453
+ const done = (v, e) => {
454
+ ctx = currentTransition(ctx);
455
+ const i = ctx.actions.indexOf(it);
456
+ if (i >= 0) ctx.actions.splice(i, 1);
386
457
  activeTransition = ctx;
387
458
  schedule();
388
- flush();
389
- return;
390
- }
391
- const yielded = result.value;
392
- if (yielded instanceof Promise) return yielded.then(step);
393
- runInTransition(ctx, () => step(yielded));
394
- };
395
- runInTransition(ctx, () => step());
396
- };
459
+ e ? reject(e) : resolve(v);
460
+ };
461
+ const step = (v, err) => {
462
+ let r;
463
+ try {
464
+ r = err ? it.throw(v) : it.next(v);
465
+ } catch (e) {
466
+ return done(undefined, e);
467
+ }
468
+ if (r instanceof Promise)
469
+ return void r.then(run, e => restoreTransition(ctx, () => step(e, true)));
470
+ run(r);
471
+ };
472
+ const run = r => {
473
+ if (r.done) return done(r.value);
474
+ if (r.value instanceof Promise)
475
+ return void r.value.then(
476
+ v => restoreTransition(ctx, () => step(v)),
477
+ e => restoreTransition(ctx, () => step(e, true))
478
+ );
479
+ restoreTransition(ctx, () => step(r.value));
480
+ };
481
+ step();
482
+ });
397
483
  }
398
484
  GlobalQueue._update = recompute;
399
485
  GlobalQueue._dispose = disposeChildren;
400
486
  let tracking = false;
401
487
  let stale = false;
402
- let pendingValueCheck = false;
403
- let pendingCheck = null;
404
488
  let refreshing = false;
489
+ let pendingCheckActive = false;
490
+ let foundPending = false;
491
+ let pendingReadActive = false;
405
492
  let context = null;
493
+ let leafEffectActive = false;
494
+ function setLeafEffectActive(v) {
495
+ leafEffectActive = v;
496
+ }
406
497
  function recompute(el, create = false) {
407
- const honoraryOptimistic = el._type && el._transition != activeTransition;
498
+ const isEffect = el._type;
408
499
  if (!create) {
409
- if (el._transition && activeTransition !== el._transition && !honoraryOptimistic)
410
- globalQueue.initTransition(el);
500
+ if (el._transition && (!isEffect || activeTransition) && activeTransition !== el._transition)
501
+ globalQueue.initTransition(el._transition);
411
502
  deleteFromHeap(el, el._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue);
412
503
  if (el._transition) disposeChildren(el);
413
504
  else {
@@ -418,35 +509,33 @@ function recompute(el, create = false) {
418
509
  el._firstChild = null;
419
510
  }
420
511
  }
512
+ const isOptimisticDirty = !!(el._flags & REACTIVE_OPTIMISTIC_DIRTY);
513
+ const hasOptimisticOverride = el._optimistic && el._pendingValue !== NOT_PENDING;
421
514
  const oldcontext = context;
422
515
  context = el;
423
516
  el._depsTail = null;
424
517
  el._flags = REACTIVE_RECOMPUTING_DEPS;
425
518
  el._time = clock;
426
- let value =
427
- el._pendingValue === NOT_PENDING || (el._optimistic && el._transition)
428
- ? el._value
429
- : el._pendingValue;
519
+ let value = el._pendingValue === NOT_PENDING ? el._value : el._pendingValue;
430
520
  let oldHeight = el._height;
431
- let prevStatusFlags = el._statusFlags;
432
- let prevError = el._error;
433
521
  let prevTracking = tracking;
434
- setStatusFlags(el, STATUS_NONE | (prevStatusFlags & STATUS_UNINITIALIZED));
522
+ let prevOptimisticRead = optimisticReadActive;
435
523
  tracking = true;
524
+ if (isOptimisticDirty) setOptimisticReadActive(true);
436
525
  try {
437
526
  value = handleAsync(el, el._fn(value));
438
- el._statusFlags &= ~STATUS_UNINITIALIZED;
527
+ clearStatus(el);
439
528
  } catch (e) {
440
529
  if (e instanceof NotReadyError) {
441
530
  if (e.cause !== el) link(e.cause, el);
442
- setStatusFlags(el, (prevStatusFlags & ~STATUS_ERROR) | STATUS_PENDING, e);
443
- } else setStatusFlags(el, STATUS_ERROR, e);
531
+ notifyStatus(el, STATUS_PENDING, e);
532
+ } else notifyStatus(el, STATUS_ERROR, e);
444
533
  } finally {
445
534
  tracking = prevTracking;
535
+ el._flags = REACTIVE_NONE;
536
+ context = oldcontext;
446
537
  }
447
- el._flags = REACTIVE_NONE;
448
- context = oldcontext;
449
- if (!(el._statusFlags & STATUS_PENDING)) {
538
+ if (!el._error) {
450
539
  const depsTail = el._depsTail;
451
540
  let toRemove = depsTail !== null ? depsTail._nextDep : el._deps;
452
541
  if (toRemove !== null) {
@@ -456,83 +545,158 @@ function recompute(el, create = false) {
456
545
  if (depsTail !== null) depsTail._nextDep = null;
457
546
  else el._deps = null;
458
547
  }
459
- }
460
- const valueChanged =
461
- !el._equals ||
462
- !el._equals(
463
- el._pendingValue === NOT_PENDING || (el._optimistic && el._transition) || honoraryOptimistic
464
- ? el._value
465
- : el._pendingValue,
466
- value
467
- );
468
- const statusFlagsChanged = el._statusFlags !== prevStatusFlags || el._error !== prevError;
469
- el._notifyQueue?.(statusFlagsChanged, prevStatusFlags);
470
- if (valueChanged || statusFlagsChanged) {
548
+ const valueChanged =
549
+ !el._equals ||
550
+ !el._equals(el._pendingValue === NOT_PENDING ? el._value : el._pendingValue, value);
471
551
  if (valueChanged) {
472
- if (create || el._optimistic || honoraryOptimistic) el._value = value;
552
+ if (create || (isEffect && activeTransition !== el._transition) || isOptimisticDirty)
553
+ el._value = value;
473
554
  else el._pendingValue = value;
474
- if (el._pendingSignal) setSignal(el._pendingSignal, value);
475
- }
476
- (!el._optimistic || optimisticRun) && notifySubs(el);
477
- } else if (el._height != oldHeight) {
478
- for (let s = el._subs; s !== null; s = s._nextSub) {
479
- insertIntoHeapHeight(s._sub, s._sub._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue);
555
+ insertSubs(el, isOptimisticDirty || hasOptimisticOverride);
556
+ } else if (el._height != oldHeight) {
557
+ for (let s = el._subs; s !== null; s = s._nextSub) {
558
+ insertIntoHeapHeight(s._sub, s._sub._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue);
559
+ }
480
560
  }
481
561
  }
482
- el._optimistic && !optimisticRun && globalQueue._optimisticNodes.push(el);
562
+ setOptimisticReadActive(prevOptimisticRead);
483
563
  (!create || el._statusFlags & STATUS_PENDING) &&
484
564
  !el._transition &&
485
565
  globalQueue._pendingNodes.push(el);
486
- el._transition && honoraryOptimistic && runInTransition(el._transition, () => recompute(el));
566
+ el._transition &&
567
+ isEffect &&
568
+ activeTransition !== el._transition &&
569
+ runInTransition(el._transition, () => recompute(el));
487
570
  }
488
571
  function handleAsync(el, result, setter) {
489
572
  const isObject = typeof result === "object" && result !== null;
490
- const isPromise = isObject && result instanceof Promise;
491
573
  const iterator = isObject && untrack(() => result[Symbol.asyncIterator]);
492
- if (!isPromise && !iterator) {
574
+ const isThenable = !iterator && isObject && untrack(() => typeof result.then === "function");
575
+ if (!isThenable && !iterator) {
493
576
  el._inFlight = null;
494
577
  return result;
495
578
  }
496
579
  el._inFlight = result;
497
- if (isPromise) {
498
- result
499
- .then(value => {
500
- if (el._inFlight !== result) return;
501
- globalQueue.initTransition(el);
502
- setter?.(value) ?? setSignal(el, () => value);
503
- flush();
504
- })
505
- .catch(e => {
506
- if (el._inFlight !== result) return;
507
- globalQueue.initTransition(el);
508
- setStatusFlags(el, STATUS_ERROR, e);
509
- el._time = clock;
510
- notifySubs(el);
511
- schedule();
512
- flush();
513
- });
580
+ let syncValue;
581
+ const handleError = error => {
582
+ if (el._inFlight !== result) return;
583
+ globalQueue.initTransition(el._transition);
584
+ if (error instanceof NotReadyError) {
585
+ if (error.cause !== el) link(error.cause, el);
586
+ notifyStatus(el, STATUS_PENDING, error);
587
+ } else notifyStatus(el, STATUS_ERROR, error);
588
+ el._time = clock;
589
+ };
590
+ const asyncWrite = (value, then) => {
591
+ if (el._inFlight !== result) return;
592
+ globalQueue.initTransition(el._transition);
593
+ clearStatus(el);
594
+ if (setter) setter(value);
595
+ else if (el._optimistic) {
596
+ const hasOverride = el._pendingValue !== NOT_PENDING;
597
+ if (el._fn) el._pendingValue = value;
598
+ if (!hasOverride) {
599
+ el._value = value;
600
+ insertSubs(el);
601
+ }
602
+ el._time = clock;
603
+ schedule();
604
+ } else setSignal(el, () => value);
605
+ flush();
606
+ then?.();
607
+ };
608
+ if (isThenable) {
609
+ let resolved = false,
610
+ isSync = true;
611
+ result.then(
612
+ v => {
613
+ if (isSync) {
614
+ syncValue = v;
615
+ resolved = true;
616
+ } else asyncWrite(v);
617
+ },
618
+ e => {
619
+ if (!isSync) handleError(e);
620
+ }
621
+ );
622
+ isSync = false;
623
+ if (!resolved) {
624
+ globalQueue.initTransition(el._transition);
625
+ throw new NotReadyError(context);
626
+ }
627
+ }
628
+ if (iterator) {
629
+ const it = result[Symbol.asyncIterator]();
630
+ let hadSyncValue = false;
631
+ const iterate = () => {
632
+ let syncResult,
633
+ resolved = false,
634
+ isSync = true;
635
+ it.next().then(
636
+ r => {
637
+ if (isSync) {
638
+ syncResult = r;
639
+ resolved = true;
640
+ } else if (!r.done) asyncWrite(r.value, iterate);
641
+ },
642
+ e => {
643
+ if (!isSync) handleError(e);
644
+ }
645
+ );
646
+ isSync = false;
647
+ if (resolved && !syncResult.done) {
648
+ syncValue = syncResult.value;
649
+ hadSyncValue = true;
650
+ return iterate();
651
+ }
652
+ return resolved && syncResult.done;
653
+ };
654
+ const immediatelyDone = iterate();
655
+ if (!hadSyncValue && !immediatelyDone) {
656
+ globalQueue.initTransition(el._transition);
657
+ throw new NotReadyError(context);
658
+ }
659
+ }
660
+ return syncValue;
661
+ }
662
+ function clearStatus(el) {
663
+ el._statusFlags = STATUS_NONE;
664
+ el._error = null;
665
+ updatePendingSignal(el);
666
+ if (el._notifyStatus) {
667
+ el._notifyStatus();
514
668
  } else {
515
- (async () => {
516
- try {
517
- for await (let value of result) {
518
- if (el._inFlight !== result) return;
519
- globalQueue.initTransition(el);
520
- setter?.(value) ?? setSignal(el, () => value);
521
- flush();
669
+ if (!el._transition) {
670
+ for (let s = el._subs; s !== null; s = s._nextSub) {
671
+ if (s._sub._statusFlags & STATUS_PENDING) {
672
+ insertIntoHeap(s._sub, s._sub._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue);
522
673
  }
523
- } catch (error) {
524
- if (el._inFlight !== result) return;
525
- globalQueue.initTransition(el);
526
- setStatusFlags(el, STATUS_ERROR, error);
527
- el._time = clock;
528
- notifySubs(el);
529
- schedule();
530
- flush();
531
674
  }
532
- })();
675
+ }
676
+ schedule();
677
+ }
678
+ }
679
+ function notifyStatus(el, status, error) {
680
+ el._statusFlags = status | (el._statusFlags & STATUS_UNINITIALIZED);
681
+ el._error = error;
682
+ updatePendingSignal(el);
683
+ if (el._notifyStatus) return el._notifyStatus();
684
+ for (let s = el._subs; s !== null; s = s._nextSub) {
685
+ s._sub._time = clock;
686
+ if (s._sub._error !== error) {
687
+ !s._sub._transition && globalQueue._pendingNodes.push(s._sub);
688
+ notifyStatus(s._sub, status, error);
689
+ }
690
+ }
691
+ for (let child = el._child; child !== null; child = child._nextChild) {
692
+ for (let s = child._subs; s !== null; s = s._nextSub) {
693
+ s._sub._time = clock;
694
+ if (s._sub._error !== error) {
695
+ !s._sub._transition && globalQueue._pendingNodes.push(s._sub);
696
+ notifyStatus(s._sub, status, error);
697
+ }
698
+ }
533
699
  }
534
- globalQueue.initTransition(el);
535
- throw new NotReadyError(context);
536
700
  }
537
701
  function updateIfNecessary(el) {
538
702
  if (el._flags & REACTIVE_CHECK) {
@@ -614,10 +778,6 @@ function isValidLink(checkLink, sub) {
614
778
  }
615
779
  return false;
616
780
  }
617
- function setStatusFlags(signal, flags, error = null) {
618
- signal._statusFlags = flags;
619
- signal._error = error;
620
- }
621
781
  function markDisposal(el) {
622
782
  let child = el._firstChild;
623
783
  while (child) {
@@ -720,10 +880,12 @@ function computed(fn, initialValue, options) {
720
880
  _inFlight: null,
721
881
  _transition: null
722
882
  };
723
- if (options?._internal) Object.assign(self, options._internal);
724
883
  self._name = options?.name ?? "computed";
725
884
  self._prevHeap = self;
726
885
  const parent = context?._root ? context._parentComputed : context;
886
+ if (leafEffectActive && context) {
887
+ throw new Error("Cannot create reactive primitives inside createTrackedEffect");
888
+ }
727
889
  if (context) {
728
890
  const lastChild = context._firstChild;
729
891
  if (lastChild === null) {
@@ -739,14 +901,12 @@ function computed(fn, initialValue, options) {
739
901
  }
740
902
  function signal(v, options, firewall = null) {
741
903
  const s = {
742
- id: options?.id ?? (context?.id != null ? getNextChildId(context) : undefined),
743
904
  _equals: options?.equals != null ? options.equals : isEqual,
744
905
  _pureWrite: !!options?.pureWrite,
745
906
  _unobserved: options?.unobserved,
746
907
  _value: v,
747
908
  _subs: null,
748
909
  _subsTail: null,
749
- _statusFlags: STATUS_NONE,
750
910
  _time: clock,
751
911
  _firewall: firewall,
752
912
  _nextChild: firewall?._child || null,
@@ -756,6 +916,40 @@ function signal(v, options, firewall = null) {
756
916
  firewall && (firewall._child = s);
757
917
  return s;
758
918
  }
919
+ function optimisticSignal(v, options) {
920
+ const s = signal(v, options);
921
+ s._optimistic = true;
922
+ return s;
923
+ }
924
+ function optimisticComputed(fn, initialValue, options) {
925
+ const c = computed(fn, initialValue, options);
926
+ c._optimistic = true;
927
+ return c;
928
+ }
929
+ function getPendingSignal(el) {
930
+ if (!el._pendingSignal) {
931
+ el._pendingSignal = optimisticSignal(false, { pureWrite: true });
932
+ if (computePendingState(el)) setSignal(el._pendingSignal, true);
933
+ }
934
+ return el._pendingSignal;
935
+ }
936
+ function computePendingState(el) {
937
+ if (el._pendingValue !== NOT_PENDING) return true;
938
+ const comp = el;
939
+ return !!(comp._statusFlags & STATUS_PENDING && !(comp._statusFlags & STATUS_UNINITIALIZED));
940
+ }
941
+ function getPendingValueComputed(el) {
942
+ if (!el._pendingValueComputed) {
943
+ const prevPending = pendingReadActive;
944
+ pendingReadActive = false;
945
+ el._pendingValueComputed = optimisticComputed(() => read(el));
946
+ pendingReadActive = prevPending;
947
+ }
948
+ return el._pendingValueComputed;
949
+ }
950
+ function updatePendingSignal(el) {
951
+ if (el._pendingSignal) setSignal(el._pendingSignal, computePendingState(el));
952
+ }
759
953
  function isEqual(a, b) {
760
954
  return a === b;
761
955
  }
@@ -769,10 +963,30 @@ function untrack(fn) {
769
963
  }
770
964
  }
771
965
  function read(el) {
966
+ if (pendingCheckActive) {
967
+ const target = el._firewall || el;
968
+ const pendingSig = getPendingSignal(target);
969
+ const prevCheck = pendingCheckActive;
970
+ pendingCheckActive = false;
971
+ if (read(pendingSig)) {
972
+ foundPending = true;
973
+ }
974
+ pendingCheckActive = prevCheck;
975
+ return el._value;
976
+ }
977
+ if (pendingReadActive) {
978
+ const pendingComputed = getPendingValueComputed(el);
979
+ const prevPending = pendingReadActive;
980
+ pendingReadActive = false;
981
+ const value = read(pendingComputed);
982
+ pendingReadActive = prevPending;
983
+ if (pendingComputed._statusFlags & STATUS_PENDING) return el._value;
984
+ return value;
985
+ }
772
986
  let c = context;
773
987
  if (c?._root) c = c._parentComputed;
774
988
  if (refreshing && el._fn) recompute(el);
775
- if (c && tracking && !pendingCheck && !pendingValueCheck) {
989
+ if (c && tracking) {
776
990
  if (el._fn && el._flags & REACTIVE_DISPOSED) recompute(el);
777
991
  link(el, c);
778
992
  const owner = el._firewall || el;
@@ -789,88 +1003,67 @@ function read(el) {
789
1003
  }
790
1004
  }
791
1005
  }
792
- if (pendingValueCheck) {
793
- if (!el._pendingSignal) {
794
- el._pendingSignal = signal(el._value);
795
- el._pendingSignal._optimistic = true;
796
- }
797
- pendingValueCheck = false;
798
- try {
799
- return read(el._pendingSignal);
800
- } finally {
801
- pendingValueCheck = true;
802
- }
803
- }
804
1006
  const asyncCompute = el._firewall || el;
805
- if (pendingCheck) {
806
- if (!asyncCompute._pendingCheck) {
807
- asyncCompute._pendingCheck = signal(false);
808
- asyncCompute._pendingCheck._optimistic = true;
809
- asyncCompute._pendingCheck._set = v => setSignal(asyncCompute._pendingCheck, v);
810
- }
811
- const prev = pendingCheck;
812
- pendingCheck = null;
813
- prev._value = read(asyncCompute._pendingCheck) || prev._value;
814
- pendingCheck = prev;
815
- }
816
- if (!pendingCheck && asyncCompute._statusFlags & STATUS_PENDING) {
817
- if ((c && !stale) || asyncCompute._statusFlags & STATUS_UNINITIALIZED || el._firewall)
818
- throw asyncCompute._error;
819
- else if (c && stale) {
820
- setStatusFlags(c, c._statusFlags | STATUS_PENDING, asyncCompute._error);
821
- }
822
- }
823
- if (el._statusFlags & STATUS_ERROR) {
1007
+ if (
1008
+ c &&
1009
+ !optimisticReadActive &&
1010
+ asyncCompute._statusFlags & STATUS_PENDING &&
1011
+ !(stale && asyncCompute._transition && activeTransition !== asyncCompute._transition)
1012
+ )
1013
+ throw asyncCompute._error;
1014
+ if (el._fn && el._statusFlags & STATUS_ERROR) {
824
1015
  if (el._time < clock) {
825
1016
  recompute(el, true);
826
1017
  return read(el);
827
- } else {
828
- throw el._error;
829
- }
1018
+ } else throw el._error;
830
1019
  }
831
1020
  return !c ||
832
- el._optimistic ||
1021
+ optimisticReadActive ||
833
1022
  el._pendingValue === NOT_PENDING ||
834
- (stale &&
835
- !pendingCheck &&
836
- (c._optimistic || (el._transition && activeTransition !== el._transition)))
1023
+ (stale && el._transition && activeTransition !== el._transition)
837
1024
  ? el._value
838
1025
  : el._pendingValue;
839
1026
  }
840
1027
  function setSignal(el, v) {
841
1028
  if (!el._pureWrite && context && el._firewall !== context)
842
1029
  console.warn("A Signal was written to in an owned scope.");
843
- if (typeof v === "function") {
844
- v = v(
845
- el._pendingValue === NOT_PENDING || (el._optimistic && el._transition)
846
- ? el._value
847
- : el._pendingValue
848
- );
1030
+ if (el._transition && activeTransition !== el._transition)
1031
+ globalQueue.initTransition(el._transition);
1032
+ const isOptimistic = el._optimistic && !projectionWriteActive;
1033
+ const currentValue = isOptimistic
1034
+ ? el._value
1035
+ : el._pendingValue === NOT_PENDING
1036
+ ? el._value
1037
+ : el._pendingValue;
1038
+ if (typeof v === "function") v = v(currentValue);
1039
+ const valueChanged = !el._equals || !el._equals(currentValue, v);
1040
+ if (!valueChanged) return v;
1041
+ if (isOptimistic) {
1042
+ const isFirstWrite = el._pendingValue === NOT_PENDING;
1043
+ if (el._transition && !isFirstWrite) {
1044
+ globalQueue.initTransition(el._transition);
1045
+ }
1046
+ if (isFirstWrite) {
1047
+ el._pendingValue = el._value;
1048
+ globalQueue._optimisticNodes.push(el);
1049
+ }
1050
+ el._value = v;
1051
+ } else {
1052
+ if (el._pendingValue === NOT_PENDING) globalQueue._pendingNodes.push(el);
1053
+ el._pendingValue = v;
849
1054
  }
850
- const valueChanged =
851
- !el._equals ||
852
- !el._equals(
853
- el._pendingValue === NOT_PENDING || (el._optimistic && el._transition)
854
- ? el._value
855
- : el._pendingValue,
856
- v
857
- );
858
- if (!valueChanged && !el._statusFlags) return v;
859
- if (valueChanged) {
860
- if (el._optimistic) el._value = v;
861
- else {
862
- if (el._pendingValue === NOT_PENDING) globalQueue._pendingNodes.push(el);
863
- el._pendingValue = v;
864
- }
865
- if (el._pendingSignal) setSignal(el._pendingSignal, v);
1055
+ updatePendingSignal(el);
1056
+ if (el._pendingValueComputed) {
1057
+ setSignal(el._pendingValueComputed, v);
866
1058
  }
867
- setStatusFlags(el, STATUS_NONE);
868
1059
  el._time = clock;
869
- el._optimistic && !optimisticRun ? globalQueue._optimisticNodes.push(el) : notifySubs(el);
1060
+ insertSubs(el, isOptimistic);
870
1061
  schedule();
871
1062
  return v;
872
1063
  }
1064
+ const PENDING_OWNER = {};
873
1065
  function getObserver() {
1066
+ if (pendingCheckActive || pendingReadActive) return PENDING_OWNER;
874
1067
  return tracking ? context : null;
875
1068
  }
876
1069
  function getOwner() {
@@ -878,25 +1071,20 @@ function getOwner() {
878
1071
  }
879
1072
  function onCleanup(fn) {
880
1073
  if (!context) return fn;
881
- const node = context;
882
- if (!node._disposal) {
883
- node._disposal = fn;
884
- } else if (Array.isArray(node._disposal)) {
885
- node._disposal.push(fn);
886
- } else {
887
- node._disposal = [node._disposal, fn];
888
- }
1074
+ if (!context._disposal) context._disposal = fn;
1075
+ else if (Array.isArray(context._disposal)) context._disposal.push(fn);
1076
+ else context._disposal = [context._disposal, fn];
889
1077
  return fn;
890
1078
  }
891
1079
  function createOwner(options) {
892
1080
  const parent = context;
893
1081
  const owner = {
1082
+ id: options?.id ?? (parent?.id != null ? getNextChildId(parent) : undefined),
894
1083
  _root: true,
895
1084
  _parentComputed: parent?._root ? parent._parentComputed : parent,
896
1085
  _firstChild: null,
897
1086
  _nextSibling: null,
898
1087
  _disposal: null,
899
- id: options?.id ?? (parent?.id != null ? getNextChildId(parent) : undefined),
900
1088
  _queue: parent?._queue ?? globalQueue,
901
1089
  _context: parent?._context || defaultContext,
902
1090
  _childCount: 0,
@@ -907,6 +1095,9 @@ function createOwner(options) {
907
1095
  disposeChildren(owner, self);
908
1096
  }
909
1097
  };
1098
+ if (leafEffectActive && parent) {
1099
+ throw new Error("Cannot create reactive primitives inside createTrackedEffect");
1100
+ }
910
1101
  if (parent) {
911
1102
  const lastChild = parent._firstChild;
912
1103
  if (lastChild === null) {
@@ -924,11 +1115,14 @@ function createRoot(init, options) {
924
1115
  }
925
1116
  function runWithOwner(owner, fn) {
926
1117
  const oldContext = context;
1118
+ const prevTracking = tracking;
927
1119
  context = owner;
1120
+ tracking = false;
928
1121
  try {
929
1122
  return fn();
930
1123
  } finally {
931
1124
  context = oldContext;
1125
+ tracking = prevTracking;
932
1126
  }
933
1127
  }
934
1128
  function staleValues(fn, set = true) {
@@ -941,25 +1135,25 @@ function staleValues(fn, set = true) {
941
1135
  }
942
1136
  }
943
1137
  function pending(fn) {
944
- const prevLatest = pendingValueCheck;
945
- pendingValueCheck = true;
1138
+ const prevPending = pendingReadActive;
1139
+ pendingReadActive = true;
946
1140
  try {
947
- return staleValues(fn, false);
1141
+ return fn();
948
1142
  } finally {
949
- pendingValueCheck = prevLatest;
1143
+ pendingReadActive = prevPending;
950
1144
  }
951
1145
  }
952
1146
  function isPending(fn) {
953
- const current = pendingCheck;
954
- pendingCheck = { _value: false };
1147
+ const prevPendingCheck = pendingCheckActive;
1148
+ const prevFoundPending = foundPending;
1149
+ pendingCheckActive = true;
1150
+ foundPending = false;
955
1151
  try {
956
- staleValues(fn);
957
- return pendingCheck._value;
958
- } catch (err) {
959
- if (!(err instanceof NotReadyError)) return false;
960
- throw err;
1152
+ fn();
1153
+ return foundPending;
961
1154
  } finally {
962
- pendingCheck = current;
1155
+ pendingCheckActive = prevPendingCheck;
1156
+ foundPending = prevFoundPending;
963
1157
  }
964
1158
  }
965
1159
  function refresh(fn) {
@@ -975,7 +1169,6 @@ function refresh(fn) {
975
1169
  refreshing = prevRefreshing;
976
1170
  if (!prevRefreshing) {
977
1171
  schedule();
978
- flush();
979
1172
  }
980
1173
  }
981
1174
  }
@@ -1012,54 +1205,50 @@ function isUndefined(value) {
1012
1205
  }
1013
1206
  function effect(compute, effect, error, initialValue, options) {
1014
1207
  let initialized = false;
1015
- const node = computed(compute, initialValue, {
1016
- ...options,
1017
- _internal: {
1018
- _modified: true,
1019
- _prevValue: initialValue,
1020
- _effectFn: effect,
1021
- _errorFn: error,
1022
- _cleanup: undefined,
1023
- _type: options?.render ? EFFECT_RENDER : EFFECT_USER,
1024
- _notifyQueue(statusFlagsChanged, prevStatusFlags) {
1025
- if (initialized) {
1026
- const errorChanged =
1027
- this._statusFlags && this._statusFlags === prevStatusFlags && statusFlagsChanged;
1028
- this._modified =
1029
- !(this._statusFlags & STATUS_ERROR) &&
1030
- !(this._statusFlags & STATUS_PENDING & ~prevStatusFlags) &&
1031
- !errorChanged;
1032
- if (this._modified) this._queue.enqueue(this._type, runEffect.bind(this));
1033
- }
1034
- if (this._statusFlags & STATUS_ERROR) {
1035
- let error = this._error;
1036
- this._queue.notify(this, STATUS_PENDING, 0);
1037
- if (this._type === EFFECT_USER) {
1038
- try {
1039
- return this._errorFn
1040
- ? this._errorFn(error, () => {
1041
- this._cleanup?.();
1042
- this._cleanup = undefined;
1043
- })
1044
- : console.error(error);
1045
- } catch (e) {
1046
- error = e;
1047
- }
1048
- }
1049
- if (!this._queue.notify(this, STATUS_ERROR, STATUS_ERROR)) throw error;
1050
- } else if (this._type === EFFECT_RENDER) {
1051
- this._queue.notify(this, STATUS_PENDING | STATUS_ERROR, this._statusFlags);
1208
+ const node = computed(
1209
+ options?.render ? p => staleValues(() => compute(p)) : compute,
1210
+ initialValue,
1211
+ {
1212
+ ...options,
1213
+ equals: () => {
1214
+ node._modified = !node._error;
1215
+ if (initialized) node._queue.enqueue(node._type, runEffect.bind(node));
1216
+ return false;
1217
+ },
1218
+ lazy: true
1219
+ }
1220
+ );
1221
+ node._prevValue = initialValue;
1222
+ node._effectFn = effect;
1223
+ node._errorFn = error;
1224
+ node._cleanup = undefined;
1225
+ node._type = options?.render ? EFFECT_RENDER : EFFECT_USER;
1226
+ node._notifyStatus = () => {
1227
+ if (node._statusFlags & STATUS_ERROR) {
1228
+ let error = node._error;
1229
+ node._queue.notify(node, STATUS_PENDING, 0);
1230
+ if (node._type === EFFECT_USER) {
1231
+ try {
1232
+ return node._errorFn
1233
+ ? node._errorFn(error, () => {
1234
+ node._cleanup?.();
1235
+ node._cleanup = undefined;
1236
+ })
1237
+ : console.error(error);
1238
+ } catch (e) {
1239
+ error = e;
1052
1240
  }
1053
1241
  }
1054
- }
1055
- });
1056
- initialized = true;
1057
- if (node._type === EFFECT_RENDER) node._fn = p => staleValues(() => compute(p));
1242
+ if (!node._queue.notify(node, STATUS_ERROR, STATUS_ERROR)) throw error;
1243
+ } else if (node._type === EFFECT_RENDER)
1244
+ node._queue.notify(node, STATUS_PENDING | STATUS_ERROR, node._statusFlags);
1245
+ };
1246
+ recompute(node, true);
1058
1247
  !options?.defer &&
1059
- !(node._statusFlags & (STATUS_ERROR | STATUS_PENDING)) &&
1060
1248
  (node._type === EFFECT_USER
1061
1249
  ? node._queue.enqueue(node._type, runEffect.bind(node))
1062
1250
  : runEffect.call(node));
1251
+ initialized = true;
1063
1252
  onCleanup(() => node._cleanup?.());
1064
1253
  if (!node._parent)
1065
1254
  console.warn("Effects created outside a reactive context will never be disposed");
@@ -1077,6 +1266,35 @@ function runEffect() {
1077
1266
  this._modified = false;
1078
1267
  }
1079
1268
  }
1269
+ function trackedEffect(fn, options) {
1270
+ const run = () => {
1271
+ if (!node._modified || node._flags & REACTIVE_DISPOSED) return;
1272
+ node._modified = false;
1273
+ recompute(node);
1274
+ };
1275
+ const node = computed(
1276
+ () => {
1277
+ setLeafEffectActive(true);
1278
+ try {
1279
+ node._cleanup?.();
1280
+ node._cleanup = undefined;
1281
+ node._cleanup = staleValues(fn) || undefined;
1282
+ } finally {
1283
+ setLeafEffectActive(false);
1284
+ }
1285
+ },
1286
+ undefined,
1287
+ { ...options, lazy: true, pureWrite: true }
1288
+ );
1289
+ node._cleanup = undefined;
1290
+ node._modified = true;
1291
+ node._type = EFFECT_TRACKED;
1292
+ node._run = run;
1293
+ node._queue.enqueue(EFFECT_USER, run);
1294
+ onCleanup(() => node._cleanup?.());
1295
+ if (!node._parent)
1296
+ console.warn("Effects created outside a reactive context will never be disposed");
1297
+ }
1080
1298
  function createSignal(first, second, third) {
1081
1299
  if (typeof first === "function") {
1082
1300
  const node = computed(first, second, third);
@@ -1090,18 +1308,20 @@ function createMemo(compute, value, options) {
1090
1308
  return read.bind(null, node);
1091
1309
  }
1092
1310
  function createEffect(compute, effectFn, value, options) {
1093
- void effect(compute, effectFn.effect || effectFn, effectFn.error, value, {
1311
+ effect(compute, effectFn.effect || effectFn, effectFn.error, value, {
1094
1312
  ...options,
1095
1313
  name: options?.name ?? "effect"
1096
1314
  });
1097
1315
  }
1098
1316
  function createRenderEffect(compute, effectFn, value, options) {
1099
- void effect(compute, effectFn, undefined, value, {
1317
+ effect(compute, effectFn, undefined, value, {
1100
1318
  render: true,
1101
1319
  ...{ ...options, name: options?.name ?? "effect" }
1102
1320
  });
1103
1321
  }
1104
- function createTrackedEffect(compute, options) {}
1322
+ function createTrackedEffect(compute, options) {
1323
+ trackedEffect(compute, { ...options, name: options?.name ?? "trackedEffect" });
1324
+ }
1105
1325
  function createReaction(effectFn, options) {
1106
1326
  let cleanup = undefined;
1107
1327
  onCleanup(() => cleanup?.());
@@ -1138,39 +1358,19 @@ function resolve(fn) {
1138
1358
  });
1139
1359
  }
1140
1360
  function createOptimistic(first, second, third) {
1141
- if (typeof first === "function") {
1142
- const node = computed(
1143
- prev => {
1144
- const n = getOwner();
1145
- const value = first(prev);
1146
- if (n._statusFlags & STATUS_UNINITIALIZED) return value;
1147
- n._pendingValue = value;
1148
- return prev;
1149
- },
1150
- second,
1151
- third
1152
- );
1153
- node._optimistic = true;
1154
- return [read.bind(null, node), setSignal.bind(null, node)];
1155
- }
1156
- const node = signal(first, second);
1157
- node._optimistic = true;
1158
- return [
1159
- read.bind(null, node),
1160
- v => {
1161
- node._pendingValue = first;
1162
- return setSignal(node, v);
1163
- }
1164
- ];
1361
+ const node =
1362
+ typeof first === "function"
1363
+ ? optimisticComputed(first, second, third)
1364
+ : optimisticSignal(first, second);
1365
+ return [read.bind(null, node), setSignal.bind(null, node)];
1165
1366
  }
1166
1367
  function onSettled(callback) {
1167
- let cleanup;
1168
- const o = getOwner();
1169
- if (o) onCleanup(() => cleanup?.());
1170
- globalQueue.enqueue(EFFECT_USER, () => {
1171
- cleanup = callback();
1172
- !o && cleanup?.();
1173
- });
1368
+ getOwner()
1369
+ ? createTrackedEffect(() => untrack(callback))
1370
+ : globalQueue.enqueue(EFFECT_USER, () => {
1371
+ const cleanup = callback();
1372
+ cleanup?.();
1373
+ });
1174
1374
  }
1175
1375
  function unwrap(value) {
1176
1376
  return value?.[$TARGET]?.[STORE_NODE] ?? value;
@@ -1339,12 +1539,12 @@ function createProjectionInternal(fn, initialValue = {}, options) {
1339
1539
  const owner = getOwner();
1340
1540
  storeSetter(new Proxy(wrappedStore, writeTraps), s => {
1341
1541
  const value = handleAsync(owner, fn(s), value => {
1342
- value !== wrappedStore &&
1542
+ value !== s &&
1343
1543
  value !== undefined &&
1344
1544
  storeSetter(wrappedStore, reconcile(value, options?.key || "id", options?.all));
1345
1545
  setSignal(owner, undefined);
1346
1546
  });
1347
- value !== wrappedStore &&
1547
+ value !== s &&
1348
1548
  value !== undefined &&
1349
1549
  reconcile(value, options?.key || "id", options?.all)(wrappedStore);
1350
1550
  });
@@ -1359,28 +1559,34 @@ const writeTraps = {
1359
1559
  get(_, prop) {
1360
1560
  let value;
1361
1561
  setWriteOverride(true);
1562
+ setProjectionWriteActive(true);
1362
1563
  try {
1363
1564
  value = _[prop];
1364
1565
  } finally {
1365
1566
  setWriteOverride(false);
1567
+ setProjectionWriteActive(false);
1366
1568
  }
1367
1569
  return typeof value === "object" && value !== null ? new Proxy(value, writeTraps) : value;
1368
1570
  },
1369
1571
  set(_, prop, value) {
1370
1572
  setWriteOverride(true);
1573
+ setProjectionWriteActive(true);
1371
1574
  try {
1372
1575
  _[prop] = value;
1373
1576
  } finally {
1374
1577
  setWriteOverride(false);
1578
+ setProjectionWriteActive(false);
1375
1579
  }
1376
1580
  return true;
1377
1581
  },
1378
1582
  deleteProperty(_, prop) {
1379
1583
  setWriteOverride(true);
1584
+ setProjectionWriteActive(true);
1380
1585
  try {
1381
1586
  delete _[prop];
1382
1587
  } finally {
1383
1588
  setWriteOverride(false);
1589
+ setProjectionWriteActive(false);
1384
1590
  }
1385
1591
  return true;
1386
1592
  }
@@ -1393,11 +1599,13 @@ const $TRACK = Symbol("STORE_TRACK"),
1393
1599
  const PARENTS = new WeakMap();
1394
1600
  const STORE_VALUE = "v",
1395
1601
  STORE_OVERRIDE = "o",
1602
+ STORE_OPTIMISTIC_OVERRIDE = "x",
1396
1603
  STORE_NODE = "n",
1397
1604
  STORE_HAS = "h",
1398
1605
  STORE_WRAP = "w",
1399
1606
  STORE_LOOKUP = "l",
1400
- STORE_FIREWALL = "f";
1607
+ STORE_FIREWALL = "f",
1608
+ STORE_OPTIMISTIC = "p";
1401
1609
  function createStoreProxy(value, traps = storeTraps, extend) {
1402
1610
  let newTarget;
1403
1611
  if (Array.isArray(value)) {
@@ -1429,9 +1637,9 @@ function getNodes(target, type) {
1429
1637
  if (!nodes) target[type] = nodes = Object.create(null);
1430
1638
  return nodes;
1431
1639
  }
1432
- function getNode(nodes, property, value, firewall, equals = isEqual) {
1640
+ function getNode(nodes, property, value, firewall, equals = isEqual, optimistic) {
1433
1641
  if (nodes[property]) return nodes[property];
1434
- return (nodes[property] = signal(
1642
+ const s = signal(
1435
1643
  value,
1436
1644
  {
1437
1645
  equals: equals,
@@ -1440,11 +1648,22 @@ function getNode(nodes, property, value, firewall, equals = isEqual) {
1440
1648
  }
1441
1649
  },
1442
1650
  firewall
1443
- ));
1651
+ );
1652
+ if (optimistic) s._optimistic = true;
1653
+ return (nodes[property] = s);
1444
1654
  }
1445
1655
  function trackSelf(target, symbol = $TRACK) {
1446
1656
  getObserver() &&
1447
- read(getNode(getNodes(target, STORE_NODE), symbol, undefined, target[STORE_FIREWALL], false));
1657
+ read(
1658
+ getNode(
1659
+ getNodes(target, STORE_NODE),
1660
+ symbol,
1661
+ undefined,
1662
+ target[STORE_FIREWALL],
1663
+ false,
1664
+ target[STORE_OPTIMISTIC]
1665
+ )
1666
+ );
1448
1667
  }
1449
1668
  function getKeys(source, override, enumerable = true) {
1450
1669
  const baseKeys = untrack(() => (enumerable ? Object.keys(source) : Reflect.ownKeys(source)));
@@ -1477,9 +1696,16 @@ const storeTraps = {
1477
1696
  }
1478
1697
  const nodes = getNodes(target, STORE_NODE);
1479
1698
  const tracked = nodes[property];
1480
- const overridden = target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE];
1699
+ const optOverridden =
1700
+ target[STORE_OPTIMISTIC_OVERRIDE] && property in target[STORE_OPTIMISTIC_OVERRIDE];
1701
+ const overridden =
1702
+ optOverridden || (target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]);
1481
1703
  const proxySource = !!target[STORE_VALUE][$TARGET];
1482
- const storeValue = overridden ? target[STORE_OVERRIDE] : target[STORE_VALUE];
1704
+ const storeValue = optOverridden
1705
+ ? target[STORE_OPTIMISTIC_OVERRIDE]
1706
+ : target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1707
+ ? target[STORE_OVERRIDE]
1708
+ : target[STORE_VALUE];
1483
1709
  if (!tracked) {
1484
1710
  const desc = Object.getOwnPropertyDescriptor(storeValue, property);
1485
1711
  if (desc && desc.get) return desc.get.call(receiver);
@@ -1488,7 +1714,9 @@ const storeTraps = {
1488
1714
  let value =
1489
1715
  tracked && (overridden || !proxySource)
1490
1716
  ? tracked._pendingValue !== NOT_PENDING
1491
- ? tracked._pendingValue
1717
+ ? tracked._optimistic
1718
+ ? tracked._value
1719
+ : tracked._pendingValue
1492
1720
  : tracked._value
1493
1721
  : storeValue[property];
1494
1722
  value === $DELETED && (value = undefined);
@@ -1517,7 +1745,9 @@ const storeTraps = {
1517
1745
  nodes,
1518
1746
  property,
1519
1747
  isWrappable(value) ? wrap(value, target) : value,
1520
- target[STORE_FIREWALL]
1748
+ target[STORE_FIREWALL],
1749
+ isEqual,
1750
+ target[STORE_OPTIMISTIC]
1521
1751
  )
1522
1752
  );
1523
1753
  }
@@ -1527,30 +1757,53 @@ const storeTraps = {
1527
1757
  has(target, property) {
1528
1758
  if (property === $PROXY || property === $TRACK || property === "__proto__") return true;
1529
1759
  const has =
1530
- target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1531
- ? target[STORE_OVERRIDE][property] !== $DELETED
1532
- : property in target[STORE_VALUE];
1760
+ target[STORE_OPTIMISTIC_OVERRIDE] && property in target[STORE_OPTIMISTIC_OVERRIDE]
1761
+ ? target[STORE_OPTIMISTIC_OVERRIDE][property] !== $DELETED
1762
+ : target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1763
+ ? target[STORE_OVERRIDE][property] !== $DELETED
1764
+ : property in target[STORE_VALUE];
1533
1765
  getObserver() &&
1534
- read(getNode(getNodes(target, STORE_HAS), property, has, target[STORE_FIREWALL]));
1766
+ read(
1767
+ getNode(
1768
+ getNodes(target, STORE_HAS),
1769
+ property,
1770
+ has,
1771
+ target[STORE_FIREWALL],
1772
+ isEqual,
1773
+ target[STORE_OPTIMISTIC]
1774
+ )
1775
+ );
1535
1776
  return has;
1536
1777
  },
1537
1778
  set(target, property, rawValue) {
1538
1779
  const store = target[$PROXY];
1539
1780
  if (writeOnly(store)) {
1781
+ if (target[STORE_OPTIMISTIC]) {
1782
+ const firewall = target[STORE_FIREWALL];
1783
+ if (firewall?._transition) {
1784
+ globalQueue.initTransition(firewall._transition);
1785
+ }
1786
+ }
1540
1787
  untrack(() => {
1541
1788
  const state = target[STORE_VALUE];
1542
1789
  const base = state[property];
1790
+ const useOptimistic = target[STORE_OPTIMISTIC] && !projectionWriteActive;
1791
+ const overrideKey = useOptimistic ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
1792
+ if (useOptimistic) trackOptimisticStore(store);
1543
1793
  const prev =
1544
- target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1545
- ? target[STORE_OVERRIDE][property]
1546
- : base;
1794
+ target[STORE_OPTIMISTIC_OVERRIDE] && property in target[STORE_OPTIMISTIC_OVERRIDE]
1795
+ ? target[STORE_OPTIMISTIC_OVERRIDE][property]
1796
+ : target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1797
+ ? target[STORE_OVERRIDE][property]
1798
+ : base;
1547
1799
  const value = rawValue?.[$TARGET]?.[STORE_VALUE] ?? rawValue;
1548
1800
  if (prev === value) return true;
1549
- const len = target[STORE_OVERRIDE]?.length || state.length;
1550
- if (value !== undefined && value === base) delete target[STORE_OVERRIDE][property];
1551
- else
1552
- (target[STORE_OVERRIDE] || (target[STORE_OVERRIDE] = Object.create(null)))[property] =
1553
- value;
1801
+ const len =
1802
+ target[STORE_OPTIMISTIC_OVERRIDE]?.length ||
1803
+ target[STORE_OVERRIDE]?.length ||
1804
+ state.length;
1805
+ if (value !== undefined && value === base) delete target[overrideKey][property];
1806
+ else (target[overrideKey] || (target[overrideKey] = Object.create(null)))[property] = value;
1554
1807
  const wrappable = isWrappable(value);
1555
1808
  if (isWrappable(prev)) {
1556
1809
  const parents = PARENTS.get(prev);
@@ -1562,8 +1815,12 @@ const storeTraps = {
1562
1815
  nodes[property] &&
1563
1816
  setSignal(nodes[property], () => (wrappable ? wrap(value, target) : value));
1564
1817
  if (Array.isArray(state)) {
1565
- const index = parseInt(property) + 1;
1566
- if (index > len) nodes.length && setSignal(nodes.length, index);
1818
+ if (property === "length") {
1819
+ nodes.length && setSignal(nodes.length, value);
1820
+ } else {
1821
+ const index = parseInt(property) + 1;
1822
+ if (index > len) nodes.length && setSignal(nodes.length, index);
1823
+ }
1567
1824
  }
1568
1825
  nodes[$TRACK] && setSignal(nodes[$TRACK], undefined);
1569
1826
  });
@@ -1571,17 +1828,26 @@ const storeTraps = {
1571
1828
  return true;
1572
1829
  },
1573
1830
  deleteProperty(target, property) {
1574
- if (writeOnly(target[$PROXY]) && target[STORE_OVERRIDE]?.[property] !== $DELETED) {
1831
+ const optDeleted = target[STORE_OPTIMISTIC_OVERRIDE]?.[property] === $DELETED;
1832
+ const regDeleted = target[STORE_OVERRIDE]?.[property] === $DELETED;
1833
+ if (writeOnly(target[$PROXY]) && !optDeleted && !regDeleted) {
1575
1834
  untrack(() => {
1835
+ const useOptimistic = target[STORE_OPTIMISTIC] && !projectionWriteActive;
1836
+ const overrideKey = useOptimistic ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
1837
+ if (useOptimistic) trackOptimisticStore(target[$PROXY]);
1576
1838
  const prev =
1577
- target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1578
- ? target[STORE_OVERRIDE][property]
1579
- : target[STORE_VALUE][property];
1580
- if (property in target[STORE_VALUE]) {
1581
- (target[STORE_OVERRIDE] || (target[STORE_OVERRIDE] = Object.create(null)))[property] =
1582
- $DELETED;
1583
- } else if (target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]) {
1584
- delete target[STORE_OVERRIDE][property];
1839
+ target[STORE_OPTIMISTIC_OVERRIDE] && property in target[STORE_OPTIMISTIC_OVERRIDE]
1840
+ ? target[STORE_OPTIMISTIC_OVERRIDE][property]
1841
+ : target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1842
+ ? target[STORE_OVERRIDE][property]
1843
+ : target[STORE_VALUE][property];
1844
+ if (
1845
+ property in target[STORE_VALUE] ||
1846
+ (target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE])
1847
+ ) {
1848
+ (target[overrideKey] || (target[overrideKey] = Object.create(null)))[property] = $DELETED;
1849
+ } else if (target[overrideKey] && property in target[overrideKey]) {
1850
+ delete target[overrideKey][property];
1585
1851
  } else return true;
1586
1852
  if (isWrappable(prev)) {
1587
1853
  const parents = PARENTS.get(prev);
@@ -1597,10 +1863,32 @@ const storeTraps = {
1597
1863
  },
1598
1864
  ownKeys(target) {
1599
1865
  trackSelf(target);
1600
- return getKeys(target[STORE_VALUE], target[STORE_OVERRIDE], false);
1866
+ let keys = getKeys(target[STORE_VALUE], target[STORE_OVERRIDE], false);
1867
+ if (target[STORE_OPTIMISTIC_OVERRIDE]) {
1868
+ const keySet = new Set(keys);
1869
+ for (const key of Reflect.ownKeys(target[STORE_OPTIMISTIC_OVERRIDE])) {
1870
+ if (target[STORE_OPTIMISTIC_OVERRIDE][key] !== $DELETED) keySet.add(key);
1871
+ else keySet.delete(key);
1872
+ }
1873
+ keys = Array.from(keySet);
1874
+ }
1875
+ return keys;
1601
1876
  },
1602
1877
  getOwnPropertyDescriptor(target, property) {
1603
1878
  if (property === $PROXY) return { value: target[$PROXY], writable: true, configurable: true };
1879
+ if (target[STORE_OPTIMISTIC_OVERRIDE] && property in target[STORE_OPTIMISTIC_OVERRIDE]) {
1880
+ if (target[STORE_OPTIMISTIC_OVERRIDE][property] === $DELETED) return undefined;
1881
+ const baseDesc = getPropertyDescriptor(target[STORE_VALUE], target[STORE_OVERRIDE], property);
1882
+ if (baseDesc) {
1883
+ return { ...baseDesc, value: target[STORE_OPTIMISTIC_OVERRIDE][property] };
1884
+ }
1885
+ return {
1886
+ value: target[STORE_OPTIMISTIC_OVERRIDE][property],
1887
+ writable: true,
1888
+ enumerable: true,
1889
+ configurable: true
1890
+ };
1891
+ }
1604
1892
  return getPropertyDescriptor(target[STORE_VALUE], target[STORE_OVERRIDE], property);
1605
1893
  },
1606
1894
  getPrototypeOf(target) {
@@ -1689,7 +1977,82 @@ function deep(store) {
1689
1977
  return store[$DEEP];
1690
1978
  }
1691
1979
  function createOptimisticStore(first, second, options) {
1692
- return [];
1980
+ GlobalQueue._clearOptimisticStore ||= clearOptimisticStore;
1981
+ const derived = typeof first === "function";
1982
+ const initialValue = (derived ? second : first) ?? {};
1983
+ const fn = derived ? first : undefined;
1984
+ const { store: wrappedStore } = createOptimisticProjectionInternal(fn, initialValue, options);
1985
+ return [wrappedStore, fn => storeSetter(wrappedStore, fn)];
1986
+ }
1987
+ function clearOptimisticStore(store) {
1988
+ const target = store[$TARGET];
1989
+ if (!target || !target[STORE_OPTIMISTIC_OVERRIDE]) return;
1990
+ const override = target[STORE_OPTIMISTIC_OVERRIDE];
1991
+ const nodes = target[STORE_NODE];
1992
+ if (nodes) {
1993
+ for (const key of Reflect.ownKeys(override)) {
1994
+ if (nodes[key]) {
1995
+ const baseValue =
1996
+ target[STORE_OVERRIDE] && key in target[STORE_OVERRIDE]
1997
+ ? target[STORE_OVERRIDE][key]
1998
+ : target[STORE_VALUE][key];
1999
+ const value = baseValue === $DELETED ? undefined : baseValue;
2000
+ setSignal(nodes[key], isWrappable(value) ? wrap(value, target) : value);
2001
+ }
2002
+ }
2003
+ nodes[$TRACK] && setSignal(nodes[$TRACK], undefined);
2004
+ }
2005
+ delete target[STORE_OPTIMISTIC_OVERRIDE];
2006
+ }
2007
+ function createOptimisticProjectionInternal(fn, initialValue = {}, options) {
2008
+ let node;
2009
+ const wrappedMap = new WeakMap();
2010
+ const wrapper = s => {
2011
+ s[STORE_WRAP] = wrapProjection;
2012
+ s[STORE_LOOKUP] = wrappedMap;
2013
+ s[STORE_OPTIMISTIC] = true;
2014
+ Object.defineProperty(s, STORE_FIREWALL, {
2015
+ get() {
2016
+ return node;
2017
+ },
2018
+ configurable: true
2019
+ });
2020
+ };
2021
+ const wrapProjection = source => {
2022
+ if (wrappedMap.has(source)) return wrappedMap.get(source);
2023
+ if (source[$TARGET]?.[STORE_WRAP] === wrapProjection) return source;
2024
+ const wrapped = createStoreProxy(source, storeTraps, wrapper);
2025
+ wrappedMap.set(source, wrapped);
2026
+ return wrapped;
2027
+ };
2028
+ const wrappedStore = wrapProjection(initialValue);
2029
+ if (fn) {
2030
+ node = computed(() => {
2031
+ const owner = getOwner();
2032
+ setProjectionWriteActive(true);
2033
+ try {
2034
+ storeSetter(new Proxy(wrappedStore, writeTraps), s => {
2035
+ const value = handleAsync(owner, fn(s), value => {
2036
+ setProjectionWriteActive(true);
2037
+ try {
2038
+ value !== s &&
2039
+ value !== undefined &&
2040
+ storeSetter(wrappedStore, reconcile(value, options?.key || "id", options?.all));
2041
+ } finally {
2042
+ setProjectionWriteActive(false);
2043
+ }
2044
+ });
2045
+ value !== s &&
2046
+ value !== undefined &&
2047
+ reconcile(value, options?.key || "id", options?.all)(wrappedStore);
2048
+ });
2049
+ } finally {
2050
+ setProjectionWriteActive(false);
2051
+ }
2052
+ });
2053
+ node._preventAutoDisposal = true;
2054
+ }
2055
+ return { store: wrappedStore, node: node };
1693
2056
  }
1694
2057
  function snapshot(item, map, lookup) {
1695
2058
  let target, isArray, override, result, unwrapped, v;
@@ -2084,21 +2447,15 @@ function compare(key, a, b) {
2084
2447
  return key ? key(a) === key(b) : true;
2085
2448
  }
2086
2449
  function boundaryComputed(fn, propagationMask) {
2087
- const node = computed(fn, undefined, {
2088
- _internal: {
2089
- _notifyQueue() {
2090
- let flags = this._statusFlags;
2091
- this._statusFlags &= ~this._propagationMask;
2092
- if (this._propagationMask & STATUS_PENDING && !(this._statusFlags & STATUS_UNINITIALIZED)) {
2093
- flags &= ~STATUS_PENDING;
2094
- }
2095
- this._queue.notify(this, this._propagationMask, flags);
2096
- },
2097
- _propagationMask: propagationMask
2098
- }
2099
- });
2450
+ const node = computed(fn, undefined, { lazy: true });
2451
+ node._notifyStatus = () => {
2452
+ const flags = node._statusFlags;
2453
+ node._statusFlags &= ~node._propagationMask;
2454
+ node._queue.notify(node, node._propagationMask, flags);
2455
+ };
2100
2456
  node._propagationMask = propagationMask;
2101
2457
  node._preventAutoDisposal = true;
2458
+ recompute(node, true);
2102
2459
  return node;
2103
2460
  }
2104
2461
  function createBoundChildren(owner, fn, queue, mask) {
@@ -2198,8 +2555,10 @@ function createCollectionBoundary(type, fn, fallback) {
2198
2555
  const decision = computed(() => {
2199
2556
  if (!read(queue._disabled)) {
2200
2557
  const resolved = read(tree);
2201
- if (!untrack(() => read(queue._disabled))) queue._initialized = true;
2202
- return resolved;
2558
+ if (!untrack(() => read(queue._disabled))) {
2559
+ queue._initialized = true;
2560
+ return resolved;
2561
+ }
2203
2562
  }
2204
2563
  return fallback(queue);
2205
2564
  });