@solidjs/signals 0.9.5 → 0.9.7

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,39 +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
255
  this._pendingNodes !== activeTransition.pendingNodes &&
230
256
  this._pendingNodes.push(...activeTransition.pendingNodes);
231
- this._optimisticNodes !== activeTransition.optimisticNodes &&
232
- this._optimisticNodes.push(...activeTransition.optimisticNodes);
233
257
  this.restoreQueues(activeTransition.queueStash);
234
258
  transitions.delete(activeTransition);
259
+ const completingTransition = activeTransition;
235
260
  activeTransition = null;
236
- runTransitionPending(this._pendingNodes, false);
237
- } else if (transitions.size) runHeap(zombieQueue, GlobalQueue._update);
238
- runOptimistic();
261
+ runTransitionPending(this._pendingNodes);
262
+ finalizePureQueue(completingTransition);
263
+ } else {
264
+ if (transitions.size) runHeap(zombieQueue, GlobalQueue._update);
265
+ finalizePureQueue();
266
+ }
239
267
  clock++;
240
- scheduled = false;
268
+ scheduled = dirtyQueue._max >= dirtyQueue._min;
269
+ this.runOptimistic(EFFECT_RENDER);
241
270
  this.run(EFFECT_RENDER);
271
+ this.runOptimistic(EFFECT_USER);
242
272
  this.run(EFFECT_USER);
