@qwik.dev/core 2.0.0-beta.8 → 2.0.0-beta.9

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/core.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @license
3
- * @qwik.dev/core 2.0.0-beta.8-dev+434cd18
3
+ * @qwik.dev/core 2.0.0-beta.9-dev+6b582c7
4
4
  * Copyright QwikDev. All Rights Reserved.
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://github.com/QwikDev/qwik/blob/main/LICENSE
@@ -11,6 +11,13 @@
11
11
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.qwikCore = {}, global.qwikBuild, global.qwikPreloader));
12
12
  })(this, (function (exports, build, preloader) { 'use strict';
13
13
 
14
+ /**
15
+ * QWIK_VERSION
16
+ *
17
+ * @public
18
+ */
19
+ const version = "2.0.0-beta.9-dev+6b582c7";
20
+
14
21
  // same as isDev but separate so we can test
15
22
  const qDev = globalThis.qDev !== false;
16
23
  const qInspector = globalThis.qInspector === true;
@@ -88,6 +95,9 @@
88
95
  const isString = (v) => {
89
96
  return typeof v === 'string';
90
97
  };
98
+ const isNumber$1 = (v) => {
99
+ return typeof v === 'number';
100
+ };
91
101
  const isFunction = (v) => {
92
102
  return typeof v === 'function';
93
103
  };
@@ -290,13 +300,6 @@
290
300
  });
291
301
  });
292
302
  },
293
- nextTick: (fn) => {
294
- return new Promise((resolve) => {
295
- setTimeout(() => {
296
- resolve(fn());
297
- });
298
- });
299
- },
300
303
  chunkForSymbol(symbolName, chunk) {
301
304
  return [symbolName, chunk ?? '_'];
302
305
  },
@@ -554,6 +557,13 @@
554
557
  this.$container$ = container;
555
558
  this.$untrackedValue$ = value;
556
559
  }
560
+ /**
561
+ * Use this to force running subscribers, for example when the calculated value has mutated but
562
+ * remained the same object
563
+ */
564
+ force() {
565
+ this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, this.$effects$);
566
+ }
557
567
  get untrackedValue() {
558
568
  return this.$untrackedValue$;
559
569
  }
@@ -567,14 +577,7 @@
567
577
  set value(value) {
568
578
  if (value !== this.$untrackedValue$) {
569
579
  this.$untrackedValue$ = value;
570
- // TODO: move this to the scheduler
571
- triggerEffects(this.$container$, this, this.$effects$);
572
- // this.$container$?.$scheduler$(
573
- // ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS,
574
- // null,
575
- // this,
576
- // this.$effects$
577
- // );
580
+ this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, this.$effects$);
578
581
  }
579
582
  }
580
583
  // prevent accidental use as value
@@ -636,12 +639,11 @@
636
639
  $funcStr$;
637
640
  $flags$;
638
641
  $hostElement$ = null;
639
- $forceRunEffects$ = false;
640
642
  [_EFFECT_BACK_REF] = null;
641
643
  constructor(container, fn, args, fnStr,
642
644
  // We need a separate flag to know when the computation needs running because
643
645
  // we need the old value to know if effects need running after computation
644
- flags = 1 /* SignalFlags.INVALID */ | 2 /* WrappedSignalFlags.UNWRAP */) {
646
+ flags = 1 /* SignalFlags.INVALID */ | 4 /* WrappedSignalFlags.UNWRAP */) {
645
647
  super(container, NEEDS_COMPUTATION);
646
648
  this.$args$ = args;
647
649
  this.$func$ = fn;
@@ -650,7 +652,6 @@
650
652
  }
651
653
  invalidate() {
652
654
  this.$flags$ |= 1 /* SignalFlags.INVALID */;
653
- this.$forceRunEffects$ = false;
654
655
  this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, this.$hostElement$, this, this.$effects$);
655
656
  }
656
657
  /**
@@ -658,29 +659,26 @@
658
659
  * remained the same object.
659
660
  */
660
661
  force() {
661
- this.$forceRunEffects$ = true;
662
+ this.$flags$ |= 2 /* SignalFlags.RUN_EFFECTS */;
662
663
  this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, this.$hostElement$, this, this.$effects$);
663
664
  }
664
665
  get untrackedValue() {
665
- const didChange = this.$computeIfNeeded$();
666
- if (didChange) {
667
- this.$forceRunEffects$ = didChange;
668
- }
666
+ this.$computeIfNeeded$();
669
667
  assertFalse(this.$untrackedValue$ === NEEDS_COMPUTATION, 'Invalid state');
670
668
  return this.$untrackedValue$;
671
669
  }
672
670
  $computeIfNeeded$() {
673
671
  if (!(this.$flags$ & 1 /* SignalFlags.INVALID */)) {
674
- return false;
672
+ return;
675
673
  }
676
674
  const untrackedValue = trackSignal(() => this.$func$(...this.$args$), this, "." /* EffectProperty.VNODE */, this.$container$);
677
- // TODO: we should remove invalid flag here
675
+ // TODO: we should remove invalid flag here, but some tests are failing
678
676
  // this.$flags$ &= ~SignalFlags.INVALID;
679
677
  const didChange = untrackedValue !== this.$untrackedValue$;
680
678
  if (didChange) {
679
+ this.$flags$ |= 2 /* SignalFlags.RUN_EFFECTS */;
681
680
  this.$untrackedValue$ = untrackedValue;
682
681
  }
683
- return didChange;
684
682
  }
685
683
  // Make this signal read-only
686
684
  set value(_) {
@@ -875,12 +873,26 @@
875
873
  };
876
874
  /** @internal */
877
875
  const _waitUntilRendered = (elm) => {
878
- const containerEl = _getQContainerElement(elm);
879
- if (!containerEl) {
876
+ const container = _getQContainerElement(elm)?.qContainer;
877
+ if (!container) {
880
878
  return Promise.resolve();
881
879
  }
882
- const container = containerEl.qContainer;
883
- return container?.renderDone ?? Promise.resolve();
880
+ // Multi-cycle idle: loop WAIT_FOR_QUEUE until the flush epoch stays stable
881
+ // across an extra microtask, which signals that no new work re-scheduled.
882
+ return (async () => {
883
+ for (;;) {
884
+ await container.$scheduler$(255 /* ChoreType.WAIT_FOR_QUEUE */).$returnValue$;
885
+ const firstEpoch = container.$flushEpoch$ || 0;
886
+ // Give a microtask for any immediate follow-up scheduling to enqueue
887
+ await Promise.resolve();
888
+ const secondEpoch = container.$flushEpoch$ || 0;
889
+ // If no epoch change occurred during and after WAIT_FOR_QUEUE, we are idle.
890
+ if (firstEpoch === secondEpoch) {
891
+ return;
892
+ }
893
+ // Continue loop if epoch advanced, meaning more work flushed.
894
+ }
895
+ })();
884
896
  };
885
897
 
886
898
  /**
@@ -958,7 +970,7 @@
958
970
  }
959
971
  return null; // Return null if not matching expected format
960
972
  }
961
- function eventNameToJsxEvent(eventName, prefix, startIdx = 0) {
973
+ function eventNameToJsxEvent(eventName, prefix) {
962
974
  eventName = eventName.charAt(0).toUpperCase() + eventName.substring(1);
963
975
  return prefix + eventName + EVENT_SUFFIX;
964
976
  }
@@ -1247,13 +1259,6 @@
1247
1259
  return true;
1248
1260
  };
1249
1261
 
1250
- /**
1251
- * QWIK_VERSION
1252
- *
1253
- * @public
1254
- */
1255
- const version = "2.0.0-beta.8-dev+434cd18";
1256
-
1257
1262
  /** @internal */
1258
1263
  const EMPTY_ARRAY = [];
1259
1264
  const EMPTY_OBJ = {};
@@ -1394,7 +1399,7 @@
1394
1399
  *
1395
1400
  * @internal
1396
1401
  */
1397
- const queueQRL = (...args) => {
1402
+ const _run = (...args) => {
1398
1403
  // This will already check container
1399
1404
  const [runQrl] = useLexicalScope();
1400
1405
  const context = getInvokeContext();
@@ -1408,7 +1413,9 @@
1408
1413
  if (!scheduler) {
1409
1414
  throw qError(1 /* QError.schedulerNotFound */);
1410
1415
  }
1411
- return scheduler(2 /* ChoreType.RUN_QRL */, hostElement, runQrl, args);
1416
+ // We don't return anything, the scheduler is in charge now
1417
+ const chore = scheduler(2 /* ChoreType.RUN_QRL */, hostElement, runQrl, args);
1418
+ return getChorePromise(chore);
1412
1419
  };
1413
1420
 
1414
1421
  /** @internal */
@@ -1532,13 +1539,12 @@
1532
1539
  */
1533
1540
  $computeQrl$;
1534
1541
  $flags$;
1535
- $forceRunEffects$ = false;
1536
1542
  [_EFFECT_BACK_REF] = null;
1537
1543
  constructor(container, fn,
1538
1544
  // We need a separate flag to know when the computation needs running because
1539
1545
  // we need the old value to know if effects need running after computation
1540
1546
  flags = 1 /* SignalFlags.INVALID */ |
1541
- 16 /* ComputedSignalFlags.SERIALIZATION_STRATEGY_ALWAYS */) {
1547
+ 32 /* SerializationSignalFlags.SERIALIZATION_STRATEGY_ALWAYS */) {
1542
1548
  // The value is used for comparison when signals trigger, which can only happen
1543
1549
  // when it was calculated before. Therefore we can pass whatever we like.
1544
1550
  super(container, NEEDS_COMPUTATION);
@@ -1547,7 +1553,6 @@
1547
1553
  }
1548
1554
  invalidate() {
1549
1555
  this.$flags$ |= 1 /* SignalFlags.INVALID */;
1550
- this.$forceRunEffects$ = false;
1551
1556
  this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, this.$effects$);
1552
1557
  }
1553
1558
  /**
@@ -1555,20 +1560,17 @@
1555
1560
  * remained the same object
1556
1561
  */
1557
1562
  force() {
1558
- this.$forceRunEffects$ = true;
1559
- this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, this.$effects$);
1563
+ this.$flags$ |= 2 /* SignalFlags.RUN_EFFECTS */;
1564
+ super.force();
1560
1565
  }
1561
1566
  get untrackedValue() {
1562
- const didChange = this.$computeIfNeeded$();
1563
- if (didChange) {
1564
- this.$forceRunEffects$ = didChange;
1565
- }
1567
+ this.$computeIfNeeded$();
1566
1568
  assertFalse(this.$untrackedValue$ === NEEDS_COMPUTATION, 'Invalid state');
1567
1569
  return this.$untrackedValue$;
1568
1570
  }
1569
1571
  $computeIfNeeded$() {
1570
1572
  if (!(this.$flags$ & 1 /* SignalFlags.INVALID */)) {
1571
- return false;
1573
+ return;
1572
1574
  }
1573
1575
  const computeQrl = this.$computeQrl$;
1574
1576
  throwIfQRLNotResolved(computeQrl);
@@ -1587,9 +1589,12 @@
1587
1589
  this.$flags$ &= ~1 /* SignalFlags.INVALID */;
1588
1590
  const didChange = untrackedValue !== this.$untrackedValue$;
1589
1591
  if (didChange) {
1592
+ // skip first computation when value is not changed
1593
+ if (this.$untrackedValue$ !== NEEDS_COMPUTATION) {
1594
+ this.$flags$ |= 2 /* SignalFlags.RUN_EFFECTS */;
1595
+ }
1590
1596
  this.$untrackedValue$ = untrackedValue;
1591
1597
  }
1592
- return didChange;
1593
1598
  }
