@solidjs/signals 0.9.5 → 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,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 | (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
+ }
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,40 @@ 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
+ 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
+ }
778
953
  function isEqual(a, b) {
779
954
  return a === b;
780
955
  }
@@ -788,10 +963,30 @@ function untrack(fn) {
788
963
  }
789
964
  }
790
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
+ }
791
986
  let c = context;
792
987
  if (c?._root) c = c._parentComputed;
793
988
  if (refreshing && el._fn) recompute(el);
794
- if (c && tracking && !pendingCheck && !pendingValueCheck) {
989
+ if (c && tracking) {
795
990
  if (el._fn && el._flags & REACTIVE_DISPOSED) recompute(el);
796
991
  link(el, c);
797
992
  const owner = el._firewall || el;
@@ -808,51 +1003,24 @@ function read(el) {
808
1003
  }
809
1004
  }
810
1005
  }
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
1006
  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) {
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) {
843
1015
  if (el._time < clock) {
844
1016
  recompute(el, true);
845
1017
  return read(el);
846
- } else {
847
- throw el._error;
848
- }
1018
+ } else throw el._error;
849
1019
  }
850
1020
  return !c ||
851
- el._optimistic ||
1021
+ optimisticReadActive ||
852
1022
  el._pendingValue === NOT_PENDING ||
853
- (stale &&
854
- !pendingCheck &&
855
- (c._optimistic || (el._transition && activeTransition !== el._transition)))
1023
+ (stale && el._transition && activeTransition !== el._transition)
856
1024
  ? el._value
857
1025
  : el._pendingValue;
858
1026
  }
@@ -861,37 +1029,41 @@ function setSignal(el, v) {
861
1029
  console.warn("A Signal was written to in an owned scope.");
862
1030
  if (el._transition && activeTransition !== el._transition)
863
1031
  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;
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);
885
1045
  }
886
- if (el._pendingSignal) setSignal(el._pendingSignal, v);
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;
1054
+ }
1055
+ updatePendingSignal(el);
1056
+ if (el._pendingValueComputed) {
1057
+ setSignal(el._pendingValueComputed, v);
887
1058
  }
888
- setStatusFlags(el, STATUS_NONE);
889
1059
  el._time = clock;
890
- el._optimistic && !optimisticRun ? globalQueue._optimisticNodes.push(el) : notifySubs(el);
1060
+ insertSubs(el, isOptimistic);
891
1061
  schedule();
892
1062
  return v;
893
1063
  }
1064
+ const PENDING_OWNER = {};
894
1065
  function getObserver() {
1066
+ if (pendingCheckActive || pendingReadActive) return PENDING_OWNER;
895
1067
  return tracking ? context : null;
896
1068
  }
897
1069
  function getOwner() {
@@ -899,25 +1071,20 @@ function getOwner() {
899
1071
  }
900
1072
  function onCleanup(fn) {
901
1073
  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
- }
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];
910
1077
  return fn;
911
1078
  }
912
1079
  function createOwner(options) {
913
1080
  const parent = context;
914
1081
  const owner = {
1082
+ id: options?.id ?? (parent?.id != null ? getNextChildId(parent) : undefined),
915
1083
  _root: true,
916
1084
  _parentComputed: parent?._root ? parent._parentComputed : parent,
917
1085
  _firstChild: null,
918
1086
  _nextSibling: null,
919
1087
  _disposal: null,
920
- id: options?.id ?? (parent?.id != null ? getNextChildId(parent) : undefined),
921
1088
  _queue: parent?._queue ?? globalQueue,
922
1089
  _context: parent?._context || defaultContext,
923
1090
  _childCount: 0,
@@ -928,6 +1095,9 @@ function createOwner(options) {
928
1095
  disposeChildren(owner, self);
929
1096
  }
930
1097
  };
1098
+ if (leafEffectActive && parent) {
1099
+ throw new Error("Cannot create reactive primitives inside createTrackedEffect");
1100
+ }
931
1101
  if (parent) {
932
1102
  const lastChild = parent._firstChild;
933
1103
  if (lastChild === null) {
@@ -965,25 +1135,25 @@ function staleValues(fn, set = true) {
965
1135
  }
966
1136
  }
967
1137
  function pending(fn) {
968
- const prevLatest = pendingValueCheck;
969
- pendingValueCheck = true;
1138
+ const prevPending = pendingReadActive;
1139
+ pendingReadActive = true;
970
1140
  try {
971
- return staleValues(fn, false);
1141
+ return fn();
972
1142
  } finally {
973
- pendingValueCheck = prevLatest;
1143
+ pendingReadActive = prevPending;
974
1144
  }
975
1145
  }
976
1146
  function isPending(fn) {
977
- const current = pendingCheck;
978
- pendingCheck = { _value: false };
1147
+ const prevPendingCheck = pendingCheckActive;
1148
+ const prevFoundPending = foundPending;
1149
+ pendingCheckActive = true;
1150
+ foundPending = false;
979
1151
  try {
980
- staleValues(fn);
981
- return pendingCheck._value;
982
- } catch (err) {
983
- if (!(err instanceof NotReadyError)) return false;
984
- throw err;
1152
+ fn();
1153
+ return foundPending;
985
1154
  } finally {
986
- pendingCheck = current;
1155
+ pendingCheckActive = prevPendingCheck;
1156
+ foundPending = prevFoundPending;
987
1157
  }
988
1158
  }
989
1159
  function refresh(fn) {
@@ -999,7 +1169,6 @@ function refresh(fn) {
999
1169
  refreshing = prevRefreshing;
1000
1170
  if (!prevRefreshing) {
1001
1171
  schedule();
1002
- flush();
1003
1172
  }
1004
1173
  }
1005
1174
  }
@@ -1036,54 +1205,50 @@ function isUndefined(value) {
1036
1205
  }
1037
1206
  function effect(compute, effect, error, initialValue, options) {
1038
1207
  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);
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;
1076
1240
  }
1077
1241
  }