243
273
  } finally {
244
274
  this._running = false;
@@ -247,7 +277,11 @@ class GlobalQueue extends Queue {
247
277
  notify(node, mask, flags) {
248
278
  if (mask & STATUS_PENDING) {
249
279
  if (flags & STATUS_PENDING) {
250
- if (activeTransition && !activeTransition.asyncNodes.includes(node._error.cause)) {
280
+ if (
281
+ activeTransition &&
282
+ node._error &&
283
+ !activeTransition.asyncNodes.includes(node._error.cause)
284
+ ) {
251
285
  activeTransition.asyncNodes.push(node._error.cause);
252
286
  schedule();
253
287
  }
@@ -266,6 +300,7 @@ class GlobalQueue extends Queue {
266
300
  pendingNodes: [],
267
301
  asyncNodes: [],
268
302
  optimisticNodes: [],
303
+ optimisticStores: new Set(),
269
304
  actions: [],
270
305
  queueStash: { _queues: [[], []], _children: [] },
271
306
  done: false
@@ -283,68 +318,86 @@ class GlobalQueue extends Queue {
283
318
  n._transition = activeTransition;
284
319
  activeTransition.pendingNodes.push(n);
285
320
  }
321
+ this._pendingNodes = activeTransition.pendingNodes;
286
322
  for (let i = 0; i < this._optimisticNodes.length; i++) {
287
- const n = this._optimisticNodes[i];
288
- n._transition = activeTransition;
289
- activeTransition.optimisticNodes.push(n);
323
+ const node = this._optimisticNodes[i];
324
+ node._transition = activeTransition;
325
+ activeTransition.optimisticNodes.push(node);
290
326
  }
291
- this._pendingNodes = activeTransition.pendingNodes;
292
327
  this._optimisticNodes = activeTransition.optimisticNodes;
328
+ for (const store of this._optimisticStores) {
329
+ activeTransition.optimisticStores.add(store);
330
+ }
331
+ this._optimisticStores = activeTransition.optimisticStores;
293
332
  }
294
333
  }
295
- function notifySubs(node) {
334
+ function insertSubs(node, optimistic = false) {
296
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
+ }
297
345
  const queue = s._sub._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue;
298
346
  if (queue._min > s._sub._height) queue._min = s._sub._height;
299
347
  insertIntoHeap(s._sub, queue);
300
348
  }
301
349
  }
302
- function runOptimistic(activeTransition = null) {
303
- let resolvePending = !activeTransition;
304
- const optimisticNodes = globalQueue._optimisticNodes;
305
- optimisticRun = true;
306
- for (let i = 0; i < optimisticNodes.length; i++) {
307
- const n = optimisticNodes[i];
308
- if (
309
- !activeTransition &&
310
- (!n._transition || n._transition.done) &&
311
- n._pendingValue !== NOT_PENDING
312
- ) {
313
- n._value = n._pendingValue;
314
- n._pendingValue = NOT_PENDING;
315
- }
316
- n._transition = activeTransition;
317
- notifySubs(n);
318
- }
319
- globalQueue._optimisticNodes = [];
350
+ function finalizePureQueue(completingTransition = null, incomplete = false) {
351
+ let resolvePending = !incomplete;
320
352
  if (dirtyQueue._max >= dirtyQueue._min) {
321
353
  resolvePending = true;
322
354
  runHeap(dirtyQueue, GlobalQueue._update);
323
355
  }
324
- optimisticRun = false;
325
- resolvePending && runPending(globalQueue._pendingNodes);
326
- }
327
- function runPending(pendingNodes) {
328
- for (let i = 0; i < pendingNodes.length; i++) {
329
- const n = pendingNodes[i];
330
- if (n._pendingValue !== NOT_PENDING) {
331
- 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
+ }
332
378
  n._pendingValue = NOT_PENDING;
333
- 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();
334
391
  }
335
- if (n._fn) GlobalQueue._dispose(n, false, true);
336
392
  }
337
- pendingNodes.length = 0;
338
393
  }
339
- function runTransitionPending(pendingNodes, value) {
340
- const p = pendingNodes.slice();
341
- for (let i = 0; i < p.length; i++) {
342
- const n = p[i];
343
- n._transition = activeTransition;
344
- if (n._pendingCheck) {
345
- n._pendingCheck._transition = activeTransition;
346
- n._pendingCheck._set(value);
347
- }
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;
348
401
  }
349
402
  }
350
403
  const globalQueue = new GlobalQueue();
@@ -377,7 +430,6 @@ function currentTransition(transition) {
377
430
  }
378
431
  function runInTransition(transition, fn) {
379
432
  const prevTransition = activeTransition;
380
- activeTransition = null;
381
433
  try {
382
434
  activeTransition = currentTransition(transition);
383
435
  return fn();
@@ -385,45 +437,67 @@ function runInTransition(transition, fn) {
385
437
  activeTransition = prevTransition;
386
438
  }
387
439
  }
440
+ function restoreTransition(transition, fn) {
441
+ globalQueue.initTransition(transition);
442
+ const result = fn();
443
+ flush();
444
+ return result;
445
+ }
388
446
  function action(genFn) {
389
- return (...args) => {
390
- const iterator = genFn(...args);
391
- globalQueue.initTransition();
392
- let ctx = activeTransition;
393
- ctx.actions.push(iterator);
394
- const step = input => {
395
- let nextValue = iterator.next(input);
396
- if (nextValue instanceof Promise) return nextValue.then(process);
397
- process(nextValue);
398
- };
399
- const process = result => {
400
- if (result.done) {
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) => {
401
454
  ctx = currentTransition(ctx);
402
- ctx.actions.splice(ctx.actions.indexOf(iterator), 1);
455
+ const i = ctx.actions.indexOf(it);
456
+ if (i >= 0) ctx.actions.splice(i, 1);
403
457
  activeTransition = ctx;
404
458
  schedule();
405
- flush();
406
- return;
407
- }
408
- const yielded = result.value;
409
- if (yielded instanceof Promise) return yielded.then(v => runInTransition(ctx, () => step(v)));
410
- runInTransition(ctx, () => step(yielded));
411
- };
412
- runInTransition(ctx, () => step());
413
- };
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
+ });
414
483
  }
415
484
  GlobalQueue._update = recompute;
416
485
  GlobalQueue._dispose = disposeChildren;
417
486
  let tracking = false;
418
487
  let stale = false;
419
- let pendingValueCheck = false;
420
- let pendingCheck = null;
421
488
  let refreshing = false;
489
+ let pendingCheckActive = false;
490
+ let foundPending = false;
491
+ let pendingReadActive = false;
422
492
  let context = null;
493
+ let leafEffectActive = false;
494
+ function setLeafEffectActive(v) {
495
+ leafEffectActive = v;
496
+ }
423
497
  function recompute(el, create = false) {
424
- const honoraryOptimistic = el._type && el._transition != activeTransition;
498
+ const isEffect = el._type;
425
499
  if (!create) {
426
- if (el._transition && activeTransition !== el._transition && !honoraryOptimistic)
500
+ if (el._transition && (!isEffect || activeTransition) && activeTransition !== el._transition)
427
501
  globalQueue.initTransition(el._transition);
428
502
  deleteFromHeap(el, el._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue);
429
503
  if (el._transition) disposeChildren(el);
@@ -435,35 +509,33 @@ function recompute(el, create = false) {
435
509
  el._firstChild = null;
436
510
  }
437
511
  }
512
+ const isOptimisticDirty = !!(el._flags & REACTIVE_OPTIMISTIC_DIRTY);
513
+ const hasOptimisticOverride = el._optimistic && el._pendingValue !== NOT_PENDING;
438
514
  const oldcontext = context;
439
515
  context = el;
440
516
  el._depsTail = null;
441
517
  el._flags = REACTIVE_RECOMPUTING_DEPS;
442
518
  el._time = clock;
443
- let value =
444
- el._pendingValue === NOT_PENDING || (el._optimistic && el._transition)
445
- ? el._value
446
- : el._pendingValue;
519
+ let value = el._pendingValue === NOT_PENDING ? el._value : el._pendingValue;
447
520
  let oldHeight = el._height;
448
- let prevStatusFlags = el._statusFlags;
449
- let prevError = el._error;
450
521
  let prevTracking = tracking;
451
- setStatusFlags(el, STATUS_NONE | (prevStatusFlags & STATUS_UNINITIALIZED));
522
+ let prevOptimisticRead = optimisticReadActive;
452
523
  tracking = true;
524
+ if (isOptimisticDirty) setOptimisticReadActive(true);
453
525
  try {
454
526
  value = handleAsync(el, el._fn(value));
455
- el._statusFlags &= ~STATUS_UNINITIALIZED;
527
+ clearStatus(el);
456
528
  } catch (e) {
457
529
  if (e instanceof NotReadyError) {
458
530
  if (e.cause !== el) link(e.cause, el);
459
- setStatusFlags(el, (prevStatusFlags & ~STATUS_ERROR) | STATUS_PENDING, e);
460
- } else setStatusFlags(el, STATUS_ERROR, e);
531
+ notifyStatus(el, STATUS_PENDING, e);
532
+ } else notifyStatus(el, STATUS_ERROR, e);
461
533
  } finally {
462
534
  tracking = prevTracking;
535
+ el._flags = REACTIVE_NONE;
536
+ context = oldcontext;
463
537
  }
464
- el._flags = REACTIVE_NONE;
465
- context = oldcontext;
466
- if (!(el._statusFlags & STATUS_PENDING)) {
538
+ if (!el._error) {
467
539
  const depsTail = el._depsTail;
468
540
  let toRemove = depsTail !== null ? depsTail._nextDep : el._deps;
469
541
  if (toRemove !== null) {
@@ -473,85 +545,158 @@ function recompute(el, create = false) {
473
545
  if (depsTail !== null) depsTail._nextDep = null;
474
546
  else el._deps = null;
475
547
  }
476
- }
477
- const valueChanged =
478
- !el._equals ||
479
- !el._equals(
480
- el._pendingValue === NOT_PENDING || (el._optimistic && el._transition) || honoraryOptimistic
481
- ? el._value
482
- : el._pendingValue,
483
- value
484
- );
485
- const statusFlagsChanged = el._statusFlags !== prevStatusFlags || el._error !== prevError;
486
- if (el._child && el._statusFlags & STATUS_PENDING && activeTransition)
487
- activeTransition.asyncNodes.push(el);
488
- el._notifyQueue?.(statusFlagsChanged, prevStatusFlags);
489
- if (valueChanged || statusFlagsChanged) {
548
+ const valueChanged =
549
+ !el._equals ||
550
+ !el._equals(el._pendingValue === NOT_PENDING ? el._value : el._pendingValue, value);
490
551
  if (valueChanged) {
491
- if (create || el._optimistic || honoraryOptimistic) el._value = value;
552
+ if (create || (isEffect && activeTransition !== el._transition) || isOptimisticDirty)
553
+ el._value = value;
492
554
  else el._pendingValue = value;
493
- if (el._pendingSignal) setSignal(el._pendingSignal, value);
494
- }
495
- (!el._optimistic || optimisticRun) && notifySubs(el);
496
- } else if (el._height != oldHeight) {
497
- for (let s = el._subs; s !== null; s = s._nextSub) {
498
- 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
+ }
499
560
  }
500
561
  }
501
- el._optimistic && !optimisticRun && globalQueue._optimisticNodes.push(el);
562
+ setOptimisticReadActive(prevOptimisticRead);
502
563
  (!create || el._statusFlags & STATUS_PENDING) &&
503
564
  !el._transition &&
504
565
  globalQueue._pendingNodes.push(el);
505
- el._transition && honoraryOptimistic && runInTransition(el._transition, () => recompute(el));
566
+ el._transition &&
567
+ isEffect &&
568
+ activeTransition !== el._transition &&
569
+ runInTransition(el._transition, () => recompute(el));
506
570
  }
507
571
  function handleAsync(el, result, setter) {
508
572
  const isObject = typeof result === "object" && result !== null;
509
- const isPromise = isObject && result instanceof Promise;
510
573
  const iterator = isObject && untrack(() => result[Symbol.asyncIterator]);
511
- if (!isPromise && !iterator) {
574
+ const isThenable = !iterator && isObject && untrack(() => typeof result.then === "function");
575
+ if (!isThenable && !iterator) {
512
576
  el._inFlight = null;
513
577
  return result;
514
578
  }
515
579
  el._inFlight = result;
516
- if (isPromise) {
517
- result
518
- .then(value => {
519
- if (el._inFlight !== result) return;
520
- globalQueue.initTransition(el._transition);
521
- setter ? setter(value) : setSignal(el, () => value);
522
- flush();
523
- })
524
- .catch(e => {
525
- if (el._inFlight !== result) return;
526
- globalQueue.initTransition(el._transition);
527
- setStatusFlags(el, STATUS_ERROR, e);
528
- el._time = clock;
529
- notifySubs(el);
530
- schedule();
531
- flush();
532
- });
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();
533
668
  } else {
534
- (async () => {
535
- try {
536
- for await (let value of result) {
537
- if (el._inFlight !== result) return;
538
- globalQueue.initTransition(el._transition);
539
- setter ? setter(value) : setSignal(el, () => value);
540
- 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);
541
673
  }
542
- } catch (error) {
543
- if (el._inFlight !== result) return;
544
- globalQueue.initTransition(el._transition);
545
- setStatusFlags(el, STATUS_ERROR, error);
546
- el._time = clock;
547
- notifySubs(el);
548
- schedule();
549
- flush();
550
674
  }
551
- })();
675
+ }
676
+ schedule();
677
+ }
678
+ }
679
+ function notifyStatus(el, status, error) {
680
+ el._statusFlags = status | (status !== STATUS_ERROR ? el._statusFlags & STATUS_UNINITIALIZED : 0);
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
+ }
552
699
  }
553
- globalQueue.initTransition(el._transition);
554
- throw new NotReadyError(context);
555
700
  }