1594
1599
  finally {
1595
1600
  if (ctx) {
@@ -1615,12 +1620,12 @@
1615
1620
  */
1616
1621
  class SerializerSignalImpl extends ComputedSignalImpl {
1617
1622
  constructor(container, argQrl) {
1618
- super(container, argQrl, 1 /* SignalFlags.INVALID */ | 16 /* ComputedSignalFlags.SERIALIZATION_STRATEGY_ALWAYS */);
1623
+ super(container, argQrl, 1 /* SignalFlags.INVALID */ | 32 /* SerializationSignalFlags.SERIALIZATION_STRATEGY_ALWAYS */);
1619
1624
  }
1620
1625
  $didInitialize$ = false;
1621
1626
  $computeIfNeeded$() {
1622
1627
  if (!(this.$flags$ & 1 /* SignalFlags.INVALID */)) {
1623
- return false;
1628
+ return;
1624
1629
  }
1625
1630
  throwIfQRLNotResolved(this.$computeQrl$);
1626
1631
  let arg = this.$computeQrl$.resolved;
@@ -1638,9 +1643,9 @@
1638
1643
  this.$flags$ &= -2 /* SignalFlags.INVALID */;
1639
1644
  this.$didInitialize$ = true;
1640
1645
  if (didChange) {
1646
+ this.$flags$ |= 2 /* SignalFlags.RUN_EFFECTS */;
1641
1647
  this.$untrackedValue$ = untrackedValue;
1642
1648
  }
1643
- return didChange;
1644
1649
  }
1645
1650
  }
1646
1651
 
@@ -1650,6 +1655,28 @@
1650
1655
  const getStoreTarget = (value) => {
1651
1656
  return value?.[STORE_TARGET] || null;
1652
1657
  };
1658
+ /**
1659
+ * Force a store to recompute and schedule effects.
1660
+ *
1661
+ * @public
1662
+ */
1663
+ const forceStoreEffects = (value, prop) => {
1664
+ const handler = getStoreHandler(value);
1665
+ if (handler) {
1666
+ handler.force(prop);
1667
+ }
1668
+ };
1669
+ /**
1670
+ * @returns True if the store has effects for the given prop
1671
+ * @internal
1672
+ */
1673
+ const _hasStoreEffects = (value, prop) => {
1674
+ const handler = getStoreHandler(value);
1675
+ if (handler) {
1676
+ return (handler.$effects$?.get(prop)?.size ?? 0) > 0;
1677
+ }
1678
+ return false;
1679
+ };
1653
1680
  /**
1654
1681
  * Get the original object that was wrapped by the store. Useful if you want to clone a store
1655
1682
  * (structuredClone, IndexedDB,...)
@@ -1688,7 +1715,12 @@
1688
1715
  toString() {
1689
1716
  return '[Store]';
1690
1717
  }
1718
+ force(prop) {
1719
+ const target = getStoreTarget(this);
1720
+ this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, getEffects(target, prop, this.$effects$));
1721
+ }
1691
1722
  get(target, prop) {
1723
+ // TODO(perf): handle better `slice` calls
1692
1724
  if (typeof prop === 'symbol') {
1693
1725
  if (prop === STORE_TARGET) {
1694
1726
  return target;
@@ -1751,7 +1783,11 @@
1751
1783
  if (typeof prop != 'string' || !delete target[prop]) {
1752
1784
  return false;
1753
1785
  }
1754
- triggerEffects(this.$container$, this, getEffects(target, prop, this.$effects$));
1786
+ if (!Array.isArray(target)) {
1787
+ // If the target is an array, we don't need to trigger effects.
1788
+ // Changing the length property will trigger effects.
1789
+ this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, getEffects(target, prop, this.$effects$));
1790
+ }
1755
1791
  return true;
1756
1792
  }
1757
1793
  has(target, prop) {
@@ -1810,8 +1846,10 @@
1810
1846
  }
1811
1847
  function setNewValueAndTriggerEffects(prop, value, target, currentStore) {
1812
1848
  target[prop] = value;
1813
- // TODO: trigger effects through the scheduler
1814
- triggerEffects(currentStore.$container$, currentStore, getEffects(target, prop, currentStore.$effects$));
1849
+ const effects = getEffects(target, prop, currentStore.$effects$);
1850
+ if (effects) {
1851
+ currentStore.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, currentStore, effects);
1852
+ }
1815
1853
  }
1816
1854
  function getEffects(target, prop, storeEffects) {
1817
1855
  let effectsToTrigger;
@@ -1939,7 +1977,7 @@
1939
1977
  }
1940
1978
  $computeIfNeeded$() {
1941
1979
  if (!(this.$flags$ & 1 /* SignalFlags.INVALID */)) {
1942
- return false;
1980
+ return;
1943
1981
  }
1944
1982
  const computeQrl = this.$computeQrl$;
1945
1983
  throwIfQRLNotResolved(computeQrl);
@@ -1969,6 +2007,7 @@
1969
2007
  this.$flags$ &= -2 /* SignalFlags.INVALID */;
1970
2008
  const didChange = untrackedValue !== this.$untrackedValue$;
1971
2009
  if (didChange) {
2010
+ this.$flags$ |= 2 /* SignalFlags.RUN_EFFECTS */;
1972
2011
  this.$untrackedValue$ = untrackedValue;
1973
2012
  }
1974
2013
  return didChange;
@@ -2056,7 +2095,7 @@
2056
2095
  if (!(obj instanceof AsyncComputedSignalImpl)) {
2057
2096
  assertEqual(prop, 'value', 'Left side is a signal, prop must be value');
2058
2097
  }
2059
- if (obj instanceof WrappedSignalImpl && obj.flags & 2 /* WrappedSignalFlags.UNWRAP */) {
2098
+ if (obj instanceof WrappedSignalImpl && obj.flags & 4 /* WrappedSignalFlags.UNWRAP */) {
2060
2099
  return obj;
2061
2100
  }
2062
2101
  return getWrapped(args);
@@ -2117,7 +2156,7 @@
2117
2156
  return !prop.startsWith('q:') && !prop.startsWith(NON_SERIALIZABLE_MARKER_PREFIX);
2118
2157
  }
2119
2158
  /** @internal */
2120
- const _restProps = (props, omit, target = {}) => {
2159
+ const _restProps = (props, omit = [], target = {}) => {
2121
2160
  let constPropsTarget = null;
2122
2161
  const constProps = props[_CONST_PROPS];
2123
2162
  if (constProps) {
@@ -2572,12 +2611,12 @@
2572
2611
  }
2573
2612
  if (targetElement) {
2574
2613
  if (targetElement.type === 'script' && key === qVisibleEvent) {
2575
- eventKey = 'document:onQinit$';
2614
+ eventKey = 'document:onQInit$';
2576
2615
  logWarn('You are trying to add an event "' +
2577
2616
  key +
2578
2617
  '" using `useVisibleTask$` hook, ' +
2579
2618
  'but a node to which you can add an event is not found. ' +
2580
- 'Using document:onQinit$ instead.');
2619
+ 'Using document:onQInit$ instead.');
2581
2620
  }
2582
2621
  addUseOnEvent(targetElement, eventKey, useOnEvents[key]);
2583
2622
  }
@@ -2700,13 +2739,13 @@
2700
2739
  if (srcProps && srcProps.children) {
2701
2740
  delete srcProps.children;
2702
2741
  }
2703
- const scheduler = ssr.$scheduler$;
2704
2742
  host.setProp(OnRenderProp, componentQrl);
2705
2743
  host.setProp(ELEMENT_PROPS, srcProps);
2706
2744
  if (jsx.key !== null) {
2707
2745
  host.setProp(ELEMENT_KEY, jsx.key);
2708
2746
  }
2709
- return scheduler(6 /* ChoreType.COMPONENT */, host, componentQrl, srcProps);
2747
+ const componentChore = ssr.$scheduler$(6 /* ChoreType.COMPONENT */, host, componentQrl, srcProps);
2748
+ return getChorePromise(componentChore);
2710
2749
  };
2711
2750
 
2712
2751
  class ParentComponentData {
@@ -2814,7 +2853,14 @@
2814
2853
  appendQwikInspectorAttribute(jsx, qwikInspectorAttrValue);
2815
2854
  }
2816
2855
  }
2817
- const innerHTML = ssr.openElement(type, varPropsToSsrAttrs(jsx.varProps, jsx.constProps, ssr.serializationCtx, options.styleScoped, jsx.key), constPropsToSsrAttrs(jsx.constProps, jsx.varProps, ssr.serializationCtx, options.styleScoped), qwikInspectorAttrValue);
2856
+ const innerHTML = ssr.openElement(type, varPropsToSsrAttrs(jsx.varProps, jsx.constProps, {
2857
+ serializationCtx: ssr.serializationCtx,
2858
+ styleScopedId: options.styleScoped,
2859
+ key: jsx.key,
2860
+ }), constPropsToSsrAttrs(jsx.constProps, jsx.varProps, {
2861
+ serializationCtx: ssr.serializationCtx,
2862
+ styleScopedId: options.styleScoped,
2863
+ }), qwikInspectorAttrValue);
2818
2864
  if (innerHTML) {
2819
2865
  ssr.htmlNode(innerHTML);
2820
2866
  }
@@ -2930,16 +2976,17 @@
2930
2976
  }
2931
2977
  }
2932
2978
  }
2933
- function varPropsToSsrAttrs(varProps, constProps, serializationCtx, styleScopedId, key) {
2934
- return toSsrAttrs(varProps, constProps, serializationCtx, true, styleScopedId, key);
2979
+ function varPropsToSsrAttrs(varProps, constProps, options) {
2980
+ return toSsrAttrs(varProps, constProps, false, options);
2935
2981
  }
2936
- function constPropsToSsrAttrs(constProps, varProps, serializationCtx, styleScopedId) {
2937
- return toSsrAttrs(constProps, varProps, serializationCtx, false, styleScopedId);
2982
+ function constPropsToSsrAttrs(constProps, varProps, options) {
2983
+ return toSsrAttrs(constProps, varProps, true, options);
2938
2984
  }
2939
- function toSsrAttrs(record, anotherRecord, serializationCtx, pushMergedEventProps, styleScopedId, key) {
2985
+ function toSsrAttrs(record, anotherRecord, isConst, options) {
2940
2986
  if (record == null) {
2941
2987
  return null;
2942
2988
  }
2989
+ const pushMergedEventProps = !isConst;
2943
2990
  const ssrAttrs = [];
2944
2991
  for (const key in record) {
2945
2992
  let value = record[key];
@@ -2976,7 +3023,7 @@
2976
3023
  }
2977
3024
  }
2978
3025
  }
2979
- const eventValue = setEvent(serializationCtx, key, value);
3026
+ const eventValue = setEvent(options.serializationCtx, key, value);
2980
3027
  if (eventValue) {
2981
3028
  ssrAttrs.push(jsxEventToHtmlAttribute(key), eventValue);
2982
3029
  }
@@ -2986,7 +3033,7 @@
2986
3033
  // write signal as is. We will track this signal inside `writeAttrs`
2987
3034
  if (isClassAttr(key)) {
2988
3035
  // additionally append styleScopedId for class attr
2989
- ssrAttrs.push(key, [value, styleScopedId]);
3036
+ ssrAttrs.push(key, [value, options.styleScopedId]);
2990
3037
  }
2991
3038
  else {
2992
3039
  ssrAttrs.push(key, value);
@@ -2994,13 +3041,13 @@
2994
3041
  continue;
2995
3042
  }
2996
3043
  if (isPreventDefault(key)) {
2997
- addPreventDefaultEventToSerializationContext(serializationCtx, key);
3044
+ addPreventDefaultEventToSerializationContext(options.serializationCtx, key);
2998
3045
  }
2999
- value = serializeAttribute(key, value, styleScopedId);
3046
+ value = serializeAttribute(key, value, options.styleScopedId);
3000
3047
  ssrAttrs.push(key, value);
3001
3048
  }
3002
- if (key != null) {
3003
- ssrAttrs.push(ELEMENT_KEY, key);
3049
+ if (options.key != null) {
3050
+ ssrAttrs.push(ELEMENT_KEY, options.key);
3004
3051
  }
3005
3052
  return ssrAttrs;
3006
3053
  }
@@ -3049,7 +3096,7 @@
3049
3096
  * For internal qrls (starting with `_`) we assume that they do the right thing.
3050
3097
  */
3051
3098
  if (!qrl.$symbol$.startsWith('_') && (qrl.$captureRef$ || qrl.$capture$)) {
3052
- qrl = createQRL(null, '_run', queueQRL, null, null, [qrl]);
3099
+ qrl = createQRL(null, '_run', _run, null, null, [qrl]);
3053
3100
  }
3054
3101
  return qrlToString(serializationCtx, qrl);
3055
3102
  };
@@ -3129,10 +3176,9 @@
3129
3176
  // deleted and we need to be able to release the task subscriptions.
3130
3177
  set(task);
3131
3178
  const container = iCtx.$container$;
3132
- const promise = container.$scheduler$(3 /* ChoreType.TASK */, task);
3133
- if (isPromise(promise)) {
3134
- // TODO: should we handle this differently?
3135
- promise.catch(() => { });
3179
+ const result = runTask(task, container, iCtx.$hostElement$);
3180
+ if (isPromise(result)) {
3181
+ throw result;
3136
3182
  }
3137
3183
  };