1078
- }
1079
- });
1080
- initialized = true;
1081
- 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);
1082
1247
  !options?.defer &&
1083
- !(node._statusFlags & (STATUS_ERROR | STATUS_PENDING)) &&
1084
1248
  (node._type === EFFECT_USER
1085
1249
  ? node._queue.enqueue(node._type, runEffect.bind(node))
1086
1250
  : runEffect.call(node));
1251
+ initialized = true;
1087
1252
  onCleanup(() => node._cleanup?.());
1088
1253
  if (!node._parent)
1089
1254
  console.warn("Effects created outside a reactive context will never be disposed");
@@ -1101,6 +1266,35 @@ function runEffect() {
1101
1266
  this._modified = false;
1102
1267
  }
1103
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
+ }
1104
1298
  function createSignal(first, second, third) {
1105
1299
  if (typeof first === "function") {
1106
1300
  const node = computed(first, second, third);
@@ -1114,18 +1308,20 @@ function createMemo(compute, value, options) {
1114
1308
  return read.bind(null, node);
1115
1309
  }
1116
1310
  function createEffect(compute, effectFn, value, options) {
1117
- void effect(compute, effectFn.effect || effectFn, effectFn.error, value, {
1311
+ effect(compute, effectFn.effect || effectFn, effectFn.error, value, {
1118
1312
  ...options,
1119
1313
  name: options?.name ?? "effect"
1120
1314
  });
1121
1315
  }
1122
1316
  function createRenderEffect(compute, effectFn, value, options) {
1123
- void effect(compute, effectFn, undefined, value, {
1317
+ effect(compute, effectFn, undefined, value, {
1124
1318
  render: true,
1125
1319
  ...{ ...options, name: options?.name ?? "effect" }
1126
1320
  });
1127
1321
  }
1128
- function createTrackedEffect(compute, options) {}
1322
+ function createTrackedEffect(compute, options) {
1323
+ trackedEffect(compute, { ...options, name: options?.name ?? "trackedEffect" });
1324
+ }
1129
1325
  function createReaction(effectFn, options) {
1130
1326
  let cleanup = undefined;
1131
1327
  onCleanup(() => cleanup?.());
@@ -1162,39 +1358,19 @@ function resolve(fn) {
1162
1358
  });
1163
1359
  }
1164
1360
  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
- ];
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)];
1189
1366
  }
1190
1367
  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
- });
1368
+ getOwner()
1369
+ ? createTrackedEffect(() => untrack(callback))
1370
+ : globalQueue.enqueue(EFFECT_USER, () => {
1371
+ const cleanup = callback();
1372
+ cleanup?.();
1373
+ });
1198
1374
  }