556
701
  function updateIfNecessary(el) {
557
702
  if (el._flags & REACTIVE_CHECK) {
@@ -633,10 +778,6 @@ function isValidLink(checkLink, sub) {
633
778
  }
634
779
  return false;
635
780
  }
636
- function setStatusFlags(signal, flags, error = null) {
637
- signal._statusFlags = flags;
638
- signal._error = error;
639
- }
640
781
  function markDisposal(el) {
641
782
  let child = el._firstChild;
642
783
  while (child) {
@@ -739,10 +880,12 @@ function computed(fn, initialValue, options) {
739
880
  _inFlight: null,
740
881
  _transition: null
741
882
  };
742
- if (options?._internal) Object.assign(self, options._internal);
743
883
  self._name = options?.name ?? "computed";
744
884
  self._prevHeap = self;
745
885
  const parent = context?._root ? context._parentComputed : context;
886
+ if (leafEffectActive && context) {
887
+ throw new Error("Cannot create reactive primitives inside createTrackedEffect");
888
+ }
746
889
  if (context) {
747
890
  const lastChild = context._firstChild;
748
891
  if (lastChild === null) {
@@ -758,14 +901,12 @@ function computed(fn, initialValue, options) {
758
901
  }
759
902
  function signal(v, options, firewall = null) {
760
903
  const s = {
761
- id: options?.id ?? (context?.id != null ? getNextChildId(context) : undefined),
762
904
  _equals: options?.equals != null ? options.equals : isEqual,
763
905
  _pureWrite: !!options?.pureWrite,
764
906
  _unobserved: options?.unobserved,
765
907
  _value: v,
766
908
  _subs: null,
767
909
  _subsTail: null,
768
- _statusFlags: STATUS_NONE,
769
910
  _time: clock,
770
911
  _firewall: firewall,
771
912
  _nextChild: firewall?._child || null,
@@ -775,6 +916,43 @@ function signal(v, options, firewall = null) {
775
916
  firewall && (firewall._child = s);
776
917
  return s;
777
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
+ const prevContext = context;
946
+ context = null;
947
+ el._pendingValueComputed = optimisticComputed(() => read(el));
948
+ context = prevContext;
949
+ pendingReadActive = prevPending;
950
+ }
951
+ return el._pendingValueComputed;
952
+ }
953
+ function updatePendingSignal(el) {
954
+ if (el._pendingSignal) setSignal(el._pendingSignal, computePendingState(el));
955
+ }
778
956
  function isEqual(a, b) {
779
957
  return a === b;
780
958
  }
@@ -788,10 +966,30 @@ function untrack(fn) {
788
966
  }
789
967
  }
790
968
  function read(el) {
969
+ if (pendingCheckActive) {
970
+ const target = el._firewall || el;
971
+ const pendingSig = getPendingSignal(target);
972
+ const prevCheck = pendingCheckActive;
973
+ pendingCheckActive = false;
974
+ if (read(pendingSig)) {
975
+ foundPending = true;
976
+ }
977
+ pendingCheckActive = prevCheck;
978
+ return el._value;
979
+ }
980
+ if (pendingReadActive) {
981
+ const pendingComputed = getPendingValueComputed(el);
982
+ const prevPending = pendingReadActive;
983
+ pendingReadActive = false;
984
+ const value = read(pendingComputed);
985
+ pendingReadActive = prevPending;
986
+ if (pendingComputed._statusFlags & STATUS_PENDING) return el._value;
987
+ return value;
988
+ }
791
989
  let c = context;
792
990
  if (c?._root) c = c._parentComputed;
793
991
  if (refreshing && el._fn) recompute(el);
794
- if (c && tracking && !pendingCheck && !pendingValueCheck) {
992
+ if (c && tracking) {
795
993
  if (el._fn && el._flags & REACTIVE_DISPOSED) recompute(el);
796
994
  link(el, c);
797
995
  const owner = el._firewall || el;
@@ -808,51 +1006,24 @@ function read(el) {
808
1006
  }
809
1007
  }
810
1008
  }
811
- if (pendingValueCheck) {
812
- if (!el._pendingSignal) {
813
- el._pendingSignal = signal(el._value);
814
- el._pendingSignal._optimistic = true;
815
- }
816
- pendingValueCheck = false;
817
- try {
818
- return read(el._pendingSignal);
819
- } finally {
820
- pendingValueCheck = true;
821
- }
822
- }
823
1009
  const asyncCompute = el._firewall || el;
824
- if (pendingCheck) {
825
- if (!asyncCompute._pendingCheck) {
826
- asyncCompute._pendingCheck = signal(false);
827
- asyncCompute._pendingCheck._optimistic = true;
828
- asyncCompute._pendingCheck._set = v => setSignal(asyncCompute._pendingCheck, v);
829
- }
830
- const prev = pendingCheck;
831
- pendingCheck = null;
832
- prev._value = read(asyncCompute._pendingCheck) || prev._value;
833
- pendingCheck = prev;
834
- }
835
- if (!pendingCheck && asyncCompute._statusFlags & STATUS_PENDING) {
836
- if ((c && !stale) || asyncCompute._statusFlags & STATUS_UNINITIALIZED || el._firewall)
837
- throw asyncCompute._error;
838
- else if (c && stale) {
839
- setStatusFlags(c, c._statusFlags | STATUS_PENDING, asyncCompute._error);
840
- }
841
- }
842
- if (el._statusFlags & STATUS_ERROR) {
1010
+ if (
1011
+ c &&
1012
+ !optimisticReadActive &&
1013
+ asyncCompute._statusFlags & STATUS_PENDING &&
1014
+ !(stale && asyncCompute._transition && activeTransition !== asyncCompute._transition)
1015
+ )
1016
+ throw asyncCompute._error;
1017
+ if (el._fn && el._statusFlags & STATUS_ERROR) {
843
1018
  if (el._time < clock) {
844
1019
  recompute(el, true);
845
1020
  return read(el);
846
- } else {
847
- throw el._error;
848
- }
1021
+ } else throw el._error;
849
1022
  }
850
1023
  return !c ||
851
- el._optimistic ||
1024
+ optimisticReadActive ||
852
1025
  el._pendingValue === NOT_PENDING ||
853
- (stale &&
854
- !pendingCheck &&
855
- (c._optimistic || (el._transition && activeTransition !== el._transition)))
1026
+ (stale && el._transition && activeTransition !== el._transition)
856
1027
  ? el._value
857
1028
  : el._pendingValue;
858
1029
  }
@@ -861,37 +1032,41 @@ function setSignal(el, v) {
861
1032
  console.warn("A Signal was written to in an owned scope.");
862
1033
  if (el._transition && activeTransition !== el._transition)
863
1034
  globalQueue.initTransition(el._transition);
864
- if (typeof v === "function") {
865
- v = v(
866
- el._pendingValue === NOT_PENDING || (el._optimistic && el._transition)
867
- ? el._value
868
- : el._pendingValue
869
- );
870
- }
871
- const valueChanged =
872
- !el._equals ||
873
- !el._equals(
874
- el._pendingValue === NOT_PENDING || (el._optimistic && el._transition)
875
- ? el._value
876
- : el._pendingValue,
877
- v
878
- );
879
- if (!valueChanged && !el._statusFlags) return v;
880
- if (valueChanged) {
881
- if (el._optimistic) el._value = v;
882
- else {
883
- if (el._pendingValue === NOT_PENDING) globalQueue._pendingNodes.push(el);
884
- el._pendingValue = v;
1035
+ const isOptimistic = el._optimistic && !projectionWriteActive;
1036
+ const currentValue = isOptimistic
1037
+ ? el._value
1038
+ : el._pendingValue === NOT_PENDING
1039
+ ? el._value
1040
+ : el._pendingValue;
1041
+ if (typeof v === "function") v = v(currentValue);
1042
+ const valueChanged = !el._equals || !el._equals(currentValue, v);
1043
+ if (!valueChanged) return v;
1044
+ if (isOptimistic) {
1045
+ const isFirstWrite = el._pendingValue === NOT_PENDING;
1046
+ if (el._transition && !isFirstWrite) {
1047
+ globalQueue.initTransition(el._transition);
885
1048
  }
886
- if (el._pendingSignal) setSignal(el._pendingSignal, v);
1049
+ if (isFirstWrite) {
1050
+ el._pendingValue = el._value;
1051
+ globalQueue._optimisticNodes.push(el);
1052
+ }
1053
+ el._value = v;
1054
+ } else {
1055
+ if (el._pendingValue === NOT_PENDING) globalQueue._pendingNodes.push(el);
1056
+ el._pendingValue = v;
1057
+ }
1058
+ updatePendingSignal(el);
1059
+ if (el._pendingValueComputed) {
1060
+ setSignal(el._pendingValueComputed, v);
887
1061
  }
888
- setStatusFlags(el, STATUS_NONE);
889
1062
  el._time = clock;
890
- el._optimistic && !optimisticRun ? globalQueue._optimisticNodes.push(el) : notifySubs(el);
1063
+ insertSubs(el, isOptimistic);
891
1064
  schedule();
892
1065
  return v;
893
1066
  }
1067
+ const PENDING_OWNER = {};
894
1068
  function getObserver() {
1069
+ if (pendingCheckActive || pendingReadActive) return PENDING_OWNER;
895
1070
  return tracking ? context : null;
896
1071
  }
897
1072
  function getOwner() {
@@ -899,25 +1074,20 @@ function getOwner() {
899
1074
  }
900
1075
  function onCleanup(fn) {
901
1076
  if (!context) return fn;
902
- const node = context;
903
- if (!node._disposal) {
904
- node._disposal = fn;
905
- } else if (Array.isArray(node._disposal)) {
906
- node._disposal.push(fn);
907
- } else {
908
- node._disposal = [node._disposal, fn];
909
- }
1077
+ if (!context._disposal) context._disposal = fn;
1078
+ else if (Array.isArray(context._disposal)) context._disposal.push(fn);
1079
+ else context._disposal = [context._disposal, fn];
910
1080
  return fn;
911
1081
  }
912
1082
  function createOwner(options) {
913
1083
  const parent = context;
914
1084
  const owner = {
1085
+ id: options?.id ?? (parent?.id != null ? getNextChildId(parent) : undefined),
915
1086
  _root: true,
916
1087
  _parentComputed: parent?._root ? parent._parentComputed : parent,
917
1088
  _firstChild: null,
918
1089
  _nextSibling: null,
919
1090
  _disposal: null,
920
- id: options?.id ?? (parent?.id != null ? getNextChildId(parent) : undefined),
921
1091
  _queue: parent?._queue ?? globalQueue,
922
1092
  _context: parent?._context || defaultContext,
923
1093
  _childCount: 0,
@@ -928,6 +1098,9 @@ function createOwner(options) {
928
1098
  disposeChildren(owner, self);
929
1099
  }
930
1100
  };
1101
+ if (leafEffectActive && parent) {
1102
+ throw new Error("Cannot create reactive primitives inside createTrackedEffect");
1103
+ }
931
1104
  if (parent) {
932
1105
  const lastChild = parent._firstChild;
933
1106
  if (lastChild === null) {
@@ -965,25 +1138,25 @@ function staleValues(fn, set = true) {
965
1138
  }
966
1139
  }
967
1140
  function pending(fn) {
968
- const prevLatest = pendingValueCheck;
969
- pendingValueCheck = true;
1141
+ const prevPending = pendingReadActive;
1142
+ pendingReadActive = true;
970
1143
  try {
971
- return staleValues(fn, false);
1144
+ return fn();
972
1145
  } finally {
973
- pendingValueCheck = prevLatest;
1146
+ pendingReadActive = prevPending;
974
1147
  }
975
1148
  }
976
1149
  function isPending(fn) {
977
- const current = pendingCheck;
978
- pendingCheck = { _value: false };
1150
+ const prevPendingCheck = pendingCheckActive;
1151
+ const prevFoundPending = foundPending;
1152
+ pendingCheckActive = true;
1153
+ foundPending = false;
979
1154
  try {
980
- staleValues(fn);
981
- return pendingCheck._value;
982
- } catch (err) {
983
- if (!(err instanceof NotReadyError)) return false;
984
- throw err;
1155
+ fn();
1156
+ return foundPending;
985
1157
  } finally {
986
- pendingCheck = current;
1158
+ pendingCheckActive = prevPendingCheck;
1159
+ foundPending = prevFoundPending;
987
1160
  }
988
1161
  }
989
1162
  function refresh(fn) {
@@ -999,7 +1172,6 @@ function refresh(fn) {
999
1172
  refreshing = prevRefreshing;
1000
1173
  if (!prevRefreshing) {
1001
1174
  schedule();
1002
- flush();
1003
1175
  }
1004
1176
  }
1005
1177
  }
@@ -1036,54 +1208,50 @@ function isUndefined(value) {
1036
1208
  }
1037
1209
  function effect(compute, effect, error, initialValue, options) {
1038
1210
  let initialized = false;
1039
- const node = computed(compute, initialValue, {
1040
- ...options,
1041
- _internal: {
1042
- _modified: true,
1043
- _prevValue: initialValue,
1044
- _effectFn: effect,
1045
- _errorFn: error,
1046
- _cleanup: undefined,
1047
- _type: options?.render ? EFFECT_RENDER : EFFECT_USER,
1048
- _notifyQueue(statusFlagsChanged, prevStatusFlags) {
1049
- if (initialized) {
1050
- const errorChanged =
1051
- this._statusFlags && this._statusFlags === prevStatusFlags && statusFlagsChanged;
1052
- this._modified =
1053
- !(this._statusFlags & STATUS_ERROR) &&
1054
- !(this._statusFlags & STATUS_PENDING & ~prevStatusFlags) &&
1055
- !errorChanged;
1056
- if (this._modified) this._queue.enqueue(this._type, runEffect.bind(this));
1057
- }
1058
- if (this._statusFlags & STATUS_ERROR) {
1059
- let error = this._error;
1060
- this._queue.notify(this, STATUS_PENDING, 0);
1061
- if (this._type === EFFECT_USER) {
1062
- try {
1063
- return this._errorFn
1064
- ? this._errorFn(error, () => {
1065
- this._cleanup?.();
1066
- this._cleanup = undefined;
1067
- })
1068
- : console.error(error);
1069
- } catch (e) {
1070
- error = e;
1071
- }
1072
- }
1073
- if (!this._queue.notify(this, STATUS_ERROR, STATUS_ERROR)) throw error;
1074
- } else if (this._type === EFFECT_RENDER) {
1075
- this._queue.notify(this, STATUS_PENDING | STATUS_ERROR, this._statusFlags);
1211
+ const node = computed(
1212
+ options?.render ? p => staleValues(() => compute(p)) : compute,
1213
+ initialValue,
1214
+ {
1215
+ ...options,
1216
+ equals: () => {
1217
+ node._modified = !node._error;
1218
+ if (initialized) node._queue.enqueue(node._type, runEffect.bind(node));
1219
+ return false;
1220
+ },
1221
+ lazy: true
1222
+ }
1223
+ );
1224
+ node._prevValue = initialValue;
1225
+ node._effectFn = effect;
1226
+ node._errorFn = error;
1227
+ node._cleanup = undefined;
1228
+ node._type = options?.render ? EFFECT_RENDER : EFFECT_USER;
1229
+ node._notifyStatus = () => {
1230
+ if (node._statusFlags & STATUS_ERROR) {
1231
+ let error = node._error;
1232
+ node._queue.notify(node, STATUS_PENDING, 0);
1233
+ if (node._type === EFFECT_USER) {
1234
+ try {
1235
+ return node._errorFn
1236
+ ? node._errorFn(error, () => {
1237
+ node._cleanup?.();
1238
+ node._cleanup = undefined;
1239
+ })
1240
+ : console.error(error);
1241
+ } catch (e) {
1242
+ error = e;
1076
1243
  }
1077
1244
  }
1078
- }
1079
- });
1080
- initialized = true;
1081
- if (node._type === EFFECT_RENDER) node._fn = p => staleValues(() => compute(p));
1245
+ if (!node._queue.notify(node, STATUS_ERROR, STATUS_ERROR)) throw error;
1246
+ } else if (node._type === EFFECT_RENDER)
1247
+ node._queue.notify(node, STATUS_PENDING | STATUS_ERROR, node._statusFlags);
1248
+ };
1249
+ recompute(node, true);
1082
1250
  !options?.defer &&
1083
- !(node._statusFlags & (STATUS_ERROR | STATUS_PENDING)) &&
1084
1251
  (node._type === EFFECT_USER
1085
1252
  ? node._queue.enqueue(node._type, runEffect.bind(node))
1086
1253
  : runEffect.call(node));
1254
+ initialized = true;
1087
1255
  onCleanup(() => node._cleanup?.());
1088
1256
  if (!node._parent)
1089
1257
  console.warn("Effects created outside a reactive context will never be disposed");
@@ -1101,6 +1269,35 @@ function runEffect() {
1101
1269
  this._modified = false;
1102
1270
  }
1103
1271
  }
1272
+ function trackedEffect(fn, options) {
1273
+ const run = () => {
1274
+ if (!node._modified || node._flags & REACTIVE_DISPOSED) return;
1275
+ node._modified = false;
1276
+ recompute(node);
1277
+ };
1278
+ const node = computed(
1279
+ () => {
1280
+ setLeafEffectActive(true);
1281
+ try {
1282
+ node._cleanup?.();
1283
+ node._cleanup = undefined;
1284
+ node._cleanup = staleValues(fn) || undefined;
1285
+ } finally {
1286
+ setLeafEffectActive(false);
1287
+ }
1288
+ },
1289
+ undefined,
1290
+ { ...options, lazy: true, pureWrite: true }
1291
+ );
1292
+ node._cleanup = undefined;
1293
+ node._modified = true;
1294
+ node._type = EFFECT_TRACKED;
1295
+ node._run = run;
1296
+ node._queue.enqueue(EFFECT_USER, run);
1297
+ onCleanup(() => node._cleanup?.());
1298
+ if (!node._parent)
1299
+ console.warn("Effects created outside a reactive context will never be disposed");
1300
+ }
1104
1301
  function createSignal(first, second, third) {
1105
1302
  if (typeof first === "function") {
1106
1303
  const node = computed(first, second, third);
@@ -1114,18 +1311,20 @@ function createMemo(compute, value, options) {
1114
1311
  return read.bind(null, node);
1115
1312
  }
1116
1313
  function createEffect(compute, effectFn, value, options) {
1117
- void effect(compute, effectFn.effect || effectFn, effectFn.error, value, {
1314
+ effect(compute, effectFn.effect || effectFn, effectFn.error, value, {
1118
1315
  ...options,
1119
1316
  name: options?.name ?? "effect"
1120
1317
  });
1121
1318
  }
1122
1319
  function createRenderEffect(compute, effectFn, value, options) {
1123
- void effect(compute, effectFn, undefined, value, {
1320
+ effect(compute, effectFn, undefined, value, {
1124
1321
  render: true,
1125
1322
  ...{ ...options, name: options?.name ?? "effect" }
1126
1323
  });
1127
1324
  }
1128
- function createTrackedEffect(compute, options) {}
1325
+ function createTrackedEffect(compute, options) {
1326
+ trackedEffect(compute, { ...options, name: options?.name ?? "trackedEffect" });
1327
+ }
1129
1328
  function createReaction(effectFn, options) {
1130
1329
  let cleanup = undefined;
1131
1330
  onCleanup(() => cleanup?.());
@@ -1162,39 +1361,19 @@ function resolve(fn) {
1162
1361
  });
1163
1362
  }
1164
1363
  function createOptimistic(first, second, third) {
1165
- if (typeof first === "function") {
1166
- const node = computed(
1167
- prev => {
1168
- const n = getOwner();
1169
- const value = first(prev);
1170
- if (n._statusFlags & STATUS_UNINITIALIZED) return value;
1171
- n._pendingValue = value;
1172
- return prev;
1173
- },
1174
- second,
1175
- third
1176
- );
1177
- node._optimistic = true;
1178
- return [read.bind(null, node), setSignal.bind(null, node)];
1179
- }
1180
- const node = signal(first, second);
1181
- node._optimistic = true;
1182
- return [
1183
- read.bind(null, node),
1184
- v => {
1185
- node._pendingValue = first;
1186
- return setSignal(node, v);
1187
- }
1188
- ];
1364
+ const node =
1365
+ typeof first === "function"
1366
+ ? optimisticComputed(first, second, third)
1367
+ : optimisticSignal(first, second);
1368
+ return [read.bind(null, node), setSignal.bind(null, node)];
1189
1369
  }
1190
1370
  function onSettled(callback) {
1191
- let cleanup;
1192
- const o = getOwner();
1193
- if (o) onCleanup(() => cleanup?.());
1194
- globalQueue.enqueue(EFFECT_USER, () => {
1195
- cleanup = callback();
1196
- !o && cleanup?.();
1197
- });
1371
+ getOwner()
1372
+ ? createTrackedEffect(() => untrack(callback))
1373
+ : globalQueue.enqueue(EFFECT_USER, () => {
1374
+ const cleanup = callback();
1375
+ cleanup?.();
1376
+ });
1198
1377
  }
1199
1378
  function unwrap(value) {
1200
1379
  return value?.[$TARGET]?.[STORE_NODE] ?? value;
@@ -1363,12 +1542,12 @@ function createProjectionInternal(fn, initialValue = {}, options) {
1363
1542
  const owner = getOwner();
1364
1543
  storeSetter(new Proxy(wrappedStore, writeTraps), s => {
1365
1544
  const value = handleAsync(owner, fn(s), value => {
1366
- value !== wrappedStore &&
1545
+ value !== s &&
1367
1546
  value !== undefined &&
1368
1547
  storeSetter(wrappedStore, reconcile(value, options?.key || "id", options?.all));
1369
1548
  setSignal(owner, undefined);
1370
1549
  });
1371
- value !== wrappedStore &&
1550
+ value !== s &&
1372
1551
  value !== undefined &&
1373
1552
  reconcile(value, options?.key || "id", options?.all)(wrappedStore);
1374
1553
  });
@@ -1383,28 +1562,34 @@ const writeTraps = {
1383
1562
  get(_, prop) {
1384
1563
  let value;
1385
1564
  setWriteOverride(true);
1565
+ setProjectionWriteActive(true);
1386
1566
  try {
1387
1567
  value = _[prop];
1388
1568
  } finally {
1389
1569
  setWriteOverride(false);
1570
+ setProjectionWriteActive(false);
1390
1571
  }
1391
1572
  return typeof value === "object" && value !== null ? new Proxy(value, writeTraps) : value;
1392
1573
  },
1393
1574
  set(_, prop, value) {
1394
1575
  setWriteOverride(true);
1576
+ setProjectionWriteActive(true);
1395
1577
  try {
1396
1578
  _[prop] = value;
1397
1579
  } finally {
1398
1580
  setWriteOverride(false);
1581
+ setProjectionWriteActive(false);
1399
1582
  }
1400
1583
  return true;
1401
1584
  },
1402
1585
  deleteProperty(_, prop) {
1403
1586
  setWriteOverride(true);
1587
+ setProjectionWriteActive(true);
1404
1588
  try {
1405
1589
  delete _[prop];
1406
1590
  } finally {
1407
1591
  setWriteOverride(false);
1592
+ setProjectionWriteActive(false);
1408
1593
  }
1409
1594
  return true;
1410
1595
  }
@@ -1417,11 +1602,13 @@ const $TRACK = Symbol("STORE_TRACK"),
1417
1602
  const PARENTS = new WeakMap();
1418
1603
  const STORE_VALUE = "v",
1419
1604
  STORE_OVERRIDE = "o",
1605
+ STORE_OPTIMISTIC_OVERRIDE = "x",
1420
1606
  STORE_NODE = "n",
1421
1607
  STORE_HAS = "h",
1422
1608
  STORE_WRAP = "w",
1423
1609
  STORE_LOOKUP = "l",
1424
- STORE_FIREWALL = "f";
1610
+ STORE_FIREWALL = "f",
1611
+ STORE_OPTIMISTIC = "p";
1425
1612
  function createStoreProxy(value, traps = storeTraps, extend) {
1426
1613
  let newTarget;
1427
1614
  if (Array.isArray(value)) {
@@ -1453,9 +1640,9 @@ function getNodes(target, type) {
1453
1640
  if (!nodes) target[type] = nodes = Object.create(null);
1454
1641
  return nodes;
1455
1642
  }
1456
- function getNode(nodes, property, value, firewall, equals = isEqual) {
1643
+ function getNode(nodes, property, value, firewall, equals = isEqual, optimistic) {
1457
1644
  if (nodes[property]) return nodes[property];
1458
- return (nodes[property] = signal(
1645
+ const s = signal(
1459
1646
  value,
1460
1647
  {
1461
1648
  equals: equals,
@@ -1464,11 +1651,22 @@ function getNode(nodes, property, value, firewall, equals = isEqual) {
1464
1651
  }
1465
1652
  },
1466
1653
  firewall
1467
- ));
1654
+ );
1655
+ if (optimistic) s._optimistic = true;
1656
+ return (nodes[property] = s);
1468
1657
  }
1469
1658
  function trackSelf(target, symbol = $TRACK) {
1470
1659
  getObserver() &&
1471
- read(getNode(getNodes(target, STORE_NODE), symbol, undefined, target[STORE_FIREWALL], false));
1660
+ read(
1661
+ getNode(
1662
+ getNodes(target, STORE_NODE),
1663
+ symbol,
1664
+ undefined,
1665
+ target[STORE_FIREWALL],
1666
+ false,
1667
+ target[STORE_OPTIMISTIC]
1668
+ )
1669
+ );
1472
1670
  }
1473
1671
  function getKeys(source, override, enumerable = true) {
1474
1672
  const baseKeys = untrack(() => (enumerable ? Object.keys(source) : Reflect.ownKeys(source)));
@@ -1501,9 +1699,16 @@ const storeTraps = {
1501
1699
  }
1502
1700
  const nodes = getNodes(target, STORE_NODE);
1503
1701
  const tracked = nodes[property];
1504
- const overridden = target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE];
1702
+ const optOverridden =
1703
+ target[STORE_OPTIMISTIC_OVERRIDE] && property in target[STORE_OPTIMISTIC_OVERRIDE];
1704
+ const overridden =
1705
+ optOverridden || (target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]);
1505
1706
  const proxySource = !!target[STORE_VALUE][$TARGET];
1506
- const storeValue = overridden ? target[STORE_OVERRIDE] : target[STORE_VALUE];
1707
+ const storeValue = optOverridden
1708
+ ? target[STORE_OPTIMISTIC_OVERRIDE]
1709
+ : target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1710
+ ? target[STORE_OVERRIDE]
1711
+ : target[STORE_VALUE];
1507
1712
  if (!tracked) {
1508
1713
  const desc = Object.getOwnPropertyDescriptor(storeValue, property);
1509
1714
  if (desc && desc.get) return desc.get.call(receiver);
@@ -1512,7 +1717,9 @@ const storeTraps = {
1512
1717
  let value =
1513
1718
  tracked && (overridden || !proxySource)
1514
1719
  ? tracked._pendingValue !== NOT_PENDING
1515
- ? tracked._pendingValue
1720
+ ? tracked._optimistic
1721
+ ? tracked._value
1722
+ : tracked._pendingValue
1516
1723
  : tracked._value
1517
1724
  : storeValue[property];
1518
1725
  value === $DELETED && (value = undefined);
@@ -1541,7 +1748,9 @@ const storeTraps = {
1541
1748
  nodes,
1542
1749
  property,
1543
1750
  isWrappable(value) ? wrap(value, target) : value,
1544
- target[STORE_FIREWALL]
1751
+ target[STORE_FIREWALL],
1752
+ isEqual,
1753
+ target[STORE_OPTIMISTIC]
1545
1754
  )
1546
1755
  );
1547
1756
  }
@@ -1551,30 +1760,53 @@ const storeTraps = {
1551
1760
  has(target, property) {
1552
1761
  if (property === $PROXY || property === $TRACK || property === "__proto__") return true;
1553
1762
  const has =
1554
- target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1555
- ? target[STORE_OVERRIDE][property] !== $DELETED
1556
- : property in target[STORE_VALUE];
1763
+ target[STORE_OPTIMISTIC_OVERRIDE] && property in target[STORE_OPTIMISTIC_OVERRIDE]
1764
+ ? target[STORE_OPTIMISTIC_OVERRIDE][property] !== $DELETED
1765
+ : target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1766
+ ? target[STORE_OVERRIDE][property] !== $DELETED
1767
+ : property in target[STORE_VALUE];
1557
1768
  getObserver() &&
1558
- read(getNode(getNodes(target, STORE_HAS), property, has, target[STORE_FIREWALL]));
1769
+ read(
1770
+ getNode(
1771
+ getNodes(target, STORE_HAS),
1772
+ property,
1773
+ has,
1774
+ target[STORE_FIREWALL],
1775
+ isEqual,
1776
+ target[STORE_OPTIMISTIC]
1777
+ )
1778
+ );
1559
1779
  return has;
1560
1780
  },
1561
1781
  set(target, property, rawValue) {
1562
1782
  const store = target[$PROXY];
1563
1783
  if (writeOnly(store)) {
1784
+ if (target[STORE_OPTIMISTIC]) {
1785
+ const firewall = target[STORE_FIREWALL];
1786
+ if (firewall?._transition) {
1787
+ globalQueue.initTransition(firewall._transition);
1788
+ }
1789
+ }
1564
1790
  untrack(() => {
1565
1791
  const state = target[STORE_VALUE];
1566
1792
  const base = state[property];
1793
+ const useOptimistic = target[STORE_OPTIMISTIC] && !projectionWriteActive;
1794
+ const overrideKey = useOptimistic ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
1795
+ if (useOptimistic) trackOptimisticStore(store);
1567
1796
  const prev =
1568
- target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1569
- ? target[STORE_OVERRIDE][property]
1570
- : base;
1797
+ target[STORE_OPTIMISTIC_OVERRIDE] && property in target[STORE_OPTIMISTIC_OVERRIDE]
1798
+ ? target[STORE_OPTIMISTIC_OVERRIDE][property]
1799
+ : target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1800
+ ? target[STORE_OVERRIDE][property]
1801
+ : base;
1571
1802
  const value = rawValue?.[$TARGET]?.[STORE_VALUE] ?? rawValue;
1572
1803
  if (prev === value) return true;
1573
- const len = target[STORE_OVERRIDE]?.length || state.length;
1574
- if (value !== undefined && value === base) delete target[STORE_OVERRIDE][property];
1575
- else
1576
- (target[STORE_OVERRIDE] || (target[STORE_OVERRIDE] = Object.create(null)))[property] =
1577
- value;
1804
+ const len =
1805
+ target[STORE_OPTIMISTIC_OVERRIDE]?.length ||
1806
+ target[STORE_OVERRIDE]?.length ||
1807
+ state.length;
1808
+ if (value !== undefined && value === base) delete target[overrideKey][property];
1809
+ else (target[overrideKey] || (target[overrideKey] = Object.create(null)))[property] = value;
1578
1810
  const wrappable = isWrappable(value);
1579
1811
  if (isWrappable(prev)) {
1580
1812
  const parents = PARENTS.get(prev);
@@ -1586,8 +1818,12 @@ const storeTraps = {
1586
1818
  nodes[property] &&
1587
1819
  setSignal(nodes[property], () => (wrappable ? wrap(value, target) : value));
1588
1820
  if (Array.isArray(state)) {
1589
- const index = parseInt(property) + 1;
1590
- if (index > len) nodes.length && setSignal(nodes.length, index);
1821
+ if (property === "length") {
1822
+ nodes.length && setSignal(nodes.length, value);
1823
+ } else {
1824
+ const index = parseInt(property) + 1;
1825
+ if (index > len) nodes.length && setSignal(nodes.length, index);
1826
+ }
1591
1827
  }
1592
1828
  nodes[$TRACK] && setSignal(nodes[$TRACK], undefined);
1593
1829
  });
@@ -1595,17 +1831,26 @@ const storeTraps = {
1595
1831
  return true;
1596
1832
  },
1597
1833
  deleteProperty(target, property) {
1598
- if (writeOnly(target[$PROXY]) && target[STORE_OVERRIDE]?.[property] !== $DELETED) {
1834
+ const optDeleted = target[STORE_OPTIMISTIC_OVERRIDE]?.[property] === $DELETED;
1835
+ const regDeleted = target[STORE_OVERRIDE]?.[property] === $DELETED;
1836
+ if (writeOnly(target[$PROXY]) && !optDeleted && !regDeleted) {
1599
1837
  untrack(() => {
1838
+ const useOptimistic = target[STORE_OPTIMISTIC] && !projectionWriteActive;
1839
+ const overrideKey = useOptimistic ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
1840
+ if (useOptimistic) trackOptimisticStore(target[$PROXY]);
1600
1841
  const prev =
1601
- target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1602
- ? target[STORE_OVERRIDE][property]
1603
- : target[STORE_VALUE][property];
1604
- if (property in target[STORE_VALUE]) {
1605
- (target[STORE_OVERRIDE] || (target[STORE_OVERRIDE] = Object.create(null)))[property] =
1606
- $DELETED;
1607
- } else if (target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]) {
1608
- delete target[STORE_OVERRIDE][property];
1842
+ target[STORE_OPTIMISTIC_OVERRIDE] && property in target[STORE_OPTIMISTIC_OVERRIDE]
1843
+ ? target[STORE_OPTIMISTIC_OVERRIDE][property]
1844
+ : target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1845
+ ? target[STORE_OVERRIDE][property]
1846
+ : target[STORE_VALUE][property];
1847
+ if (
1848
+ property in target[STORE_VALUE] ||
1849
+ (target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE])
1850
+ ) {
1851
+ (target[overrideKey] || (target[overrideKey] = Object.create(null)))[property] = $DELETED;
1852
+ } else if (target[overrideKey] && property in target[overrideKey]) {
1853
+ delete target[overrideKey][property];
1609
1854
  } else return true;
1610
1855
  if (isWrappable(prev)) {
1611
1856
  const parents = PARENTS.get(prev);
@@ -1621,10 +1866,32 @@ const storeTraps = {
1621
1866
  },
1622
1867
  ownKeys(target) {
1623
1868
  trackSelf(target);
1624
- return getKeys(target[STORE_VALUE], target[STORE_OVERRIDE], false);
1869
+ let keys = getKeys(target[STORE_VALUE], target[STORE_OVERRIDE], false);
1870
+ if (target[STORE_OPTIMISTIC_OVERRIDE]) {
1871
+ const keySet = new Set(keys);
1872
+ for (const key of Reflect.ownKeys(target[STORE_OPTIMISTIC_OVERRIDE])) {
1873
+ if (target[STORE_OPTIMISTIC_OVERRIDE][key] !== $DELETED) keySet.add(key);
1874
+ else keySet.delete(key);
1875
+ }
1876
+ keys = Array.from(keySet);
1877
+ }
1878
+ return keys;
1625
1879
  },
1626
1880
  getOwnPropertyDescriptor(target, property) {
1627
1881
  if (property === $PROXY) return { value: target[$PROXY], writable: true, configurable: true };
1882
+ if (target[STORE_OPTIMISTIC_OVERRIDE] && property in target[STORE_OPTIMISTIC_OVERRIDE]) {
1883
+ if (target[STORE_OPTIMISTIC_OVERRIDE][property] === $DELETED) return undefined;
1884
+ const baseDesc = getPropertyDescriptor(target[STORE_VALUE], target[STORE_OVERRIDE], property);
1885
+ if (baseDesc) {
1886
+ return { ...baseDesc, value: target[STORE_OPTIMISTIC_OVERRIDE][property] };
1887
+ }
1888
+ return {
1889
+ value: target[STORE_OPTIMISTIC_OVERRIDE][property],
1890
+ writable: true,
1891
+ enumerable: true,
1892
+ configurable: true
1893
+ };
1894
+ }
1628
1895
  return getPropertyDescriptor(target[STORE_VALUE], target[STORE_OVERRIDE], property);
1629
1896
  },
1630
1897
  getPrototypeOf(target) {
@@ -1713,7 +1980,82 @@ function deep(store) {
1713
1980
  return store[$DEEP];
1714
1981
  }
1715
1982
  function createOptimisticStore(first, second, options) {
1716
- return [];
1983
+ GlobalQueue._clearOptimisticStore ||= clearOptimisticStore;
1984
+ const derived = typeof first === "function";
1985
+ const initialValue = (derived ? second : first) ?? {};
1986
+ const fn = derived ? first : undefined;
1987
+ const { store: wrappedStore } = createOptimisticProjectionInternal(fn, initialValue, options);
1988
+ return [wrappedStore, fn => storeSetter(wrappedStore, fn)];
1989
+ }
1990
+ function clearOptimisticStore(store) {
1991
+ const target = store[$TARGET];
1992
+ if (!target || !target[STORE_OPTIMISTIC_OVERRIDE]) return;
1993
+ const override = target[STORE_OPTIMISTIC_OVERRIDE];
1994
+ const nodes = target[STORE_NODE];
1995
+ if (nodes) {
1996
+ for (const key of Reflect.ownKeys(override)) {
1997
+ if (nodes[key]) {
1998
+ const baseValue =
1999
+ target[STORE_OVERRIDE] && key in target[STORE_OVERRIDE]
2000
+ ? target[STORE_OVERRIDE][key]
2001
+ : target[STORE_VALUE][key];
2002
+ const value = baseValue === $DELETED ? undefined : baseValue;
2003
+ setSignal(nodes[key], isWrappable(value) ? wrap(value, target) : value);
2004
+ }
2005
+ }
2006
+ nodes[$TRACK] && setSignal(nodes[$TRACK], undefined);
2007
+ }
2008
+ delete target[STORE_OPTIMISTIC_OVERRIDE];
2009
+ }
2010
+ function createOptimisticProjectionInternal(fn, initialValue = {}, options) {
2011
+ let node;
2012
+ const wrappedMap = new WeakMap();
2013
+ const wrapper = s => {
2014
+ s[STORE_WRAP] = wrapProjection;
2015
+ s[STORE_LOOKUP] = wrappedMap;
2016
+ s[STORE_OPTIMISTIC] = true;
2017
+ Object.defineProperty(s, STORE_FIREWALL, {
2018
+ get() {
2019
+ return node;
2020
+ },
2021
+ configurable: true
2022
+ });
2023
+ };
2024
+ const wrapProjection = source => {
2025
+ if (wrappedMap.has(source)) return wrappedMap.get(source);
2026
+ if (source[$TARGET]?.[STORE_WRAP] === wrapProjection) return source;
2027
+ const wrapped = createStoreProxy(source, storeTraps, wrapper);
2028
+ wrappedMap.set(source, wrapped);
2029
+ return wrapped;
2030
+ };
2031
+ const wrappedStore = wrapProjection(initialValue);
2032
+ if (fn) {
2033
+ node = computed(() => {
2034
+ const owner = getOwner();
2035
+ setProjectionWriteActive(true);
2036
+ try {
2037
+ storeSetter(new Proxy(wrappedStore, writeTraps), s => {
2038
+ const value = handleAsync(owner, fn(s), value => {
2039
+ setProjectionWriteActive(true);
2040
+ try {
2041
+ value !== s &&
2042
+ value !== undefined &&
2043
+ storeSetter(wrappedStore, reconcile(value, options?.key || "id", options?.all));
2044
+ } finally {
2045
+ setProjectionWriteActive(false);
2046
+ }
2047
+ });
2048
+ value !== s &&
2049
+ value !== undefined &&
2050
+ reconcile(value, options?.key || "id", options?.all)(wrappedStore);
2051
+ });
2052
+ } finally {
2053
+ setProjectionWriteActive(false);
2054
+ }
2055
+ });
2056
+ node._preventAutoDisposal = true;
2057
+ }
2058
+ return { store: wrappedStore, node: node };
1717
2059
  }
1718
2060
  function snapshot(item, map, lookup) {
1719
2061
  let target, isArray, override, result, unwrapped, v;
@@ -2108,21 +2450,15 @@ function compare(key, a, b) {
2108
2450
  return key ? key(a) === key(b) : true;
2109
2451
  }
2110
2452
  function boundaryComputed(fn, propagationMask) {
2111
- const node = computed(fn, undefined, {
2112
- _internal: {
2113
- _notifyQueue() {
2114
- let flags = this._statusFlags;
2115
- this._statusFlags &= ~this._propagationMask;
2116
- if (this._propagationMask & STATUS_PENDING && !(this._statusFlags & STATUS_UNINITIALIZED)) {
2117
- flags &= ~STATUS_PENDING;
2118
- }
2119
- this._queue.notify(this, this._propagationMask, flags);
2120
- },
2121
- _propagationMask: propagationMask
2122
- }
2123
- });
2453
+ const node = computed(fn, undefined, { lazy: true });
2454
+ node._notifyStatus = () => {
2455
+ const flags = node._statusFlags;
2456
+ node._statusFlags &= ~node._propagationMask;
2457
+ node._queue.notify(node, node._propagationMask, flags);
2458
+ };
2124
2459
  node._propagationMask = propagationMask;
2125
2460
  node._preventAutoDisposal = true;
2461
+ recompute(node, true);
2126
2462
  return node;
2127
2463
  }
2128
2464
  function createBoundChildren(owner, fn, queue, mask) {
@@ -2222,8 +2558,10 @@ function createCollectionBoundary(type, fn, fallback) {
2222
2558
  const decision = computed(() => {
2223
2559
  if (!read(queue._disabled)) {
2224
2560
  const resolved = read(tree);
2225
- if (!untrack(() => read(queue._disabled))) queue._initialized = true;
2226
- return resolved;
2561
+ if (!untrack(() => read(queue._disabled))) {
2562
+ queue._initialized = true;
2563
+ return resolved;
2564
+ }
2227
2565
  }
2228
2566
  return fallback(queue);
2229
2567
  });