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