1199
1375
  function unwrap(value) {
1200
1376
  return value?.[$TARGET]?.[STORE_NODE] ?? value;
@@ -1363,12 +1539,12 @@ function createProjectionInternal(fn, initialValue = {}, options) {
1363
1539
  const owner = getOwner();
1364
1540
  storeSetter(new Proxy(wrappedStore, writeTraps), s => {
1365
1541
  const value = handleAsync(owner, fn(s), value => {
1366
- value !== wrappedStore &&
1542
+ value !== s &&
1367
1543
  value !== undefined &&
1368
1544
  storeSetter(wrappedStore, reconcile(value, options?.key || "id", options?.all));
1369
1545
  setSignal(owner, undefined);
1370
1546
  });
1371
- value !== wrappedStore &&
1547
+ value !== s &&
1372
1548
  value !== undefined &&
1373
1549
  reconcile(value, options?.key || "id", options?.all)(wrappedStore);
1374
1550
  });
@@ -1383,28 +1559,34 @@ const writeTraps = {
1383
1559
  get(_, prop) {
1384
1560
  let value;
1385
1561
  setWriteOverride(true);
1562
+ setProjectionWriteActive(true);
1386
1563
  try {
1387
1564
  value = _[prop];
1388
1565
  } finally {
1389
1566
  setWriteOverride(false);
1567
+ setProjectionWriteActive(false);
1390
1568
  }
1391
1569
  return typeof value === "object" && value !== null ? new Proxy(value, writeTraps) : value;
1392
1570
  },
1393
1571
  set(_, prop, value) {
1394
1572
  setWriteOverride(true);
1573
+ setProjectionWriteActive(true);
1395
1574
  try {
1396
1575
  _[prop] = value;
1397
1576
  } finally {
1398
1577
  setWriteOverride(false);
1578
+ setProjectionWriteActive(false);
1399
1579
  }
1400
1580
  return true;
1401
1581
  },
1402
1582
  deleteProperty(_, prop) {
1403
1583
  setWriteOverride(true);
1584
+ setProjectionWriteActive(true);
1404
1585
  try {
1405
1586
  delete _[prop];
1406
1587
  } finally {
1407
1588
  setWriteOverride(false);
1589
+ setProjectionWriteActive(false);
1408
1590
  }
1409
1591
  return true;
1410
1592
  }
@@ -1417,11 +1599,13 @@ const $TRACK = Symbol("STORE_TRACK"),
1417
1599
  const PARENTS = new WeakMap();
1418
1600
  const STORE_VALUE = "v",
1419
1601
  STORE_OVERRIDE = "o",
1602
+ STORE_OPTIMISTIC_OVERRIDE = "x",
1420
1603
  STORE_NODE = "n",
1421
1604
  STORE_HAS = "h",
1422
1605
  STORE_WRAP = "w",
1423
1606
  STORE_LOOKUP = "l",
1424
- STORE_FIREWALL = "f";
1607
+ STORE_FIREWALL = "f",
1608
+ STORE_OPTIMISTIC = "p";
1425
1609
  function createStoreProxy(value, traps = storeTraps, extend) {
1426
1610
  let newTarget;
1427
1611
  if (Array.isArray(value)) {
@@ -1453,9 +1637,9 @@ function getNodes(target, type) {
1453
1637
  if (!nodes) target[type] = nodes = Object.create(null);
1454
1638
  return nodes;
1455
1639
  }
1456
- function getNode(nodes, property, value, firewall, equals = isEqual) {
1640
+ function getNode(nodes, property, value, firewall, equals = isEqual, optimistic) {
1457
1641
  if (nodes[property]) return nodes[property];
1458
- return (nodes[property] = signal(
1642
+ const s = signal(
1459
1643
  value,
1460
1644
  {
1461
1645
  equals: equals,
@@ -1464,11 +1648,22 @@ function getNode(nodes, property, value, firewall, equals = isEqual) {
1464
1648
  }
1465
1649
  },
1466
1650
  firewall
1467
- ));
1651
+ );
1652
+ if (optimistic) s._optimistic = true;
1653
+ return (nodes[property] = s);
1468
1654
  }
1469
1655
  function trackSelf(target, symbol = $TRACK) {
1470
1656
  getObserver() &&
1471
- 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
+ );
1472
1667
  }
1473
1668
  function getKeys(source, override, enumerable = true) {
1474
1669
  const baseKeys = untrack(() => (enumerable ? Object.keys(source) : Reflect.ownKeys(source)));
@@ -1501,9 +1696,16 @@ const storeTraps = {
1501
1696
  }
1502
1697
  const nodes = getNodes(target, STORE_NODE);
1503
1698
  const tracked = nodes[property];
1504
- 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]);
1505
1703
  const proxySource = !!target[STORE_VALUE][$TARGET];
1506
- 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];
1507
1709
  if (!tracked) {
1508
1710
  const desc = Object.getOwnPropertyDescriptor(storeValue, property);
1509
1711
  if (desc && desc.get) return desc.get.call(receiver);
@@ -1512,7 +1714,9 @@ const storeTraps = {
1512
1714
  let value =
1513
1715
  tracked && (overridden || !proxySource)
1514
1716
  ? tracked._pendingValue !== NOT_PENDING
1515
- ? tracked._pendingValue
1717
+ ? tracked._optimistic
1718
+ ? tracked._value
1719
+ : tracked._pendingValue
1516
1720
  : tracked._value
1517
1721
  : storeValue[property];
1518
1722
  value === $DELETED && (value = undefined);
@@ -1541,7 +1745,9 @@ const storeTraps = {
1541
1745
  nodes,
1542
1746
  property,
1543
1747
  isWrappable(value) ? wrap(value, target) : value,
1544
- target[STORE_FIREWALL]
1748
+ target[STORE_FIREWALL],
1749
+ isEqual,
1750
+ target[STORE_OPTIMISTIC]
1545
1751
  )
1546
1752
  );
1547
1753
  }
@@ -1551,30 +1757,53 @@ const storeTraps = {
1551
1757
  has(target, property) {
1552
1758
  if (property === $PROXY || property === $TRACK || property === "__proto__") return true;
1553
1759
  const has =
1554
- target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1555
- ? target[STORE_OVERRIDE][property] !== $DELETED
1556
- : 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];
1557
1765
  getObserver() &&
1558
- 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
+ );
1559
1776
  return has;
1560
1777
  },
1561
1778
  set(target, property, rawValue) {
1562
1779
  const store = target[$PROXY];
1563
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
+ }
1564
1787
  untrack(() => {
1565
1788
  const state = target[STORE_VALUE];
1566
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);
1567
1793
  const prev =
1568
- target[STORE_OVERRIDE] && property in target[STORE_OVERRIDE]
1569
- ? target[STORE_OVERRIDE][property]
1570
- : 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;
1571
1799
  const value = rawValue?.[$TARGET]?.[STORE_VALUE] ?? rawValue;
1572
1800
  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;
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;
1578
1807
  const wrappable = isWrappable(value);
1579
1808
  if (isWrappable(prev)) {
1580
1809
  const parents = PARENTS.get(prev);
@@ -1586,8 +1815,12 @@ const storeTraps = {
1586
1815
  nodes[property] &&
1587
1816
  setSignal(nodes[property], () => (wrappable ? wrap(value, target) : value));
1588
1817
  if (Array.isArray(state)) {
1589
- const index = parseInt(property) + 1;
1590
- 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
+ }
1591
1824
  }
1592
1825
  nodes[$TRACK] && setSignal(nodes[$TRACK], undefined);
1593
1826
  });
@@ -1595,17 +1828,26 @@ const storeTraps = {
1595
1828
  return true;
1596
1829
  },
1597
1830
  deleteProperty(target, property) {
1598
- 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) {
1599
1834
  untrack(() => {
1835
+ const useOptimistic = target[STORE_OPTIMISTIC] && !projectionWriteActive;
1836
+ const overrideKey = useOptimistic ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
1837
+ if (useOptimistic) trackOptimisticStore(target[$PROXY]);
1600
1838
  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];
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];
1609
1851
  } else return true;
1610
1852
  if (isWrappable(prev)) {
1611
1853
  const parents = PARENTS.get(prev);
@@ -1621,10 +1863,32 @@ const storeTraps = {
1621
1863
  },
1622
1864
  ownKeys(target) {
1623
1865
  trackSelf(target);
1624
- 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;
1625
1876
  },
1626
1877
  getOwnPropertyDescriptor(target, property) {
1627
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
+ }
1628
1892
  return getPropertyDescriptor(target[STORE_VALUE], target[STORE_OVERRIDE], property);
1629
1893
  },
1630
1894
  getPrototypeOf(target) {
@@ -1713,7 +1977,82 @@ function deep(store) {
1713
1977
  return store[$DEEP];
1714
1978
  }
1715
1979
  function createOptimisticStore(first, second, options) {
1716
- 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 };
1717
2056
  }
1718
2057
  function snapshot(item, map, lookup) {
1719
2058
  let target, isArray, override, result, unwrapped, v;
@@ -2108,21 +2447,15 @@ function compare(key, a, b) {
2108
2447
  return key ? key(a) === key(b) : true;
2109
2448
  }
2110
2449
  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
- });
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
+ };
2124
2456
  node._propagationMask = propagationMask;
2125
2457
  node._preventAutoDisposal = true;
2458
+ recompute(node, true);
2126
2459
  return node;
2127
2460
  }
2128
2461
  function createBoundChildren(owner, fn, queue, mask) {
@@ -2222,8 +2555,10 @@ function createCollectionBoundary(type, fn, fallback) {
2222
2555
  const decision = computed(() => {
2223
2556
  if (!read(queue._disabled)) {
2224
2557
  const resolved = read(tree);
2225
- if (!untrack(() => read(queue._disabled))) queue._initialized = true;
2226
- return resolved;
2558
+ if (!untrack(() => read(queue._disabled))) {
2559
+ queue._initialized = true;
2560
+ return resolved;
2561
+ }
2227
2562
  }
2228
2563
  return fallback(queue);
2229
2564
  });