3138
3184
  const runTask = (task, container, host) => {
@@ -3144,7 +3190,7 @@
3144
3190
  const track = trackFn(task, container);
3145
3191
  const [cleanup] = cleanupFn(task, (reason) => container.handleError(reason, host));
3146
3192
  const taskApi = { track, cleanup };
3147
- const result = safeCall(() => taskFn(taskApi), cleanup, (err) => {
3193
+ return safeCall(() => taskFn(taskApi), cleanup, (err) => {
3148
3194
  // If a Promise is thrown, that means we need to re-run the task.
3149
3195
  if (isPromise(err)) {
3150
3196
  return err.then(() => runTask(task, container, host));
@@ -3153,7 +3199,6 @@
3153
3199
  throw err;
3154
3200
  }
3155
3201
  });
3156
- return result;
3157
3202
  };
3158
3203
  const cleanupTask = (task) => {
3159
3204
  const destroy = task.$destroy$;
@@ -3195,7 +3240,7 @@
3195
3240
  */
3196
3241
  const scheduleTask = (_event, element) => {
3197
3242
  const [task] = useLexicalScope();
3198
- const type = task.$flags$ & 1 /* TaskFlags.VISIBLE_TASK */ ? 32 /* ChoreType.VISIBLE */ : 3 /* ChoreType.TASK */;
3243
+ const type = task.$flags$ & 1 /* TaskFlags.VISIBLE_TASK */ ? 16 /* ChoreType.VISIBLE */ : 3 /* ChoreType.TASK */;
3199
3244
  const container = getDomContainer(element);
3200
3245
  container.$scheduler$(type, task);
3201
3246
  };
@@ -3447,7 +3492,7 @@
3447
3492
  ? this.$constProps$[prop]
3448
3493
  : this.$varProps$[prop];
3449
3494
  // a proxied value that the optimizer made
3450
- return value instanceof WrappedSignalImpl && value.$flags$ & 2 /* WrappedSignalFlags.UNWRAP */
3495
+ return value instanceof WrappedSignalImpl && value.$flags$ & 4 /* WrappedSignalFlags.UNWRAP */
3451
3496
  ? value.value
3452
3497
  : value;
3453
3498
  }
@@ -3861,6 +3906,10 @@
3861
3906
  vNewNode = null;
3862
3907
  vCurrent = vnode_getFirstChild(vStartNode);
3863
3908
  stackPush(jsxNode, true);
3909
+ if (vParent[0 /* VNodeProps.flags */] & 32 /* VNodeFlags.Deleted */) {
3910
+ // Ignore diff if the parent is deleted.
3911
+ return;
3912
+ }
3864
3913
  while (stack.length) {
3865
3914
  while (jsxIdx < jsxCount) {
3866
3915
  assertFalse(vParent === vCurrent, "Parent and current can't be the same");
@@ -4393,8 +4442,13 @@
4393
4442
  let returnValue = false;
4394
4443
  qrls.flat(2).forEach((qrl) => {
4395
4444
  if (qrl) {
4396
- const value = container.$scheduler$(2 /* ChoreType.RUN_QRL */, vNode, qrl, [event, element]);
4397
- returnValue = returnValue || value === true;
4445
+ if (isSyncQrl(qrl)) {
4446
+ qrl(event, element);
4447
+ }
4448
+ else {
4449
+ const value = container.$scheduler$(2 /* ChoreType.RUN_QRL */, vNode, qrl, [event, element]);
4450
+ returnValue = returnValue || value === true;
4451
+ }
4398
4452
  }
4399
4453
  });
4400
4454
  return returnValue;
@@ -4896,7 +4950,7 @@
4896
4950
  const task = obj;
4897
4951
  clearAllEffects(container, task);
4898
4952
  if (task.$flags$ & 1 /* TaskFlags.VISIBLE_TASK */) {
4899
- container.$scheduler$(48 /* ChoreType.CLEANUP_VISIBLE */, task);
4953
+ container.$scheduler$(32 /* ChoreType.CLEANUP_VISIBLE */, task);
4900
4954
  }
4901
4955
  else {
4902
4956
  cleanupTask(task);
@@ -4947,7 +5001,16 @@
4947
5001
  */
4948
5002
  const vFirstChild = vnode_getFirstChild(vCursor);
4949
5003
  if (vFirstChild) {
4950
- vnode_walkVNode(vFirstChild);
5004
+ vnode_walkVNode(vFirstChild, (vNode) => {
5005
+ /**
5006
+ * Instead of an ID, we store a direct reference to the VNode. This is necessary to
5007
+ * locate the slot's parent in a detached subtree, as the ID would become invalid.
5008
+ */
5009
+ if (vNode[0 /* VNodeProps.flags */] & 2 /* VNodeFlags.Virtual */) {
5010
+ // The QSlotParent is used to find the slot parent during scheduling
5011
+ vnode_getProp(vNode, QSlotParent, (id) => vnode_locate(container.rootVNode, id));
5012
+ }
5013
+ });
4951
5014
  return;
4952
5015
  }
4953
5016
  }
@@ -5024,8 +5087,8 @@
5024
5087
  const resource = createResourceReturn(container, opts);
5025
5088
  const el = iCtx.$hostElement$;
5026
5089
  const task = new Task(8 /* TaskFlags.DIRTY */ | 4 /* TaskFlags.RESOURCE */, i, el, qrl, resource, null);
5027
- container.$scheduler$(3 /* ChoreType.TASK */, task);
5028
5090
  set(resource);
5091
+ runResource(task, container, el);
5029
5092
  return resource;
5030
5093
  };
5031
5094
  // <docs markdown="../readme.md#useResource">
@@ -5093,10 +5156,10 @@
5093
5156
  function getResourceValueAsPromise(props) {
5094
5157
  const resource = props.value;
5095
5158
  if (isResourceReturn(resource)) {
5159
+ // create a subscription for the resource._state changes
5160
+ const state = resource._state;
5096
5161
  const isBrowser = !isServerPlatform();
5097
5162
  if (isBrowser) {
5098
- // create a subscription for the resource._state changes
5099
- const state = resource._state;
5100
5163
  if (state === 'pending' && props.onPending) {
5101
5164
  return Promise.resolve().then(useBindInvokeContext(props.onPending));
5102
5165
  }
@@ -5111,14 +5174,7 @@
5111
5174
  }
5112
5175
  }
5113
5176
  }
5114
- const value = resource.value;
5115
- if (value) {
5116
- return value.then(useBindInvokeContext(props.onResolved), useBindInvokeContext(props.onRejected));
5117
- }
5118
- else {
5119
- // this is temporary value until the `runResource` is executed and promise is assigned to the value
5120
- return Promise.resolve(undefined);
5121
- }
5177
+ return untrack(() => resource.value).then(useBindInvokeContext(props.onResolved), useBindInvokeContext(props.onRejected));
5122
5178
  }
5123
5179
  else if (isPromise(resource)) {
5124
5180
  return resource.then(useBindInvokeContext(props.onResolved), useBindInvokeContext(props.onRejected));
@@ -5136,7 +5192,7 @@
5136
5192
  const resource = {
5137
5193
  __brand: 'resource',
5138
5194
  value: undefined,
5139
- loading: isServerPlatform() ? false : true,
5195
+ loading: !isServerPlatform(),
5140
5196
  _resolved: undefined,
5141
5197
  _error: undefined,
5142
5198
  _state: 'pending',
@@ -5187,19 +5243,22 @@
5187
5243
  done = true;
5188
5244
  if (resolved) {
5189
5245
  done = true;
5190
- resource.loading = false;
5191
- resource._state = 'resolved';
5192
- resource._resolved = value;
5193
- resource._error = undefined;
5246
+ resourceTarget.loading = false;
5247
+ resourceTarget._state = 'resolved';
5248
+ resourceTarget._resolved = value;
5249
+ resourceTarget._error = undefined;
5194
5250
  resolve(value);
5195
5251
  }
5196
5252
  else {
5197
5253
  done = true;
5198
- resource.loading = false;
5199
- resource._state = 'rejected';
5200
- resource._error = value;
5254
+ resourceTarget.loading = false;
5255
+ resourceTarget._state = 'rejected';
5256
+ resourceTarget._error = value;
5201
5257
  reject(value);
5202
5258
  }
5259
+ if (!isServerPlatform()) {
5260
+ forceStoreEffects(resource, '_state');
5261
+ }
5203
5262
  return true;
5204
5263
  }
5205
5264
  return false;
@@ -5215,17 +5274,17 @@
5215
5274
  }
5216
5275
  });
5217
5276
  // Execute mutation inside empty invocation
5277
+ // TODO: is it right? why we need to invoke inside context and trigger effects?
5218
5278
  invoke(iCtx, () => {
5219
5279
  // console.log('RESOURCE.pending: ');
5220
5280
  resource._state = 'pending';
5221
5281
  resource.loading = !isServerPlatform();
5222
- const promise = (resource.value = new Promise((r, re) => {
5282
+ resource.value = new Promise((r, re) => {
5223
5283
  resolve = r;
5224
5284
  reject = re;
5225
- }));
5226
- promise.catch(ignoreErrorToPreventNodeFromCrashing);
5285
+ });
5227
5286
  });
5228
- const promise = safeCall(() => Promise.resolve(taskFn(opts)), (value) => {
5287
+ const promise = safeCall(() => taskFn(opts), (value) => {
5229
5288
  setState(true, value);
5230
5289
  }, (err) => {
5231
5290
  if (isPromise(err)) {
@@ -5248,10 +5307,6 @@
5248
5307
  }
5249
5308
  return promise;
5250
5309
  };
5251
- const ignoreErrorToPreventNodeFromCrashing = (err) => {
5252
- // ignore error to prevent node from crashing
5253
- // node will crash in promise is rejected and no one is listening to the rejection.
5254
- };
5255
5310
 
5256
5311
  /// These global variables are used to avoid creating new arrays for each call to `vnode_documentPosition`.
5257
5312
  const aVNodePath = [];
@@ -5334,11 +5389,11 @@
5334
5389
  let bDepth = -1;
5335
5390
  while (a) {
5336
5391
  const ssrNode = (aSsrNodePath[++aDepth] = a);
5337
- a = ssrNode.parentSsrNode;
5392
+ a = ssrNode.parentComponent;
5338
5393
  }
5339
5394
  while (b) {
5340
5395
  const ssrNode = (bSsrNodePath[++bDepth] = b);
5341
- b = ssrNode.parentSsrNode;
5396
+ b = ssrNode.parentComponent;
5342
5397
  }
5343
5398
  while (aDepth >= 0 && bDepth >= 0) {
5344
5399
  a = aSsrNodePath[aDepth];
@@ -5355,6 +5410,210 @@
5355
5410
  return aDepth < bDepth ? -1 : 1;
5356
5411
  };
5357
5412
 
5413
+ /**
5414
+ * Rules for determining if a chore is blocked by another chore. Some chores can block other chores.
5415
+ * They cannot run until the blocking chore has completed.
5416
+ *
5417
+ * The match function is used to determine if the blocked chore is blocked by the blocking chore.
5418
+ * The match function is called with the blocked chore, the blocking chore, and the container.
5419
+ */
5420
+ const VISIBLE_BLOCKING_RULES = [
5421
+ // NODE_DIFF blocks VISIBLE on same host,
5422
+ // if the blocked chore is a child of the blocking chore
5423
+ // or the blocked chore is a sibling of the blocking chore
5424
+ {
5425
+ blockedType: 16 /* ChoreType.VISIBLE */,
5426
+ blockingType: 4 /* ChoreType.NODE_DIFF */,
5427
+ match: (blocked, blocking, container) => isDescendant(blocked, blocking, container) || isDescendant(blocking, blocked, container),
5428
+ },
5429
+ // COMPONENT blocks VISIBLE on same host
5430
+ // if the blocked chore is a child of the blocking chore
5431
+ // or the blocked chore is a sibling of the blocking chore
5432
+ {
5433
+ blockedType: 16 /* ChoreType.VISIBLE */,
5434
+ blockingType: 6 /* ChoreType.COMPONENT */,
5435
+ match: (blocked, blocking, container) => isDescendant(blocked, blocking, container) || isDescendant(blocking, blocked, container),
5436
+ },
5437
+ ];
5438
+ const BLOCKING_RULES = [
5439
+ // QRL_RESOLVE blocks RUN_QRL, TASK, VISIBLE on same host
5440
+ {
5441
+ blockedType: 2 /* ChoreType.RUN_QRL */,
5442
+ blockingType: 1 /* ChoreType.QRL_RESOLVE */,
5443
+ match: (blocked, blocking) => {
5444
+ const blockedQrl = blocked.$target$;
5445
+ const blockingQrl = blocking.$target$;
5446
+ return isSameHost(blocked, blocking) && isSameQrl(blockedQrl, blockingQrl);
5447
+ },
5448
+ },
5449
+ {
5450
+ blockedType: 3 /* ChoreType.TASK */,
5451
+ blockingType: 1 /* ChoreType.QRL_RESOLVE */,
5452
+ match: (blocked, blocking) => {
5453
+ const blockedTask = blocked.$payload$;
5454
+ const blockingQrl = blocking.$target$;
5455
+ return isSameHost(blocked, blocking) && isSameQrl(blockedTask.$qrl$, blockingQrl);
5456
+ },
5457
+ },
5458
+ {
5459
+ blockedType: 16 /* ChoreType.VISIBLE */,
5460
+ blockingType: 1 /* ChoreType.QRL_RESOLVE */,
5461
+ match: (blocked, blocking) => {
5462
+ const blockedTask = blocked.$payload$;
5463
+ const blockingQrl = blocking.$target$;
5464
+ return isSameHost(blocked, blocking) && isSameQrl(blockedTask.$qrl$, blockingQrl);
5465
+ },
5466
+ },
5467
+ // COMPONENT blocks NODE_DIFF, NODE_PROP on same host
5468
+ {
5469
+ blockedType: 4 /* ChoreType.NODE_DIFF */,
5470
+ blockingType: 6 /* ChoreType.COMPONENT */,
5471
+ match: (blocked, blocking) => blocked.$host$ === blocking.$host$,
5472
+ },
5473
+ {
5474
+ blockedType: 5 /* ChoreType.NODE_PROP */,
5475
+ blockingType: 6 /* ChoreType.COMPONENT */,
5476
+ match: (blocked, blocking) => blocked.$host$ === blocking.$host$,
5477
+ },
5478
+ ...VISIBLE_BLOCKING_RULES,
5479
+ // TASK blocks subsequent TASKs in the same component
5480
+ {
5481
+ blockedType: 3 /* ChoreType.TASK */,
5482
+ blockingType: 3 /* ChoreType.TASK */,
5483
+ match: (blocked, blocking, container) => {
5484
+ if (blocked.$host$ !== blocking.$host$) {
5485
+ return false;
5486
+ }
5487
+ const blockedIdx = blocked.$idx$;
5488
+ if (!isNumber$1(blockedIdx) || blockedIdx <= 0) {
5489
+ return false;
5490
+ }
5491
+ const previousTask = findPreviousTaskInComponent(blocked.$host$, blockedIdx, container);
5492
+ return previousTask === blocking.$payload$;
5493
+ },
5494
+ },
5495
+ ];
5496
+ function isDescendant(descendantChore, ancestorChore, container) {
5497
+ const descendantHost = descendantChore.$host$;
5498
+ const ancestorHost = ancestorChore.$host$;
5499
+ if (!vnode_isVNode(descendantHost) || !vnode_isVNode(ancestorHost)) {
5500
+ return false;
5501
+ }
5502
+ return vnode_isDescendantOf(descendantHost, ancestorHost, container.rootVNode);
5503
+ }
5504
+ function isSameHost(a, b) {
5505
+ return a.$host$ === b.$host$;
5506
+ }
5507
+ function isSameQrl(a, b) {
5508
+ return a.$symbol$ === b.$symbol$;
5509
+ }
5510
+ function findBlockingChoreInQueue(chore, choreQueue, container) {
5511
+ for (const candidate of choreQueue) {
5512
+ // everything after VISIBLE is not blocking. Visible task, task and resource should not block anything in this rule.
5513
+ if (candidate.$type$ >= 16 /* ChoreType.VISIBLE */ || candidate.$type$ === 3 /* ChoreType.TASK */) {
5514
+ continue;
5515
+ }
5516
+ if (isDescendant(chore, candidate, container)) {
5517
+ return candidate;
5518
+ }
5519
+ }
5520
+ return null;
5521
+ }
5522
+ function findBlockingChore(chore, choreQueue, blockedChores, runningChores, container) {
5523
+ const blockingChoreInChoreQueue = findBlockingChoreInQueue(chore, choreQueue, container);
5524
+ if (blockingChoreInChoreQueue) {
5525
+ return blockingChoreInChoreQueue;
5526
+ }
5527
+ const blockingChoreInBlockedChores = findBlockingChoreInQueue(chore, Array.from(blockedChores), container);
5528
+ if (blockingChoreInBlockedChores) {
5529
+ return blockingChoreInBlockedChores;
5530
+ }
5531
+ const blockingChoreInRunningChores = findBlockingChoreInQueue(chore, Array.from(runningChores), container);
5532
+ if (blockingChoreInRunningChores) {
5533
+ return blockingChoreInRunningChores;
5534
+ }
5535
+ for (const rule of BLOCKING_RULES) {
5536
+ if (chore.$type$ !== rule.blockedType) {
5537
+ continue;
5538
+ }
5539
+ // Check in choreQueue
5540
+ // TODO(perf): better to iterate in reverse order?
5541
+ for (const candidate of choreQueue) {
5542
+ if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) {
5543
+ return candidate;
5544
+ }
5545
+ }
5546
+ // Check in blockedChores
5547
+ for (const candidate of blockedChores) {
5548
+ if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) {
5549
+ return candidate;
5550
+ }
5551
+ }
5552
+ // Check in runningChores
5553
+ for (const candidate of runningChores) {
5554
+ if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) {
5555
+ return candidate;
5556
+ }
5557
+ }
5558
+ }
5559
+ return null;
5560
+ }
5561
+ function findPreviousTaskInComponent(host, currentTaskIdx, container) {
5562
+ const elementSeq = container.getHostProp(host, ELEMENT_SEQ);
5563
+ if (!elementSeq || elementSeq.length <= currentTaskIdx) {
5564
+ return null;
5565
+ }
5566
+ for (let i = currentTaskIdx - 1; i >= 0; i--) {
5567
+ const candidate = elementSeq[i];
5568
+ if (candidate instanceof Task && candidate.$flags$ & 2 /* TaskFlags.TASK */) {
5569
+ return candidate;
5570
+ }
5571
+ }
5572
+ return null;
5573
+ }
5574
+ function findBlockingChoreForVisible(chore, runningChores, container) {
5575
+ for (const rule of VISIBLE_BLOCKING_RULES) {
5576
+ if (chore.$type$ !== rule.blockedType) {
5577
+ continue;
5578
+ }
5579
+ for (const candidate of runningChores) {
5580
+ if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) {
5581
+ return candidate;
5582
+ }
5583
+ }
5584
+ }
5585
+ return null;
5586
+ }
5587
+
5588
+ // This can't be in platform.ts because it uses MessageChannel which cannot post messages with functions
5589
+ // TODO: move this to platform.ts somehow
5590
+ const createNextTick = (fn) => {
5591
+ let nextTick;
5592
+ // according to the https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate#notes
5593
+ if (typeof setImmediate === 'function') {
5594
+ // setImmediate is the fastest way to schedule a task, but works only in node.js
5595
+ nextTick = () => {
5596
+ setImmediate(fn);
5597
+ };
5598
+ }
5599
+ else if (typeof MessageChannel !== 'undefined') {
5600
+ const channel = new MessageChannel();
5601
+ channel.port1.onmessage = () => {
5602
+ fn();
5603
+ };
5604
+ nextTick = () => {
5605
+ channel.port2.postMessage(null);
5606
+ };
5607
+ }
5608
+ else {
5609
+ // setTimeout is a fallback, creates 4ms delay
5610
+ nextTick = () => {
5611
+ setTimeout(fn);
5612
+ };
5613
+ }
5614
+ return nextTick;
5615
+ };
5616
+
5358
5617
  /**
5359
5618
  * Scheduler is responsible for running application code in predictable order.
5360
5619
  *
@@ -5438,29 +5697,43 @@
5438
5697
  */
5439
5698
  // Turn this on to get debug output of what the scheduler is doing.
5440
5699
  const DEBUG = false;
5441
- const getPromise = (chore) => (chore.$promise$ ||= new Promise((resolve) => {
5442
- chore.$resolve$ = resolve;
5443
- }));
5444
- const createScheduler = (container, scheduleDrain, journalFlush) => {
5445
- const choreQueue = [];
5446
- const qrlRuns = [];
5447
- let currentChore = null;
5700
+ var ChoreState;
5701
+ (function (ChoreState) {
5702
+ ChoreState[ChoreState["NONE"] = 0] = "NONE";
5703
+ ChoreState[ChoreState["RUNNING"] = 1] = "RUNNING";
5704
+ ChoreState[ChoreState["FAILED"] = 2] = "FAILED";
5705
+ ChoreState[ChoreState["DONE"] = 3] = "DONE";
5706
+ })(ChoreState || (ChoreState = {}));
5707
+ const getChorePromise = (chore) => chore.$state$ === ChoreState.NONE
5708
+ ? (chore.$returnValue$ ||= new Promise((resolve, reject) => {
5709
+ chore.$resolve$ = resolve;
5710
+ chore.$reject$ = reject;
5711
+ }))
5712
+ : chore.$returnValue$;
5713
+ const createScheduler = (container, journalFlush, choreQueue = [], blockedChores = new Set(), runningChores = new Set()) => {
5714
+ let drainChore = null;
5448
5715
  let drainScheduled = false;
5716
+ let isDraining = false;
5717
+ let isJournalFlushRunning = false;
5718
+ let flushBudgetStart = 0;
5719
+ let currentTime = performance.now();
5720
+ const nextTick = createNextTick(drainChoreQueue);
5721
+ function drainInNextTick() {
5722
+ if (!drainScheduled) {
5723
+ drainScheduled = true;
5724
+ nextTick();
5725
+ }
5726
+ }
5727
+ // Drain for ~16.67ms, then apply journal flush for ~16.67ms, then repeat
5728
+ // We divide by 60 because we want to run at 60fps
5729
+ const FREQUENCY_MS = Math.floor(1000 / 60);
5449
5730
  return schedule;
5450
5731
  ///// IMPLEMENTATION /////
5451
5732
  function schedule(type, hostOrTask = null, targetOrQrl = null, payload = null) {
5452
- const isServer = !isDomContainer(container);
5453
- const isComponentSsr = isServer && type === 6 /* ChoreType.COMPONENT */;
5454
- const runLater = type !== 255 /* ChoreType.WAIT_FOR_ALL */ && !isComponentSsr && type !== 2 /* ChoreType.RUN_QRL */;
5455
- const isTask = type === 3 /* ChoreType.TASK */ || type === 32 /* ChoreType.VISIBLE */ || type === 48 /* ChoreType.CLEANUP_VISIBLE */;
5456
- const isClientOnly = type === 16 /* ChoreType.JOURNAL_FLUSH */ ||
5457
- type === 4 /* ChoreType.NODE_DIFF */ ||
5458
- type === 5 /* ChoreType.NODE_PROP */ ||
5459
- type === 1 /* ChoreType.QRL_RESOLVE */ ||
5460
- type === 7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */;
5461
- if (isServer && isClientOnly) {
5462
- return;
5733
+ if (type === 255 /* ChoreType.WAIT_FOR_QUEUE */ && drainChore) {
5734
+ return drainChore;
5463
5735
  }
5736
+ const isTask = type === 3 /* ChoreType.TASK */ || type === 16 /* ChoreType.VISIBLE */ || type === 32 /* ChoreType.CLEANUP_VISIBLE */;
5464
5737
  if (isTask) {
5465
5738
  hostOrTask.$flags$ |= 8 /* TaskFlags.DIRTY */;
5466
5739
  }
@@ -5474,167 +5747,310 @@
5474
5747
  $host$: isTask ? hostOrTask.$el$ : hostOrTask,
5475
5748
  $target$: targetOrQrl,
5476
5749
  $payload$: isTask ? hostOrTask : payload,
5477
- $resolve$: null,
5478
- $promise$: null,
5750
+ $state$: ChoreState.NONE,
5751
+ $blockedChores$: null,
5752
+ $startTime$: undefined,
5753
+ $endTime$: undefined,
5754
+ $resolve$: undefined,
5755
+ $reject$: undefined,
5479
5756
  $returnValue$: null,
5480
- $executed$: false,
5481
5757
  };
5482
- chore = sortedInsert(choreQueue, chore, container.rootVNode || null);
5483
- if (!drainScheduled && runLater) {
5484
- // If we are not currently draining, we need to schedule a drain.
5485
- drainScheduled = true;
5486
- schedule(16 /* ChoreType.JOURNAL_FLUSH */);
5487
- // Catch here to avoid unhandled promise rejection
5488
- scheduleDrain()?.catch?.(() => { });
5758
+ if (type === 255 /* ChoreType.WAIT_FOR_QUEUE */) {
5759
+ getChorePromise(chore);
5760
+ drainChore = chore;
5761
+ drainInNextTick();
5762
+ return chore;
5763
+ }
5764
+ const isServer = isServerPlatform();
5765
+ const isClientOnly = type === 4 /* ChoreType.NODE_DIFF */ || type === 1 /* ChoreType.QRL_RESOLVE */;
5766
+ if (isServer && isClientOnly) {
5767
+ // Mark skipped client-only chores as completed on the server
5768
+ finishChore(chore, undefined);
5769
+ return chore;
5770
+ }
5771
+ if (isServer && chore.$host$ && isSsrNode(chore.$host$)) {
5772
+ const isUpdatable = !!(chore.$host$.flags & 1 /* SsrNodeFlags.Updatable */);
5773
+ if (!isUpdatable) {
5774
+ if (
5775
+ // backpatching exceptions:
5776
+ // - node prop is allowed because it is used to update the node property
5777
+ // - recompute and schedule effects because it triggers effects (so node prop too)
5778
+ chore.$type$ !== 5 /* ChoreType.NODE_PROP */ &&
5779
+ chore.$type$ !== 7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */) {
5780
+ // We are running on the server.
5781
+ // On server we can't schedule task for a different host!
5782
+ // Server is SSR, and therefore scheduling for anything but the current host
5783
+ // implies that things need to be re-run and that is not supported because of streaming.
5784
+ const warningMessage = `A '${choreTypeToName(chore.$type$)}' chore was scheduled on a host element that has already been streamed to the client.
5785
+ This can lead to inconsistencies between Server-Side Rendering (SSR) and Client-Side Rendering (CSR).
5786
+
5787
+ Problematic chore:
5788
+ - Type: ${choreTypeToName(chore.$type$)}
5789
+ - Host: ${chore.$host$.toString()}
5790
+ - Nearest element location: ${chore.$host$.currentFile}
5791
+
5792
+ This is often caused by modifying a signal in an already rendered component during SSR.`;
5793
+ logWarn(warningMessage);
5794
+ return chore;
5795
+ }
5796
+ }
5797
+ }
5798
+ const blockingChore = findBlockingChore(chore, choreQueue, blockedChores, runningChores, container);
5799
+ if (blockingChore) {
5800
+ addBlockedChore(chore, blockingChore, blockedChores);
5801
+ return chore;
5489
5802
  }
5490
- // TODO figure out what to do with chore errors
5491
- if (runLater) {
5492
- return getPromise(chore);
5803
+ chore = sortedInsert(choreQueue, chore, container.rootVNode || null);
5804
+ const runImmediately = (isServer && type === 6 /* ChoreType.COMPONENT */) || type === 2 /* ChoreType.RUN_QRL */;
5805
+ if (runImmediately && !isDraining) {
5806
+ immediateDrain();
5493
5807
  }
5494
5808
  else {
5495
- return drainUpTo(chore, isServer);
5496
- }
5497
- }
5498
- /** Execute all of the chores up to and including the given chore. */
5499
- function drainUpTo(runUptoChore, isServer) {
5500
- let maxRetries = 5000;
5501
- while (choreQueue.length) {
5502
- if (maxRetries-- < 0) {
5503
- throw new Error('drainUpTo: max retries reached');
5504
- }
5505
- if (currentChore) {
5506
- // Already running chore, which means we're waiting for async completion
5507
- return getPromise(currentChore)
5508
- .then(() => drainUpTo(runUptoChore, isServer))
5509
- .catch((e) => {
5510
- container.handleError(e, currentChore?.$host$);
5511
- });
5809
+ drainInNextTick();
5810
+ }
5811
+ return chore;
5812
+ }
5813
+ function immediateDrain() {
5814
+ drainScheduled = true;
5815
+ drainChoreQueue();
5816
+ }
5817
+ ////////////////////////////////////////////////////////////////////////////////
5818
+ // Drain queue helpers
5819
+ ////////////////////////////////////////////////////////////////////////////////
5820
+ function applyJournalFlush() {
5821
+ if (!isJournalFlushRunning) {
5822
+ // prevent multiple journal flushes from running at the same time
5823
+ isJournalFlushRunning = true;
5824
+ journalFlush();
5825
+ isJournalFlushRunning = false;
5826
+ flushBudgetStart = performance.now();
5827
+ }
5828
+ }
5829
+ function shouldApplyJournalFlush(isServer) {
5830
+ return !isServer && currentTime - flushBudgetStart >= FREQUENCY_MS;
5831
+ }
5832
+ function drainChoreQueue() {
5833
+ const isServer = isServerPlatform();
5834
+ drainScheduled = false;
5835
+ if (isDraining) {
5836
+ return;
5837
+ }
5838
+ // early return if the queue is empty
5839
+ if (!choreQueue.length) {
5840
+ applyJournalFlush();
5841
+ if (drainChore && !runningChores.size) {
5842
+ // resolve drainChore only if there are no running chores, because
5843
+ // we are sure that we are done
5844
+ drainChore.$resolve$(null);
5845
+ drainChore = null;
5512
5846
  }
5513
- const nextChore = choreQueue[0];
5514
- if (nextChore.$executed$) {
5515
- choreQueue.shift();
5516
- if (nextChore === runUptoChore) {
5517
- break;
5847
+ return;
5848
+ }
5849
+ isDraining = true;
5850
+ flushBudgetStart = performance.now();
5851
+ const maybeFinishDrain = () => {
5852
+ if (choreQueue.length) {
5853
+ drainInNextTick();
5854
+ return false;
5855
+ }
5856
+ if (drainChore && runningChores.size) {
5857
+ if (shouldApplyJournalFlush(isServer)) {
5858
+ // apply journal flush even if we are not finished draining the queue
5859
+ applyJournalFlush();
5518
5860
  }
5519
- continue;
5861
+ return false;
5520
5862
  }
5521
- if (vNodeAlreadyDeleted(nextChore) &&
5522
- // we need to process cleanup tasks for deleted nodes
5523
- nextChore.$type$ !== 48 /* ChoreType.CLEANUP_VISIBLE */) {
5524
- choreQueue.shift();
5525
- continue;
5863
+ currentChore = null;
5864
+ applyJournalFlush();
5865
+ drainChore?.$resolve$(null);
5866
+ drainChore = null;
5867
+ return true;
5868
+ };
5869
+ const scheduleBlockedChoresAndDrainIfNeeded = (chore) => {
5870
+ let blockedChoresScheduled = false;
5871
+ if (chore.$blockedChores$) {
5872
+ for (const blockedChore of chore.$blockedChores$) {
5873
+ const blockingChore = findBlockingChore(blockedChore, choreQueue, blockedChores, runningChores, container);
5874
+ if (blockingChore) {
5875
+ addBlockedChore(blockedChore, blockingChore, blockedChores);
5876
+ }
5877
+ else {
5878
+ blockedChores.delete(blockedChore);
5879
+ sortedInsert(choreQueue, blockedChore, container.rootVNode || null);
5880
+ blockedChoresScheduled = true;
5881
+ }
5882
+ }
5883
+ chore.$blockedChores$ = null;
5526
5884
  }
5527
- executeChore(nextChore, isServer);
5528
- }
5529
- return runUptoChore.$returnValue$;
5530
- }
5531
- function executeChore(chore, isServer) {
5532
- const host = chore.$host$;
5533
- assertEqual(currentChore, null, 'Chore already running.');
5534
- currentChore = chore;
5535
- let returnValue = null;
5885
+ if (blockedChoresScheduled && !isDraining) {
5886
+ drainInNextTick();
5887
+ }
5888
+ };
5889
+ let currentChore = null;
5536
5890
  try {
5537
- switch (chore.$type$) {
5538
- case 255 /* ChoreType.WAIT_FOR_ALL */:
5539
- {
5540
- if (isServer) {
5541
- drainScheduled = false;
5542
- }
5543
- }
5544
- break;
5545
- case 16 /* ChoreType.JOURNAL_FLUSH */:
5546
- {
5547
- returnValue = journalFlush();
5548
- drainScheduled = false;
5891
+ while (choreQueue.length) {
5892
+ currentTime = performance.now();
5893
+ const chore = (currentChore = choreQueue.shift());
5894
+ if (chore.$state$ !== ChoreState.NONE) {
5895
+ continue;
5896
+ }
5897
+ if (vNodeAlreadyDeleted(chore) &&
5898
+ // we need to process cleanup tasks for deleted nodes
5899
+ chore.$type$ !== 32 /* ChoreType.CLEANUP_VISIBLE */) {
5900
+ // skip deleted chore
5901
+ DEBUG && debugTrace('skip chore', chore, choreQueue, blockedChores);
5902
+ continue;
5903
+ }
5904
+ if (chore.$type$ === 16 /* ChoreType.VISIBLE */) {
5905
+ // ensure that the journal flush is applied before the visible chore is executed
5906
+ // so that the visible chore can see the latest DOM changes
5907
+ applyJournalFlush();
5908
+ const blockingChore = findBlockingChoreForVisible(chore, runningChores, container);
5909
+ if (blockingChore && blockingChore.$state$ === ChoreState.RUNNING) {
5910
+ addBlockedChore(chore, blockingChore, blockedChores);
5911
+ continue;
5549
5912
  }
5550
- break;
5551
- case 6 /* ChoreType.COMPONENT */:
5552
- {
5553
- returnValue = safeCall(() => executeComponent(container, host, host, chore.$target$, chore.$payload$), (jsx) => {
5554
- if (isServer) {
5555
- return jsx;
5913
+ }
5914
+ // Note that this never throws
5915
+ chore.$startTime$ = performance.now();
5916
+ const result = executeChore(chore, isServer);
5917
+ chore.$returnValue$ = result;
5918
+ if (isPromise(result)) {
5919
+ runningChores.add(chore);
5920
+ chore.$state$ = ChoreState.RUNNING;
5921
+ result
5922
+ .then((value) => {
5923
+ finishChore(chore, value);
5924
+ })
5925
+ .catch((e) => {
5926
+ if (chore.$state$ !== ChoreState.RUNNING) {
5927
+ // we already handled the error
5928
+ return;
5929
+ }
5930
+ handleError(chore, e);
5931
+ })
5932
+ .finally(() => {
5933
+ runningChores.delete(chore);
5934
+ // Note that we ignore failed chores so the app keeps working
5935
+ // TODO decide if this is ok and document it
5936
+ scheduleBlockedChoresAndDrainIfNeeded(chore);
5937
+ // If drainChore is not null, we are waiting for it to finish.
5938
+ // If there are no running chores, we can finish the drain.
5939
+ if (!runningChores.size) {
5940
+ let finished = false;
5941
+ if (drainChore) {
5942
+ finished = maybeFinishDrain();
5556
5943
  }
5557
- else {
5558
- const styleScopedId = container.getHostProp(host, QScopedStyle);
5559
- return retryOnPromise(() => vnode_diff(container, jsx, host, addComponentStylePrefix(styleScopedId)));
5944
+ if (!finished && !isDraining) {
5945
+ // if finished, then journal flush is already applied
5946
+ applyJournalFlush();
5560
5947
  }
5561
- }, (err) => container.handleError(err, host));
5562
- }
5563
- break;
5564
- case 2 /* ChoreType.RUN_QRL */:
5565
- {
5566
- const fn = chore.$target$.getFn();
5567
- const result = retryOnPromise(() => fn(...chore.$payload$));
5568
- if (isPromise(result)) {
5569
- const handled = result
5570
- .finally(() => {
5571
- qrlRuns.splice(qrlRuns.indexOf(handled), 1);
5572
- })
5573
- .catch((error) => {
5574
- container.handleError(error, chore.$host$);
5575
- });
5576
- // Don't wait for the promise to resolve
5577
- // TODO come up with a better solution, we also want concurrent signal handling with tasks but serial tasks
5578
- qrlRuns.push(handled);
5579
- DEBUG &&
5580
- debugTrace('execute.DONE (but still running)', chore, currentChore, choreQueue);
5581
- chore.$returnValue$ = handled;
5582
- chore.$resolve$?.(handled);
5583
- currentChore = null;
5584
- chore.$executed$ = true;
5585
- // early out so we don't call after()
5586
- return;
5587
5948
  }
5588
- returnValue = null;
5589
- }
5590
- break;
5591
- case 3 /* ChoreType.TASK */:
5592
- case 32 /* ChoreType.VISIBLE */:
5593
- {
5594
- const payload = chore.$payload$;
5595
- if (payload.$flags$ & 4 /* TaskFlags.RESOURCE */) {
5596
- const result = runResource(payload, container, host);
5597
- // Don't await the return value of the resource, because async resources should not be awaited.
5598
- // The reason for this is that we should be able to update for example a node with loading
5599
- // text. If we await the resource, the loading text will not be displayed until the resource
5600
- // is loaded.
5601
- // Awaiting on the client also causes a deadlock.
5602
- // In any case, the resource will never throw.
5603
- returnValue = isServer ? result : null;
5949
+ });
5950
+ }
5951
+ else {
5952
+ finishChore(chore, result);
5953
+ scheduleBlockedChoresAndDrainIfNeeded(chore);
5954
+ }
5955
+ if (shouldApplyJournalFlush(isServer)) {
5956
+ applyJournalFlush();
5957
+ drainInNextTick();
5958
+ return;
5959
+ }
5960
+ }
5961
+ }
5962
+ catch (e) {
5963
+ handleError(currentChore, e);
5964
+ scheduleBlockedChoresAndDrainIfNeeded(currentChore);
5965
+ }
5966
+ finally {
5967
+ isDraining = false;
5968
+ maybeFinishDrain();
5969
+ }
5970
+ }
5971
+ function finishChore(chore, value) {
5972
+ chore.$endTime$ = performance.now();
5973
+ chore.$state$ = ChoreState.DONE;
5974
+ chore.$returnValue$ = value;
5975
+ chore.$resolve$?.(value);
5976
+ }
5977
+ function handleError(chore, e) {
5978
+ chore.$endTime$ = performance.now();
5979
+ chore.$state$ = ChoreState.FAILED;
5980
+ // If we used the result as promise, this won't exist
5981
+ chore.$reject$?.(e);
5982
+ container.handleError(e, chore.$host$);
5983
+ }
5984
+ function executeChore(chore, isServer) {
5985
+ const host = chore.$host$;
5986
+ let returnValue;
5987
+ switch (chore.$type$) {
5988
+ case 6 /* ChoreType.COMPONENT */:
5989
+ {
5990
+ returnValue = safeCall(() => executeComponent(container, host, host, chore.$target$, chore.$payload$), (jsx) => {
5991
+ if (isServer) {
5992
+ return jsx;
5604
5993
  }
5605
5994
  else {
5606
- returnValue = runTask(payload, container, host);
5995
+ const styleScopedId = container.getHostProp(host, QScopedStyle);
5996
+ return retryOnPromise(() => vnode_diff(container, jsx, host, addComponentStylePrefix(styleScopedId)));
5607
5997
  }
5998
+ }, (err) => {
5999
+ handleError(chore, err);
6000
+ });
6001
+ }
6002
+ break;
6003
+ case 2 /* ChoreType.RUN_QRL */:
6004
+ {
6005
+ const fn = chore.$target$.getFn();
6006
+ returnValue = retryOnPromise(() => fn(...chore.$payload$));
6007
+ }
6008
+ break;
6009
+ case 3 /* ChoreType.TASK */:
6010
+ case 16 /* ChoreType.VISIBLE */:
6011
+ {
6012
+ const payload = chore.$payload$;
6013
+ if (payload.$flags$ & 4 /* TaskFlags.RESOURCE */) {
6014
+ returnValue = runResource(payload, container, host);
5608
6015
  }
5609
- break;
5610
- case 48 /* ChoreType.CLEANUP_VISIBLE */:
5611
- {
5612
- const task = chore.$payload$;
5613
- cleanupTask(task);
6016
+ else {
6017
+ returnValue = runTask(payload, container, host);
5614
6018
  }
5615
- break;
5616
- case 4 /* ChoreType.NODE_DIFF */:
5617
- {
5618
- const parentVirtualNode = chore.$target$;
5619
- let jsx = chore.$payload$;
5620
- if (isSignal(jsx)) {
5621
- jsx = jsx.value;
5622
- }
5623
- returnValue = retryOnPromise(() => vnode_diff(container, jsx, parentVirtualNode, null));
6019
+ }
6020
+ break;
6021
+ case 32 /* ChoreType.CLEANUP_VISIBLE */:
6022
+ {
6023
+ const task = chore.$payload$;
6024
+ cleanupTask(task);
6025
+ }
6026
+ break;
6027
+ case 4 /* ChoreType.NODE_DIFF */:
6028
+ {
6029
+ const parentVirtualNode = chore.$target$;
6030
+ let jsx = chore.$payload$;
6031
+ if (isSignal(jsx)) {
6032
+ jsx = jsx.value;
5624
6033
  }
5625
- break;
5626
- case 5 /* ChoreType.NODE_PROP */:
5627
- {
5628
- const virtualNode = chore.$host$;
5629
- const payload = chore.$payload$;
5630
- let value = payload.$value$;
5631
- if (isSignal(value)) {
5632
- value = value.value;
5633
- }
5634
- const isConst = payload.$isConst$;
5635
- const journal = container.$journal$;
5636
- const property = chore.$idx$;
5637
- const serializedValue = serializeAttribute(property, value, payload.$scopedStyleIdPrefix$);
6034
+ returnValue = retryOnPromise(() => vnode_diff(container, jsx, parentVirtualNode, null));
6035
+ }
6036
+ break;
6037
+ case 5 /* ChoreType.NODE_PROP */:
6038
+ {
6039
+ const virtualNode = chore.$host$;
6040
+ const payload = chore.$payload$;
6041
+ let value = payload.$value$;
6042
+ if (isSignal(value)) {
6043
+ value = value.value;
6044
+ }
6045
+ const isConst = payload.$isConst$;
6046
+ const journal = container.$journal$;
6047
+ const property = chore.$idx$;
6048
+ const serializedValue = serializeAttribute(property, value, payload.$scopedStyleIdPrefix$);
6049
+ if (isServer) {
6050
+ container.addBackpatchEntry(chore.$host$.id, property, serializedValue);
6051
+ returnValue = null;
6052
+ }
6053
+ else {
5638
6054
  if (isConst) {
5639
6055
  const element = virtualNode[6 /* ElementVNodeProps.element */];
5640
6056
  journal.push(2 /* VNodeJournalOpCode.SetAttribute */, element, property, serializedValue);
@@ -5642,64 +6058,49 @@
5642
6058
  else {
5643
6059
  vnode_setAttr(journal, virtualNode, property, serializedValue);
5644
6060
  }
6061
+ returnValue = undefined;
5645
6062
  }
5646
- break;
5647
- case 1 /* ChoreType.QRL_RESOLVE */: {
5648
- {
5649
- const target = chore.$target$;
5650
- returnValue = !target.resolved ? target.resolve() : null;
5651
- }
5652
- break;
5653
6063
  }
5654
- case 7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */: {
5655
- {
5656
- const target = chore.$target$;
5657
- const effects = chore.$payload$;
6064
+ break;
6065
+ case 1 /* ChoreType.QRL_RESOLVE */: {
6066
+ {
6067
+ const target = chore.$target$;
6068
+ returnValue = (!target.resolved ? target.resolve() : null);
6069
+ }
6070
+ break;
6071
+ }
6072
+ case 7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */: {
6073
+ {
6074
+ const target = chore.$target$;
6075
+ const effects = chore.$payload$;
6076
+ if (!effects?.size) {
6077
+ break;
6078
+ }
6079
+ let shouldCompute = target instanceof ComputedSignalImpl || target instanceof WrappedSignalImpl;
6080
+ if (target instanceof AsyncComputedSignalImpl && effects !== target.$effects$) {
6081
+ shouldCompute = false;
6082
+ }
6083
+ if (shouldCompute) {
5658
6084
  const ctx = newInvokeContext();
5659
6085
  ctx.$container$ = container;
5660
- if (target instanceof ComputedSignalImpl || target instanceof WrappedSignalImpl) {
5661
- const forceRunEffects = target.$forceRunEffects$;
5662
- target.$forceRunEffects$ = false;
5663
- if (!effects?.size && !forceRunEffects) {
5664
- break;
6086
+ // needed for computed signals and throwing QRLs
6087
+ returnValue = maybeThen(retryOnPromise(() => invoke.call(target, ctx, target.$computeIfNeeded$)), () => {
6088
+ if (target.$flags$ & 2 /* SignalFlags.RUN_EFFECTS */) {
6089
+ target.$flags$ &= -3 /* SignalFlags.RUN_EFFECTS */;
6090
+ return retryOnPromise(() => triggerEffects(container, target, effects));
5665
6091
  }
5666
- // needed for computed signals and throwing QRLs
5667
- returnValue = maybeThen(retryOnPromise(() => invoke.call(target, ctx, target.$computeIfNeeded$)), (didChange) => {
5668
- if (didChange || forceRunEffects) {
5669
- return retryOnPromise(() => triggerEffects(container, target, effects));
5670
- }
5671
- });
5672
- }
5673
- else {
5674
- returnValue = retryOnPromise(() => triggerEffects(container, target, effects));
5675
- }
6092
+ });
6093
+ }
6094
+ else {
6095
+ returnValue = retryOnPromise(() => {
6096
+ triggerEffects(container, target, effects);
6097
+ });
5676
6098
  }
5677
- break;
5678
6099
  }
6100
+ break;
5679
6101
  }
5680
6102
  }
5681
- catch (e) {
5682
- returnValue = Promise.reject(e);
5683
- }
5684
- const after = (value, error) => {
5685
- currentChore = null;
5686
- chore.$executed$ = true;
5687
- if (error) {
5688
- container.handleError(error, host);
5689
- }
5690
- else {
5691
- chore.$returnValue$ = value;
5692
- chore.$resolve$?.(value);
5693
- }
5694
- };
5695
- if (isPromise(returnValue)) {
5696
- chore.$promise$ = returnValue.then(after, (error) => after(undefined, error));
5697
- chore.$resolve$?.(chore.$promise$);
5698
- chore.$resolve$ = undefined;
5699
- }
5700
- else {
5701
- after(returnValue);
5702
- }
6103
+ return returnValue;
5703
6104
  }
5704
6105
  /**
5705
6106
  * Compares two chores to determine their execution order in the scheduler's queue.
@@ -5728,15 +6129,6 @@
5728
6129
  else {
5729
6130
  assertFalse(vnode_isVNode(aHost), 'expected aHost to be SSRNode but it is a VNode');
5730
6131
  assertFalse(vnode_isVNode(bHost), 'expected bHost to be SSRNode but it is a VNode');
5731
- // we are running on the server.
5732
- // On server we can't schedule task for a different host!
5733
- // Server is SSR, and therefore scheduling for anything but the current host
5734
- // implies that things need to be re-run nad that is not supported because of streaming.
5735
- const errorMessage = `SERVER: during HTML streaming, re-running tasks on a different host is not allowed.
5736
- You are attempting to change a state that has already been streamed to the client.
5737
- This can lead to inconsistencies between Server-Side Rendering (SSR) and Client-Side Rendering (CSR).
5738
- Problematic Node: ${aHost.toString()}`;
5739
- logWarn(errorMessage);
5740
6132
  const hostDiff = ssrNodeDocumentPosition(aHost, bHost);
5741
6133
  if (hostDiff !== 0) {
5742
6134
  return hostDiff;
@@ -5760,8 +6152,14 @@
5760
6152
  // 1 means that we are going to process chores as FIFO
5761
6153
  return 1;
5762
6154
  }
5763
- // If the chore is the same as the current chore, we will run it again
5764
- if (b === currentChore) {
6155
+ // ensure that the effect chores are scheduled for the same target
6156
+ // TODO: can we do this better?
6157
+ if (a.$type$ === 7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */ &&
6158
+ b.$type$ === 7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */ &&
6159
+ ((a.$target$ instanceof StoreHandler && b.$target$ instanceof StoreHandler) ||
6160
+ (a.$target$ instanceof AsyncComputedSignalImpl &&
6161
+ b.$target$ instanceof AsyncComputedSignalImpl)) &&
6162
+ a.$payload$ !== b.$payload$) {
5765
6163
  return 1;
5766
6164
  }
5767
6165
  // The chores are the same and will run only once
@@ -5793,6 +6191,15 @@
5793
6191
  /// We need to ensure that the `queue` is sorted by priority.
5794
6192
  /// 1. Find a place where to insert into.
5795
6193
  const idx = sortedFindIndex(sortedArray, value, rootVNode);
6194
+ if (idx < 0 && runningChores.size) {
6195
+ // 1.1. Check if the chore is already running.
6196
+ for (const chore of runningChores) {
6197
+ const comp = choreComparator(value, chore, rootVNode);
6198
+ if (comp === 0) {
6199
+ return chore;
6200
+ }
6201
+ }
6202
+ }
5796
6203
  if (idx < 0) {
5797
6204
  /// 2. Insert the chore into the queue.
5798
6205
  sortedArray.splice(~idx, 0, value);
@@ -5807,9 +6214,6 @@
5807
6214
  if (existing.$payload$ !== value.$payload$) {
5808
6215
  existing.$payload$ = value.$payload$;
5809
6216
  }
5810
- if (existing.$executed$) {
5811
- existing.$executed$ = false;
5812
- }
5813
6217
  return existing;
5814
6218
  }
5815
6219
  };
@@ -5821,6 +6225,25 @@
5821
6225
  vnode_isVNode(chore.$host$) &&
5822
6226
  chore.$host$[0 /* VNodeProps.flags */] & 32 /* VNodeFlags.Deleted */);
5823
6227
  }
6228
+ function addBlockedChore(blockedChore, blockingChore, blockedChores) {
6229
+ blockingChore.$blockedChores$ ||= [];
6230
+ blockingChore.$blockedChores$.push(blockedChore);
6231
+ blockedChores.add(blockedChore);
6232
+ }
6233
+ function choreTypeToName(type) {
6234
+ return ({
6235
+ [1 /* ChoreType.QRL_RESOLVE */]: 'Resolve QRL',
6236
+ [2 /* ChoreType.RUN_QRL */]: 'Run QRL',
6237
+ [3 /* ChoreType.TASK */]: 'Task',
6238
+ [4 /* ChoreType.NODE_DIFF */]: 'Changes diffing',
6239
+ [5 /* ChoreType.NODE_PROP */]: 'Updating node property',
6240
+ [6 /* ChoreType.COMPONENT */]: 'Component',
6241
+ [7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */]: 'Signal recompute',
6242
+ [16 /* ChoreType.VISIBLE */]: 'Visible',
6243
+ [32 /* ChoreType.CLEANUP_VISIBLE */]: 'Cleanup visible',
6244
+ [255 /* ChoreType.WAIT_FOR_QUEUE */]: 'Wait for queue',
6245
+ }[type] || 'Unknown: ' + type);
6246
+ }
5824
6247
  function debugChoreTypeToString(type) {
5825
6248
  return ({
5826
6249
  [1 /* ChoreType.QRL_RESOLVE */]: 'QRL_RESOLVE',
@@ -5830,31 +6253,87 @@
5830
6253
  [5 /* ChoreType.NODE_PROP */]: 'NODE_PROP',
5831
6254
  [6 /* ChoreType.COMPONENT */]: 'COMPONENT',
5832
6255
  [7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */]: 'RECOMPUTE_SIGNAL',
5833
- [16 /* ChoreType.JOURNAL_FLUSH */]: 'JOURNAL_FLUSH',
5834
- [32 /* ChoreType.VISIBLE */]: 'VISIBLE',
5835
- [48 /* ChoreType.CLEANUP_VISIBLE */]: 'CLEANUP_VISIBLE',
5836
- [255 /* ChoreType.WAIT_FOR_ALL */]: 'WAIT_FOR_ALL',
6256
+ [16 /* ChoreType.VISIBLE */]: 'VISIBLE',
6257
+ [32 /* ChoreType.CLEANUP_VISIBLE */]: 'CLEANUP_VISIBLE',
6258
+ [255 /* ChoreType.WAIT_FOR_QUEUE */]: 'WAIT_FOR_QUEUE',
5837
6259
  }[type] || 'UNKNOWN: ' + type);
5838
6260
  }
5839
- function debugChoreToString(chore) {
5840
- const type = debugChoreTypeToString(chore.$type$);
5841
- const host = String(chore.$host$).replaceAll(/\n.*/gim, '');
5842
- const qrlTarget = chore.$target$?.$symbol$;
5843
- return `Chore(${type} ${chore.$type$ === 1 /* ChoreType.QRL_RESOLVE */ || chore.$type$ === 2 /* ChoreType.RUN_QRL */ ? qrlTarget : host} ${chore.$idx$})`;
5844
- }
5845
- function debugTrace(action, arg, currentChore, queue) {
5846
- const lines = ['===========================\nScheduler: ' + action];
5847
- if (arg && !('$type$' in arg)) {
5848
- lines.push(' arg: ' + String(arg).replaceAll(/\n.*/gim, ''));
5849
- }
5850
- if (queue) {
5851
- queue.forEach((chore) => {
5852
- const active = chore === arg ? '>>>' : ' ';
5853
- lines.push(` ${active} > ` +
5854
- (chore === currentChore ? '[running] ' : '') +
5855
- debugChoreToString(chore));
6261
+ function debugTrace(action, arg, queue, blockedChores) {
6262
+ const lines = [];
6263
+ // Header
6264
+ lines.push(`Scheduler: ${action}`);
6265
+ // Argument section
6266
+ if (arg) {
6267
+ lines.push('');
6268
+ if (arg && '$type$' in arg) {
6269
+ const chore = arg;
6270
+ const type = debugChoreTypeToString(chore.$type$);
6271
+ const host = String(chore.$host$).replaceAll(/\n.*/gim, '');
6272
+ const qrlTarget = chore.$target$?.$symbol$;
6273
+ const targetOrHost = chore.$type$ === 1 /* ChoreType.QRL_RESOLVE */ || chore.$type$ === 2 /* ChoreType.RUN_QRL */
6274
+ ? qrlTarget
6275
+ : host;
6276
+ lines.push(`🎯 Current Chore:`);
6277
+ lines.push(` Type: ${type}`);
6278
+ lines.push(` Host: ${targetOrHost}`);
6279
+ // Show execution time if available
6280
+ if (chore.$startTime$ && chore.$endTime$) {
6281
+ const executionTime = chore.$endTime$ - chore.$startTime$;
6282
+ lines.push(` Time: ${executionTime.toFixed(2)}ms`);
6283
+ }
6284
+ else if (chore.$startTime$) {
6285
+ const elapsedTime = performance.now() - chore.$startTime$;
6286
+ lines.push(` Time: ${elapsedTime.toFixed(2)}ms (running)`);
6287
+ }
6288
+ // Show blocked chores for this chore
6289
+ if (chore.$blockedChores$ && chore.$blockedChores$.length > 0) {
6290
+ lines.push(` ⛔ Blocked Chores:`);
6291
+ chore.$blockedChores$.forEach((blockedChore, index) => {
6292
+ const blockedType = debugChoreTypeToString(blockedChore.$type$);
6293
+ const blockedTarget = String(blockedChore.$host$).replaceAll(/\n.*/gim, '');
6294
+ lines.push(` ${index + 1}. ${blockedType} ${blockedTarget} ${blockedChore.$idx$}`);
6295
+ });
6296
+ }
6297
+ }
6298
+ else {
6299
+ lines.push(`📝 Argument: ${String(arg).replaceAll(/\n.*/gim, '')}`);
6300
+ }
6301
+ }
6302
+ // Queue section
6303
+ if (queue && queue.length > 0) {
6304
+ lines.push('');
6305
+ lines.push(`📋 Queue (${queue.length} items):`);
6306
+ queue.forEach((chore, index) => {
6307
+ const isActive = chore === arg;
6308
+ const activeMarker = isActive ? `▶ ` : ' ';
6309
+ const type = debugChoreTypeToString(chore.$type$);
6310
+ const state = chore.$state$ ? `[${ChoreState[chore.$state$]}]` : '';
6311
+ const host = String(chore.$host$).replaceAll(/\n.*/gim, '');
6312
+ const qrlTarget = chore.$target$?.$symbol$;
6313
+ const target = chore.$type$ === 1 /* ChoreType.QRL_RESOLVE */ || chore.$type$ === 2 /* ChoreType.RUN_QRL */
6314
+ ? qrlTarget
6315
+ : host;
6316
+ const line = `${activeMarker}${state} ${type} ${target} ${chore.$idx$}`;
6317
+ lines.push(line);
6318
+ });
6319
+ }
6320
+ // Blocked chores section
6321
+ if (blockedChores && blockedChores.size > 0) {
6322
+ lines.push('');
6323
+ lines.push(`🚫 Blocked Chores (${blockedChores.size} items):`);
6324
+ Array.from(blockedChores).forEach((chore, index) => {
6325
+ const type = debugChoreTypeToString(chore.$type$);
6326
+ const host = String(chore.$host$).replaceAll(/\n.*/gim, '');
6327
+ const qrlTarget = chore.$target$?.$symbol$;
6328
+ const target = chore.$type$ === 1 /* ChoreType.QRL_RESOLVE */ || chore.$type$ === 2 /* ChoreType.RUN_QRL */
6329
+ ? qrlTarget
6330
+ : host;
6331
+ lines.push(` ${index + 1}. ${type} ${target} ${chore.$idx$}`);
5856
6332
  });
5857
6333
  }
6334
+ // Footer
6335
+ lines.push('');
6336
+ lines.push('─'.repeat(60));
5858
6337
  // eslint-disable-next-line no-console
5859
6338
  console.log(lines.join('\n') + '\n');
5860
6339
  }
@@ -5872,7 +6351,8 @@
5872
6351
  $currentUniqueId$ = 0;
5873
6352
  $instanceHash$ = null;
5874
6353
  $buildBase$ = null;
5875
- constructor(scheduleDrain, journalFlush, serverData, locale) {
6354
+ $flushEpoch$ = 0;
6355
+ constructor(journalFlush, serverData, locale) {
5876
6356
  this.$serverData$ = serverData;
5877
6357
  this.$locale$ = locale;
5878
6358
  this.$version$ = version;
@@ -5880,7 +6360,7 @@
5880
6360
  this.$getObjectById$ = (_id) => {
5881
6361
  throw Error('Not implemented');
5882
6362
  };
5883
- this.$scheduler$ = createScheduler(this, scheduleDrain, journalFlush);
6363
+ this.$scheduler$ = createScheduler(this, journalFlush);
5884
6364
  }
5885
6365
  trackSignalValue(signal, subscriber, property, data) {
5886
6366
  return trackSignalAndAssignHost(signal, subscriber, property, this, data);
@@ -6189,9 +6669,10 @@
6189
6669
  const shadowRootContainer = node;
6190
6670
  const shadowRoot = shadowRootContainer?.shadowRoot;
6191
6671
  if (shadowRoot) {
6672
+ const firstShadowRootChild = firstChild(shadowRoot);
6192
6673
  walkContainer(
6193
6674
  // we need to create a new walker for the shadow root
6194
- document.createTreeWalker(shadowRoot, 0x1 /* NodeFilter.SHOW_ELEMENT */ | 0x80 /* NodeFilter.SHOW_COMMENT */), null, firstChild(shadowRoot), null, '', null);
6675
+ document.createTreeWalker(firstShadowRootChild, 0x1 /* NodeFilter.SHOW_ELEMENT */ | 0x80 /* NodeFilter.SHOW_COMMENT */), null, firstShadowRootChild, null, '', null);
6195
6676
  }
6196
6677
  }
6197
6678
  if ((nodeType & 2 /* NodeType.ELEMENT */) === 2 /* NodeType.ELEMENT */) {
@@ -6267,7 +6748,7 @@
6267
6748
  /** @internal */
6268
6749
  function _getQContainerElement(element) {
6269
6750
  const qContainerElement = Array.isArray(element)
6270
- ? vnode_getDomParent(element)
6751
+ ? vnode_getDomParent(element, true)
6271
6752
  : element;
6272
6753
  return qContainerElement.closest(QContainerSelector);
6273
6754
  }
@@ -6282,7 +6763,6 @@
6282
6763
  rootVNode;
6283
6764
  document;
6284
6765
  $journal$;
6285
- renderDone = null;
6286
6766
  $rawStateData$;
6287
6767
  $storeProxyMap$ = new WeakMap();
6288
6768
  $qFuncs$;
@@ -6292,9 +6772,11 @@
6292
6772
  vNodeLocate = (id) => vnode_locate(this.rootVNode, id);
6293
6773
  $stateData$;
6294
6774
  $styleIds$ = null;
6295
- $renderCount$ = 0;
6296
6775
  constructor(element) {
6297
- super(() => this.scheduleRender(), () => vnode_applyJournal(this.$journal$), {}, element.getAttribute(QLocaleAttr));
6776
+ super(() => {
6777
+ this.$flushEpoch$++;
6778
+ vnode_applyJournal(this.$journal$);
6779
+ }, {}, element.getAttribute(QLocaleAttr));
6298
6780
  this.qContainer = element.getAttribute(QContainerAttr);
6299
6781
  if (!this.qContainer) {
6300
6782
  throw qError(25 /* QError.elementWithoutContainer */);
@@ -6336,20 +6818,17 @@
6336
6818
  }
6337
6819
  handleError(err, host) {
6338
6820
  if (qDev && host) {
6339
- // Clean vdom
6340
6821
  if (typeof document !== 'undefined') {
6341
6822
  const vHost = host;
6342
- const errorDiv = document.createElement('errored-host');
6343
- if (err && err instanceof Error) {
6344
- errorDiv.props = { error: err };
6345
- }
6346
- errorDiv.setAttribute('q:key', '_error_');
6347
6823
  const journal = [];
6348
- const vErrorDiv = vnode_newElement(errorDiv, 'errored-host');
6349
- vnode_getDOMChildNodes(journal, vHost, true).forEach((child) => {
6350
- vnode_insertBefore(journal, vErrorDiv, child, null);
6351
- });
6352
- vnode_insertBefore(journal, vHost, vErrorDiv, null);
6824
+ const vHostParent = vnode_getParent(vHost);
6825
+ const vHostNextSibling = vnode_getNextSibling(vHost);
6826
+ const vErrorDiv = vnode_createErrorDiv(document, vHost, err, journal);
6827
+ // If the host is an element node, we need to insert the error div into its parent.
6828
+ const insertHost = vnode_isElementVNode(vHost) ? vHostParent || vHost : vHost;
6829
+ // If the host is different then we need to insert errored-host in the same position as the host.
6830
+ const insertBefore = insertHost === vHost ? null : vHostNextSibling;
6831
+ vnode_insertBefore(journal, insertHost, vErrorDiv, insertBefore);
6353
6832
  vnode_applyJournal(journal);
6354
6833
  }
6355
6834
  if (err && err instanceof Error) {
@@ -6424,29 +6903,6 @@
6424
6903
  }
6425
6904
  return vnode_getProp(vNode, name, getObjectById);
6426
6905
  }
6427
- scheduleRender() {
6428
- this.$renderCount$++;
6429
- this.renderDone ||= getPlatform().nextTick(() => this.processChores());
6430
- return this.renderDone.finally(() => emitEvent('qrender', { instanceHash: this.$instanceHash$, renderCount: this.$renderCount$ }));
6431
- }
6432
- processChores() {
6433
- let renderCount = this.$renderCount$;
6434
- const result = this.$scheduler$(255 /* ChoreType.WAIT_FOR_ALL */);
6435
- if (isPromise(result)) {
6436
- return result.then(async () => {
6437
- while (renderCount !== this.$renderCount$) {
6438
- renderCount = this.$renderCount$;
6439
- await this.$scheduler$(255 /* ChoreType.WAIT_FOR_ALL */);
6440
- }
6441
- this.renderDone = null;
6442
- });
6443
- }
6444
- if (renderCount !== this.$renderCount$) {
6445
- this.processChores();
6446
- return;
6447
- }
6448
- this.renderDone = null;
6449
- }
6450
6906
  ensureProjectionResolved(vNode) {
6451
6907
  if ((vNode[0 /* VNodeProps.flags */] & 16 /* VNodeFlags.Resolved */) === 0) {
6452
6908
  vNode[0 /* VNodeProps.flags */] |= 16 /* VNodeFlags.Resolved */;
@@ -6456,7 +6912,9 @@
6456
6912
  if (isSlotProp(prop)) {
6457
6913
  const value = props[i + 1];
6458
6914
  if (typeof value == 'string') {
6459
- props[i + 1] = this.vNodeLocate(value);
6915
+ const projection = this.vNodeLocate(value);
6916
+ props[i + 1] = projection;
6917
+ vnode_getProp(projection, QSlotParent, (id) => this.vNodeLocate(id));
6460
6918
  }
6461
6919
  }
6462
6920
  }
@@ -6577,7 +7035,7 @@
6577
7035
  }
6578
7036
  };
6579
7037
  const triggerEffects = (container, signal, effects) => {
6580
- const isBrowser = isDomContainer(container);
7038
+ const isBrowser = !isServerPlatform();
6581
7039
  if (effects) {
6582
7040
  const scheduleEffect = (effectSubscription) => {
6583
7041
  const consumer = effectSubscription[0 /* EffectSubscriptionProp.CONSUMER */];
@@ -6587,7 +7045,7 @@
6587
7045
  consumer.$flags$ |= 8 /* TaskFlags.DIRTY */;
6588
7046
  let choreType = 3 /* ChoreType.TASK */;
6589
7047
  if (consumer.$flags$ & 1 /* TaskFlags.VISIBLE_TASK */) {
6590
- choreType = 32 /* ChoreType.VISIBLE */;
7048
+ choreType = 16 /* ChoreType.VISIBLE */;
6591
7049
  }
6592
7050
  container.$scheduler$(choreType, consumer);
6593
7051
  }
@@ -6610,22 +7068,22 @@
6610
7068
  const props = container.getHostProp(host, ELEMENT_PROPS);
6611
7069
  container.$scheduler$(6 /* ChoreType.COMPONENT */, host, qrl, props);
6612
7070
  }
6613
- else if (isBrowser) {
6614
- if (property === "." /* EffectProperty.VNODE */) {
7071
+ else if (property === "." /* EffectProperty.VNODE */) {
7072
+ if (isBrowser) {
6615
7073
  const host = consumer;
6616
7074
  container.$scheduler$(4 /* ChoreType.NODE_DIFF */, host, host, signal);
6617
7075
  }
6618
- else {
6619
- const host = consumer;
6620
- const effectData = effectSubscription[3 /* EffectSubscriptionProp.DATA */];
6621
- if (effectData instanceof SubscriptionData) {
6622
- const data = effectData.data;
6623
- const payload = {
6624
- ...data,
6625
- $value$: signal,
6626
- };
6627
- container.$scheduler$(5 /* ChoreType.NODE_PROP */, host, property, payload);
6628
- }
7076
+ }
7077
+ else {
7078
+ const host = consumer;
7079
+ const effectData = effectSubscription[3 /* EffectSubscriptionProp.DATA */];
7080
+ if (effectData instanceof SubscriptionData) {
7081
+ const data = effectData.data;
7082
+ const payload = {
7083
+ ...data,
7084
+ $value$: signal,
7085
+ };
7086
+ container.$scheduler$(5 /* ChoreType.NODE_PROP */, host, property, payload);
6629
7087
  }
6630
7088
  }
6631
7089
  };
@@ -6646,10 +7104,10 @@
6646
7104
  // flags |= ComputedSignalFlags.SERIALIZATION_STRATEGY_AUTO;
6647
7105
  // break;
6648
7106
  case 'never':
6649
- flags |= 8 /* ComputedSignalFlags.SERIALIZATION_STRATEGY_NEVER */;
7107
+ flags |= 16 /* SerializationSignalFlags.SERIALIZATION_STRATEGY_NEVER */;
6650
7108
  break;
6651
7109
  case 'always':
6652
- flags |= 16 /* ComputedSignalFlags.SERIALIZATION_STRATEGY_ALWAYS */;
7110
+ flags |= 32 /* SerializationSignalFlags.SERIALIZATION_STRATEGY_ALWAYS */;
6653
7111
  break;
6654
7112
  }
6655
7113
  return flags;
@@ -7041,6 +7499,9 @@
7041
7499
  }
7042
7500
  let vParent = null;
7043
7501
  do {
7502
+ if (callback?.(vCursor, vParent)) {
7503
+ return;
7504
+ }
7044
7505
  const vFirstChild = vnode_getFirstChild(vCursor);
7045
7506
  if (vFirstChild) {
7046
7507
  vCursor = vFirstChild;
@@ -7209,6 +7670,7 @@
7209
7670
  const flags = textVNode[0 /* VNodeProps.flags */];
7210
7671
  if ((flags & 8 /* VNodeFlags.Inflated */) === 0) {
7211
7672
  const parentNode = vnode_getDomParent(vnode);
7673
+ assertDefined(parentNode, 'Missing parent node.');
7212
7674
  const sharedTextNode = textVNode[4 /* TextVNodeProps.node */];
7213
7675
  const doc = parentNode.ownerDocument;
7214
7676
  // Walk the previous siblings and inflate them.
@@ -7367,6 +7829,18 @@
7367
7829
  }
7368
7830
  return length;
7369
7831
  };
7832
+ const vnode_createErrorDiv = (document, host, err, journal) => {
7833
+ const errorDiv = document.createElement('errored-host');
7834
+ if (err && err instanceof Error) {
7835
+ errorDiv.props = { error: err };
7836
+ }
7837
+ errorDiv.setAttribute('q:key', '_error_');
7838
+ const vErrorDiv = vnode_newElement(errorDiv, 'errored-host');
7839
+ vnode_getDOMChildNodes(journal, host, true).forEach((child) => {
7840
+ vnode_insertBefore(journal, vErrorDiv, child, null);
7841
+ });
7842
+ return vErrorDiv;
7843
+ };
7370
7844
  const parseBoolean = (value) => {
7371
7845
  if (value === 'false') {
7372
7846
  return false;
@@ -7505,7 +7979,7 @@
7505
7979
  * unlink the previous or next sibling, we don't know that after "a" node is "b". So we need to
7506
7980
  * find children first (and inflate them).
7507
7981
  */
7508
- const domParentVNode = vnode_getDomParentVNode(parent);
7982
+ const domParentVNode = vnode_getDomParentVNode(parent, false);
7509
7983
  const parentNode = domParentVNode && domParentVNode[6 /* ElementVNodeProps.element */];
7510
7984
  let domChildren = null;
7511
7985
  if (domParentVNode) {
@@ -7554,27 +8028,31 @@
7554
8028
  newChildCurrentParent !== parent)) {
7555
8029
  vnode_remove(journal, newChildCurrentParent, newChild, false);
7556
8030
  }
7557
- let adjustedInsertBefore = null;
7558
- if (insertBefore == null) {
7559
- if (vnode_isVirtualVNode(parent)) {
7560
- // If `insertBefore` is null, than we need to insert at the end of the list.
7561
- // Well, not quite. If the parent is a virtual node, our "last node" is not the same
7562
- // as the DOM "last node". So in that case we need to look for the "next node" from
7563
- // our parent.
7564
- adjustedInsertBefore = vnode_getDomSibling(parent, true, false);
8031
+ const parentIsDeleted = parent[0 /* VNodeProps.flags */] & 32 /* VNodeFlags.Deleted */;
8032
+ // if the parent is deleted, then we don't need to insert the new child
8033
+ if (!parentIsDeleted) {
8034
+ let adjustedInsertBefore = null;
8035
+ if (insertBefore == null) {
8036
+ if (vnode_isVirtualVNode(parent)) {
8037
+ // If `insertBefore` is null, than we need to insert at the end of the list.
8038
+ // Well, not quite. If the parent is a virtual node, our "last node" is not the same
8039
+ // as the DOM "last node". So in that case we need to look for the "next node" from
8040
+ // our parent.
8041
+ adjustedInsertBefore = vnode_getDomSibling(parent, true, false);
8042
+ }
8043
+ }
8044
+ else if (vnode_isVirtualVNode(insertBefore)) {
8045
+ // If the `insertBefore` is virtual, than we need to descend into the virtual and find e actual
8046
+ adjustedInsertBefore = vnode_getDomSibling(insertBefore, true, true);
8047
+ }
8048
+ else {
8049
+ adjustedInsertBefore = insertBefore;
8050
+ }
8051
+ adjustedInsertBefore && vnode_ensureInflatedIfText(journal, adjustedInsertBefore);
8052
+ // Here we know the insertBefore node
8053
+ if (domChildren && domChildren.length) {
8054
+ journal.push(5 /* VNodeJournalOpCode.Insert */, parentNode, vnode_getNode(adjustedInsertBefore), ...domChildren);
7565
8055
  }
7566
- }
7567
- else if (vnode_isVirtualVNode(insertBefore)) {
7568
- // If the `insertBefore` is virtual, than we need to descend into the virtual and find e actual
7569
- adjustedInsertBefore = vnode_getDomSibling(insertBefore, true, true);
7570
- }
7571
- else {
7572
- adjustedInsertBefore = insertBefore;
7573
- }
7574
- adjustedInsertBefore && vnode_ensureInflatedIfText(journal, adjustedInsertBefore);
7575
- // Here we know the insertBefore node
7576
- if (domChildren && domChildren.length) {
7577
- journal.push(5 /* VNodeJournalOpCode.Insert */, parentNode, vnode_getNode(adjustedInsertBefore), ...domChildren);
7578
8056
  }
7579
8057
  // link newChild into the previous/next list
7580
8058
  const vNext = insertBefore;
@@ -7596,14 +8074,22 @@
7596
8074
  newChild[2 /* VNodeProps.previousSibling */] = vPrevious;
7597
8075
  newChild[3 /* VNodeProps.nextSibling */] = vNext;
7598
8076
  newChild[1 /* VNodeProps.parent */] = parent;
8077
+ if (parentIsDeleted) {
8078
+ // if the parent is deleted, then the new child is also deleted
8079
+ newChild[0 /* VNodeProps.flags */] |= 32 /* VNodeFlags.Deleted */;
8080
+ }
7599
8081
  };
7600
- const vnode_getDomParent = (vnode) => {
7601
- vnode = vnode_getDomParentVNode(vnode);
8082
+ const vnode_getDomParent = (vnode, includeProjection = true) => {
8083
+ vnode = vnode_getDomParentVNode(vnode, includeProjection);
7602
8084
  return (vnode && vnode[6 /* ElementVNodeProps.element */]);
7603
8085
  };
7604
- const vnode_getDomParentVNode = (vnode) => {
8086
+ const vnode_getDomParentVNode = (vnode, includeProjection = true) => {
7605
8087
  while (vnode && !vnode_isElementVNode(vnode)) {
7606
- vnode = vnode[1 /* VNodeProps.parent */];
8088
+ vnode =
8089
+ vnode[1 /* VNodeProps.parent */] ||
8090
+ (includeProjection
8091
+ ? vnode_getProp(vnode, QSlotParent, (id) => (vnode_isVNode(id) ? id : null))
8092
+ : null);
7607
8093
  }
7608
8094
  return vnode;
7609
8095
  };
@@ -7613,7 +8099,7 @@
7613
8099
  vnode_ensureTextInflated(journal, vToRemove);
7614
8100
  }
7615
8101
  if (removeDOM) {
7616
- const domParent = vnode_getDomParent(vParent);
8102
+ const domParent = vnode_getDomParent(vParent, false);
7617
8103
  const isInnerHTMLParent = vnode_getAttr(vParent, dangerouslySetInnerHTML);
7618
8104
  if (isInnerHTMLParent) {
7619
8105
  // ignore children, as they are inserted via innerHTML
@@ -8094,6 +8580,26 @@
8094
8580
  const vnode_getParent = (vnode) => {
8095
8581
  return vnode[1 /* VNodeProps.parent */] || null;
8096
8582
  };
8583
+ const vnode_isDescendantOf = (vnode, ancestor, rootVNode) => {
8584
+ let parent = vnode_getParentOrProjectionParent(vnode, rootVNode);
8585
+ while (parent) {
8586
+ if (parent === ancestor) {
8587
+ return true;
8588
+ }
8589
+ parent = vnode_getParentOrProjectionParent(parent, rootVNode);
8590
+ }
8591
+ return false;
8592
+ };
8593
+ const vnode_getParentOrProjectionParent = (vnode, rootVNode) => {
8594
+ if (rootVNode) {
8595
+ const parentProjection = vnode_getProp(vnode, QSlotParent, (id) => vnode_locate(rootVNode, id));
8596
+ if (parentProjection) {
8597
+ // This is a projection, so we need to check the parent of the projection
8598
+ return parentProjection;
8599
+ }
8600
+ }
8601
+ return vnode_getParent(vnode);
8602
+ };
8097
8603
  const vnode_getNode = (vnode) => {
8098
8604
  if (vnode === null || vnode_isVirtualVNode(vnode)) {
8099
8605
  return null;
@@ -8453,13 +8959,15 @@
8453
8959
  }
8454
8960
  const container = this.$container$;
8455
8961
  let propValue = allocate(container, typeId, value);
8962
+ Reflect.set(target, property, propValue);
8963
+ this.$data$[idx] = undefined;
8964
+ this.$data$[idx + 1] = propValue;
8456
8965
  /** We stored the reference, so now we can inflate, allowing cycles. */
8457
8966
  if (typeId >= 14 /* TypeIds.Error */) {
8458
8967
  propValue = inflate(container, propValue, typeId, value);
8968
+ Reflect.set(target, property, propValue);
8969
+ this.$data$[idx + 1] = propValue;
8459
8970
  }
8460
- Reflect.set(target, property, propValue);
8461
- this.$data$[idx] = undefined;
8462
- this.$data$[idx + 1] = propValue;
8463
8971
  return propValue;
8464
8972
  }
8465
8973
  has(target, property) {
@@ -8638,7 +9146,7 @@
8638
9146
  */
8639
9147
  // try to download qrl in this tick
8640
9148
  computed.$computeQrl$.resolve();
8641
- container.$scheduler$?.(1 /* ChoreType.QRL_RESOLVE */, null, computed.$computeQrl$);
9149
+ container.$scheduler$(1 /* ChoreType.QRL_RESOLVE */, null, computed.$computeQrl$);
8642
9150
  }
8643
9151
  break;
8644
9152
  }
@@ -9426,8 +9934,8 @@
9426
9934
  }
9427
9935
  else if (value instanceof ComputedSignalImpl) {
9428
9936
  let v = value.$untrackedValue$;
9429
- const shouldAlwaysSerialize = value.$flags$ & 16 /* ComputedSignalFlags.SERIALIZATION_STRATEGY_ALWAYS */;
9430
- const shouldNeverSerialize = value.$flags$ & 8 /* ComputedSignalFlags.SERIALIZATION_STRATEGY_NEVER */;
9937
+ const shouldAlwaysSerialize = value.$flags$ & 32 /* SerializationSignalFlags.SERIALIZATION_STRATEGY_ALWAYS */;
9938
+ const shouldNeverSerialize = value.$flags$ & 16 /* SerializationSignalFlags.SERIALIZATION_STRATEGY_NEVER */;
9431
9939
  const isInvalid = value.$flags$ & 1 /* SignalFlags.INVALID */;
9432
9940
  const isSkippable = fastSkipSerialize(value.$untrackedValue$);
9433
9941
  if (shouldAlwaysSerialize) {
@@ -10769,7 +11277,7 @@
10769
11277
  container.$serverData$ = opts.serverData || {};
10770
11278
  const host = container.rootVNode;
10771
11279
  container.$scheduler$(4 /* ChoreType.NODE_DIFF */, host, host, jsxNode);
10772
- await container.$scheduler$(255 /* ChoreType.WAIT_FOR_ALL */);
11280
+ await container.$scheduler$(255 /* ChoreType.WAIT_FOR_QUEUE */).$returnValue$;
10773
11281
  return {
10774
11282
  cleanup: () => {
10775
11283
  cleanup(container, container.rootVNode);
@@ -11607,7 +12115,7 @@
11607
12115
  useRunTask(task, eagerness);
11608
12116
  if (!isServerPlatform()) {
11609
12117
  qrl.resolve(iCtx.$element$);
11610
- iCtx.$container$.$scheduler$(32 /* ChoreType.VISIBLE */, task);
12118
+ iCtx.$container$.$scheduler$(16 /* ChoreType.VISIBLE */, task);
11611
12119
  }
11612
12120
  };
11613
12121
  const useRunTask = (task, eagerness) => {
@@ -11885,6 +12393,16 @@
11885
12393
  */
11886
12394
  const PrefetchGraph = (_opts = {}) => null;
11887
12395
 
12396
+ //////////////////////////////////////////////////////////////////////////////////////////
12397
+ // Protect against duplicate imports
12398
+ //////////////////////////////////////////////////////////////////////////////////////////
12399
+ if (globalThis.__qwik) {
12400
+ console.error(`==============================================\n` +
12401
+ `Qwik version ${globalThis.__qwik} already imported while importing ${version}. Verify external vs bundled imports etc. This can lead to issues due to duplicated shared structures.\n` +
12402
+ `==============================================\n`);
12403
+ }
12404
+ globalThis.__qwik = version;
12405
+
11888
12406
  Object.defineProperty(exports, "isBrowser", {
11889
12407
  enumerable: true,
11890
12408
  get: function () { return build.isBrowser; }
@@ -11930,6 +12448,7 @@
11930
12448
  exports._getDomContainer = getDomContainer;
11931
12449
  exports._getQContainerElement = _getQContainerElement;
11932
12450
  exports._getVarProps = _getVarProps;
12451
+ exports._hasStoreEffects = _hasStoreEffects;
11933
12452
  exports._isJSXNode = isJSXNode;
11934
12453
  exports._isStore = isStore;
11935
12454
  exports._isStringifiable = isStringifiable;
@@ -11950,7 +12469,7 @@
11950
12469
  exports._regSymbol = _regSymbol;
11951
12470
  exports._resolveContextWithoutSequentialScope = _resolveContextWithoutSequentialScope;
11952
12471
  exports._restProps = _restProps;
11953
- exports._run = queueQRL;
12472
+ exports._run = _run;
11954
12473
  exports._serializationWeakRef = _serializationWeakRef;
11955
12474
  exports._serialize = _serialize;
11956
12475
  exports._task = scheduleTask;
@@ -11983,6 +12502,7 @@
11983
12502
  exports.createSignal = createSignal;
11984
12503
  exports.event$ = event$;
11985
12504
  exports.eventQrl = eventQrl;
12505
+ exports.forceStoreEffects = forceStoreEffects;
11986
12506
  exports.getDomContainer = getDomContainer;
11987
12507
  exports.getLocale = getLocale;
11988
12508
  exports.getPlatform = getPlatform;