ripple 0.4.1 → 0.4.3

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.
Files changed (96) hide show
  1. package/CHANGELOG.md +162 -0
  2. package/package.json +9 -3
  3. package/src/constants.js +4 -0
  4. package/src/jsx-runtime.d.ts +2 -1
  5. package/src/runtime/array.js +15 -9
  6. package/src/runtime/index-client.js +25 -7
  7. package/src/runtime/index-server.js +1 -0
  8. package/src/runtime/internal/client/attributes.js +313 -0
  9. package/src/runtime/internal/client/blocks.js +4 -14
  10. package/src/runtime/internal/client/component.js +2 -5
  11. package/src/runtime/internal/client/composite.js +14 -6
  12. package/src/runtime/internal/client/constants.js +12 -6
  13. package/src/runtime/internal/client/context.js +3 -2
  14. package/src/runtime/internal/client/css.js +11 -26
  15. package/src/runtime/internal/client/errors.js +188 -0
  16. package/src/runtime/internal/client/events.js +79 -34
  17. package/src/runtime/internal/client/expression.js +21 -116
  18. package/src/runtime/internal/client/for.js +272 -222
  19. package/src/runtime/internal/client/head.js +4 -3
  20. package/src/runtime/internal/client/html.js +5 -3
  21. package/src/runtime/internal/client/hydrate.js +484 -0
  22. package/src/runtime/internal/client/hydration-enabled.js +7 -0
  23. package/src/runtime/internal/client/hydration.js +16 -97
  24. package/src/runtime/internal/client/if.js +13 -9
  25. package/src/runtime/internal/client/index.js +16 -8
  26. package/src/runtime/internal/client/operations.js +12 -9
  27. package/src/runtime/internal/client/portal.js +3 -2
  28. package/src/runtime/internal/client/render.js +75 -321
  29. package/src/runtime/internal/client/root-boundary-enabled.js +7 -0
  30. package/src/runtime/internal/client/runtime.js +129 -362
  31. package/src/runtime/internal/client/template-ns.js +47 -0
  32. package/src/runtime/internal/client/template.js +63 -140
  33. package/src/runtime/internal/client/track-async.js +356 -0
  34. package/src/runtime/internal/client/try.js +133 -319
  35. package/src/runtime/internal/client/types.d.ts +48 -24
  36. package/src/runtime/internal/server/index.js +38 -33
  37. package/src/runtime/internal/server/prerender.js +27 -0
  38. package/src/server/index.js +1 -0
  39. package/src/utils/class-name.js +28 -0
  40. package/tests/client/basic/basic.errors.test.tsrx +3 -3
  41. package/tests/client/compiler/__snapshots__/compiler.typescript.test.tsrx.snap +0 -2
  42. package/tests/client/compiler/compiler.basic.test.tsrx +4 -4
  43. package/tests/client/compiler/compiler.tracked-access.test.tsrx +3 -3
  44. package/tests/client/composite/composite.props.test.tsrx +10 -10
  45. package/tests/client/derived-release.test.tsrx +3 -3
  46. package/tests/client/for-item.test.tsrx +443 -0
  47. package/tests/client/for-selector.test.tsrx +95 -0
  48. package/tests/hydration/compiled/client/basic.js +97 -123
  49. package/tests/hydration/compiled/client/composite.js +5 -5
  50. package/tests/hydration/compiled/client/events.js +84 -79
  51. package/tests/hydration/compiled/client/for.js +229 -233
  52. package/tests/hydration/compiled/client/fragment-cursor.js +781 -481
  53. package/tests/hydration/compiled/client/head.js +23 -26
  54. package/tests/hydration/compiled/client/hmr.js +4 -4
  55. package/tests/hydration/compiled/client/html-in-template.js +5 -5
  56. package/tests/hydration/compiled/client/html.js +174 -208
  57. package/tests/hydration/compiled/client/if-children.js +71 -70
  58. package/tests/hydration/compiled/client/if-fragment-controlflow.js +46 -46
  59. package/tests/hydration/compiled/client/if.js +26 -26
  60. package/tests/hydration/compiled/client/mixed-control-flow.js +63 -105
  61. package/tests/hydration/compiled/client/nested-control-flow.js +170 -390
  62. package/tests/hydration/compiled/client/portal.js +10 -10
  63. package/tests/hydration/compiled/client/reactivity.js +36 -50
  64. package/tests/hydration/compiled/client/return.js +2 -2
  65. package/tests/hydration/compiled/client/streaming.js +34 -40
  66. package/tests/hydration/compiled/client/switch.js +25 -25
  67. package/tests/hydration/compiled/client/track-async-serialization.js +41 -41
  68. package/tests/hydration/compiled/client/try.js +15 -15
  69. package/tests/hydration/compiled/client/typed-text.js +8 -19
  70. package/tests/hydration/compiled/server/basic.js +6 -6
  71. package/tests/hydration/compiled/server/events.js +13 -61
  72. package/tests/hydration/compiled/server/for.js +23 -31
  73. package/tests/hydration/compiled/server/fragment-cursor.js +214 -366
  74. package/tests/hydration/compiled/server/head.js +6 -6
  75. package/tests/hydration/compiled/server/hmr.js +1 -1
  76. package/tests/hydration/compiled/server/html.js +1 -1
  77. package/tests/hydration/compiled/server/if-children.js +9 -9
  78. package/tests/hydration/compiled/server/if.js +5 -5
  79. package/tests/hydration/compiled/server/mixed-control-flow.js +4 -4
  80. package/tests/hydration/compiled/server/portal.js +1 -1
  81. package/tests/hydration/compiled/server/reactivity.js +9 -9
  82. package/tests/hydration/compiled/server/streaming.js +9 -9
  83. package/tests/hydration/compiled/server/switch.js +4 -4
  84. package/tests/hydration/compiled/server/track-async-serialization.js +16 -16
  85. package/tests/hydration/compiled/server/try.js +4 -4
  86. package/tests/hydration/compiled/server/typed-text.js +1 -1
  87. package/tests/hydration/components/fragment-cursor.tsrx +60 -0
  88. package/tests/hydration/components/track-async-serialization.tsrx +2 -2
  89. package/tests/hydration/fragment-cursor.test.js +34 -1
  90. package/tests/hydration/track-async-serialization.test.js +1 -1
  91. package/tests/server/basic.components.test.tsrx +3 -3
  92. package/tests/server/basic.test.tsrx +23 -0
  93. package/tests/server/track-async-serialization.test.tsrx +6 -6
  94. package/tests/utils/tracked-types.test.js +3 -3
  95. package/types/index.d.ts +8 -8
  96. package/types/server.d.ts +17 -0
@@ -4,13 +4,7 @@
4
4
  /** @typedef {DerivedValue} Derived */
5
5
 
6
6
  import { DEV } from 'esm-env';
7
- import {
8
- destroy_block,
9
- destroy_non_branch_children,
10
- effect,
11
- pause_block,
12
- pre_effect,
13
- } from './blocks.js';
7
+ import { destroy_block, destroy_non_branch_children, effect } from './blocks.js';
14
8
  import {
15
9
  ASYNC_DERIVED_READ_THROWN,
16
10
  BLOCK_HAS_RUN,
@@ -28,29 +22,18 @@ import {
28
22
  UNINITIALIZED,
29
23
  REF_PROP,
30
24
  DEFAULT_NAMESPACE,
31
- TRACKED_UPDATED,
32
25
  SUSPENSE_PENDING,
33
26
  SUSPENSE_REJECTED,
34
- TRY_BLOCK,
35
- DIRECT_CHILD_BLOCK,
36
27
  SCHEDULED,
37
28
  SELECTOR,
38
29
  IF_BLOCK,
30
+ ITEM_BLOCK,
39
31
  RELEASED,
40
32
  RENDER_ENTRY,
41
33
  CREATES_DERIVEDS,
42
34
  } from './constants.js';
43
- import {
44
- begin_boundary_request,
45
- complete_boundary_request,
46
- get_boundary_with_catch,
47
- get_pending_boundary,
48
- handle_boundary_error,
49
- register_boundary_deferred,
50
- register_boundary_paused_block,
51
- replace_boundary_request,
52
- } from './try.js';
53
35
  import { is_ripple_object } from './utils.js';
36
+ import { scope_orphan, set_in_derived, track_orphan, update_depth_exceeded } from './errors.js';
54
37
  import { render_value } from './expression.js';
55
38
  import { throw_invalid_component_type } from './component.js';
56
39
 
@@ -60,10 +43,8 @@ import {
60
43
  is_array,
61
44
  object_keys,
62
45
  } from '@tsrx/core/runtime/language-helpers';
63
- import { get_async_track_result } from '../../../utils/async.js';
64
- import { get_track_async_script_id } from '../../../utils/track-async-serialization.js';
65
- import { revive } from './transport.js';
66
46
  import { hydrating, track_hash_reference } from './hydration.js';
47
+ import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
67
48
  import { create_ref_prop as create_core_ref_prop } from '@tsrx/core/runtime/ref';
68
49
 
69
50
  const FLUSH_MICROTASK = 0;
@@ -129,7 +110,7 @@ let flush_count = 0;
129
110
  /** @type {(() => void)[]} */
130
111
  var queued_post_block_flush = [];
131
112
  /** @type {null | Dependency} */
132
- let active_dependency = null;
113
+ export let active_dependency = null;
133
114
 
134
115
  export let tracking = false;
135
116
  export let teardown = false;
@@ -169,6 +150,28 @@ export function set_tracking(value) {
169
150
  tracking = value;
170
151
  }
171
152
 
153
+ /**
154
+ * @param {boolean} value
155
+ */
156
+ export function set_mutating_allowed(value) {
157
+ is_mutating_allowed = value;
158
+ }
159
+
160
+ /**
161
+ * Handles a block whose run read a pending async value (see
162
+ * `track-async.js`). Installed by the first `trackAsync()`, so an application
163
+ * without one carries none of the boundary request machinery.
164
+ * @type {((block: Block) => void) | null}
165
+ */
166
+ let pending_read = null;
167
+
168
+ /**
169
+ * @param {(block: Block) => void} fn
170
+ */
171
+ export function set_pending_read_handler(fn) {
172
+ pending_read = fn;
173
+ }
174
+
172
175
  /**
173
176
  * @param {Block} block
174
177
  */
@@ -235,7 +238,7 @@ function update_derived(computed) {
235
238
  * @param {Tracked} tracked
236
239
  * @param {any} value
237
240
  */
238
- function update_tracked_value_clock(tracked, value) {
241
+ export function update_tracked_value_clock(tracked, value) {
239
242
  tracked.__v = value;
240
243
  tracked.c = increment_clock();
241
244
  mark_subscribers(tracked);
@@ -305,76 +308,51 @@ function run_derived(computed) {
305
308
  }
306
309
  }
307
310
 
311
+ /**
312
+ * Routes an error thrown by a block into the nearest boundary with a catch
313
+ * branch (see `try.js`). Installed by the first `try_block`, so a bundle whose
314
+ * mount and components create no boundary carries no catch machinery.
315
+ * @type {((error: unknown, block: Block) => BlockWithTryBoundaryAndCatch | null) | null}
316
+ */
317
+ let catch_router = null;
318
+
319
+ /**
320
+ * @param {(error: unknown, block: Block) => BlockWithTryBoundaryAndCatch | null} fn
321
+ */
322
+ export function set_catch_router(fn) {
323
+ catch_router = fn;
324
+ }
325
+
308
326
  /**
309
327
  * @param {unknown} error
310
328
  * @param {Block} block
311
329
  * @returns {BlockWithTryBoundaryAndCatch}
312
330
  */
313
331
  export function handle_error(error, block) {
314
- var boundary_with_catch = get_boundary_with_catch(block);
315
- if (boundary_with_catch !== null) {
316
- handle_boundary_error(boundary_with_catch, error);
317
- return boundary_with_catch;
332
+ var boundary_with_catch = catch_router === null ? null : catch_router(error, block);
333
+ if (boundary_with_catch === null) {
334
+ throw error;
318
335
  }
319
-
320
- throw error;
336
+ return boundary_with_catch;
321
337
  }
322
338
 
323
339
  /**
324
340
  * The error path of {@link run_block}, kept out of that hot function so a
325
341
  * healthy mount never compiles it: routes real errors to the nearest catch
326
- * boundary and pauses a block that read a pending async value under its
327
- * pending boundary.
342
+ * boundary, and a read of a pending async value to the handler `trackAsync`
343
+ * installs (the read is registered as a dependency, so the block re-runs once
344
+ * the value settles).
328
345
  * @param {unknown} error
329
346
  * @param {Block} block
330
347
  */
331
348
  function handle_run_error(error, block) {
332
- var is_component_direct = false;
333
- var is_try_fn_block = false;
334
349
  finish_dependencies(block, active_dependency);
335
- // When a derived read throws ASYNC_DERIVED_READ_THROWN, it means the
336
- // derived is still SUSPENSE_PENDING. The dependency was already registered,
337
- // so we swallow the throw and let the parent continue processing. When
338
- // the derived settles, the block will be dirty and rerun automatically.
339
350
  if (error !== ASYNC_DERIVED_READ_THROWN) {
340
351
  handle_error(error, block);
341
- } else if (
342
- // pending async tracked was read outside allowed blocks
343
- (is_component_direct = active_component?.b === block) ||
344
- (is_try_fn_block =
345
- block.p !== null && (block.p.f & TRY_BLOCK) !== 0 && (block.f & DIRECT_CHILD_BLOCK) !== 0)
346
- ) {
347
- throw new Error(
348
- `Reads on pending tracked values directly inside ${is_component_direct ? 'component' : 'try/pending/catch'} body are prohibited. Use trackPending() test or peek() for safe access or create another derived instead.`,
349
- );
352
+ } else if (pending_read !== null) {
353
+ pending_read(block);
350
354
  } else {
351
- // pending async tracked was read and threw ASYNC_DERIVED_READ_THROWN
352
- var boundary = get_pending_boundary(block);
353
- if (boundary !== null) {
354
- pause_block(block);
355
- register_boundary_paused_block(boundary, block);
356
-
357
- // Register deferred boundary completions for async tracked deps.
358
- // This handles the case where a child boundary reads a tracked value
359
- // whose resolution is managed by a different (parent) boundary.
360
- var dep = block.d;
361
- while (dep !== null) {
362
- var dep_tracked = /** @type {Tracked} */ (dep.t);
363
- if (
364
- (dep_tracked.__v === SUSPENSE_PENDING || dep_tracked.__v === SUSPENSE_REJECTED) &&
365
- (dep_tracked.f & TRACKED) !== 0
366
- ) {
367
- var deferred_req = begin_boundary_request(boundary);
368
- var entry = /** @type {DeferredTrackedEntry} */ ({ b: boundary, r: deferred_req });
369
- if (dep_tracked.d === null) {
370
- dep_tracked.d = [entry];
371
- } else {
372
- dep_tracked.d.push(entry);
373
- }
374
- }
375
- dep = dep.n;
376
- }
377
- }
355
+ throw error;
378
356
  }
379
357
  }
380
358
 
@@ -389,7 +367,9 @@ function handle_run_error(error, block) {
389
367
  * @returns {Derived[] | null}
390
368
  */
391
369
  function prepare_rerun(block) {
392
- if ((block.f & (FOR_BLOCK | IF_BLOCK)) === 0) {
370
+ // A list, an if, and a list item re-run only their own logic; their child
371
+ // blocks (items, the branch's blocks, an item body's nested blocks) stay.
372
+ if ((block.f & (FOR_BLOCK | IF_BLOCK | ITEM_BLOCK)) === 0) {
393
373
  destroy_non_branch_children(block);
394
374
  }
395
375
  run_teardown(block);
@@ -411,6 +391,46 @@ function register_teardown(block, teardown) {
411
391
  }
412
392
  }
413
393
 
394
+ /**
395
+ * Runs the body of a branch block that was just created, in place of a first
396
+ * `run_block`: a branch renders untracked and never re-runs, so the only
397
+ * bookkeeping its first run needs is the globals the body reads and the
398
+ * dependencies a nested `item` records on it. Errors are handled as
399
+ * `run_block` handles them.
400
+ * @param {Block} block
401
+ * @param {(anchor: any, value: any, index?: any, key?: any) => void} fn
402
+ * @param {any} anchor
403
+ * @param {any} value
404
+ * @param {any} [key] a keyed list item's key (see `create_item`)
405
+ */
406
+ export function run_branch(block, fn, anchor, value, key) {
407
+ var previous_block = active_block;
408
+ var previous_reaction = active_reaction;
409
+ var previous_tracking = tracking;
410
+ var previous_dependency = active_dependency;
411
+ var previous_component = active_component;
412
+
413
+ try {
414
+ active_block = block;
415
+ active_reaction = block;
416
+ active_component = block.co;
417
+ tracking = false;
418
+ active_dependency = null;
419
+ fn(anchor, value, undefined, key);
420
+ if (active_dependency !== null) {
421
+ block.d = active_dependency;
422
+ }
423
+ } catch (error) {
424
+ handle_run_error(error, block);
425
+ } finally {
426
+ active_block = previous_block;
427
+ active_reaction = previous_reaction;
428
+ tracking = previous_tracking;
429
+ active_dependency = previous_dependency;
430
+ active_component = previous_component;
431
+ }
432
+ }
433
+
414
434
  /**
415
435
  * @param {Block} block
416
436
  * @param {boolean} [first_run] true when the block has no children, teardown,
@@ -472,21 +492,6 @@ export function run_block(block, first_run = false) {
472
492
 
473
493
  var empty_get_set = { get: undefined, set: undefined };
474
494
 
475
- /**
476
- * Complete all deferred boundary requests registered on a tracked value.
477
- * @param {Tracked} t
478
- * @param {boolean} [show_resolved=true]
479
- */
480
- function complete_deferred_boundaries(t, show_resolved = true) {
481
- if (t.d !== null) {
482
- for (var i = 0; i < t.d.length; i++) {
483
- var entry = t.d[i];
484
- complete_boundary_request(entry.b, entry.r, show_resolved);
485
- }
486
- t.d = null;
487
- }
488
- }
489
-
490
495
  class TrackedValue {
491
496
  /**
492
497
  * @param {any} v
@@ -522,17 +527,6 @@ class TrackedValue {
522
527
  set value(v) {
523
528
  set(this, v);
524
529
  }
525
- /**
526
- * A read-only view: a derived over this value, for a receiver that should
527
- * read but not write it. Equivalent to `track(() => tracked.value)`. The
528
- * view is owned by the block that creates it, not by this value's block, so
529
- * a view made in a component is released with that component even when the
530
- * value outlives it.
531
- * @returns {Derived}
532
- */
533
- readOnly() {
534
- return derived(() => get_tracked(this), /** @type {Block} */ (active_block));
535
- }
536
530
  }
537
531
 
538
532
  class DerivedValue {
@@ -576,17 +570,6 @@ class DerivedValue {
576
570
  set value(v) {
577
571
  set(this, v);
578
572
  }
579
- /**
580
- * A read-only view (see `TrackedValue#readOnly`). A derived without a
581
- * setter is already read-only and is returned as is; a writable one is
582
- * wrapped in a derived that follows it.
583
- * @returns {Derived}
584
- */
585
- readOnly() {
586
- return this.a.set === undefined
587
- ? this
588
- : derived(() => get_derived(this), /** @type {Block} */ (active_block));
589
- }
590
573
  }
591
574
 
592
575
  if (DEV) {
@@ -607,7 +590,7 @@ export function tracked(v, block, hash, get, set) {
607
590
  var t = /** @type {Tracked} */ (
608
591
  new TrackedValue(v, block || active_block, get || set ? { get, set } : empty_get_set, hash)
609
592
  );
610
- if (hydrating && hash !== undefined) {
593
+ if (HYDRATION && hydrating && hash !== undefined) {
611
594
  track_hash_reference.set(hash, t);
612
595
  }
613
596
  return t;
@@ -638,7 +621,7 @@ export function derived(fn, block, hash, get, set) {
638
621
  created.push(d);
639
622
  }
640
623
  }
641
- if (hydrating && hash !== undefined) {
624
+ if (HYDRATION && hydrating && hash !== undefined) {
642
625
  track_hash_reference.set(hash, d);
643
626
  }
644
627
  return d;
@@ -732,6 +715,30 @@ function keep_deriveds(block, kept) {
732
715
  block.f |= CREATES_DERIVEDS;
733
716
  }
734
717
 
718
+ /**
719
+ * A read-only view of a tracked or derived value, for a receiver that should
720
+ * read but not write it: a derived over a tracked (or a writable derived),
721
+ * equivalent to `track(() => value.value)`; a read-only derived as it is; a
722
+ * plain value as it is, like `get`. The view is owned by the block that
723
+ * creates it, not by the value's block, so a view made in a component is
724
+ * released with that component even when the value outlives it.
725
+ * @param {any} value
726
+ * @param {Block} [block]
727
+ * @returns {any}
728
+ */
729
+ export function track_read_only(value, block) {
730
+ if (!is_ripple_object(value)) {
731
+ return value;
732
+ }
733
+ var owner = block || /** @type {Block} */ (active_block);
734
+ if ((value.f & DERIVED) !== 0) {
735
+ var d = /** @type {Derived} */ (value);
736
+ return d.a.set === undefined ? d : derived(() => get_derived(d), owner);
737
+ }
738
+ var t = /** @type {Tracked} */ (value);
739
+ return derived(() => get_tracked(t), owner);
740
+ }
741
+
735
742
  /**
736
743
  * @param {any} v
737
744
  * @param {Block} b
@@ -745,7 +752,7 @@ export function track(v, b, hash, get, set) {
745
752
  return v;
746
753
  }
747
754
  if (b === null) {
748
- throw new TypeError('track() requires a valid component context');
755
+ track_orphan();
749
756
  }
750
757
 
751
758
  if (typeof v === 'function') {
@@ -754,243 +761,6 @@ export function track(v, b, hash, get, set) {
754
761
  return tracked(v, b, hash, get, set);
755
762
  }
756
763
 
757
- /**
758
- * @param {any} fn
759
- * @param {Block} b
760
- * @param {string} hash - Unique hash for SSR serialization/hydration
761
- * @returns {Tracked | void}
762
- */
763
- export function track_async(fn, b, hash) {
764
- if (is_ripple_object(fn)) {
765
- return fn;
766
- }
767
-
768
- var target_block = b || active_block;
769
- if (target_block === null) {
770
- throw new TypeError('trackAsync() requires a valid component context');
771
- }
772
-
773
- if (typeof fn !== 'function') {
774
- throw new TypeError(
775
- 'trackAsync() only accepts function arguments that return a promise or an object with a promise property',
776
- );
777
- }
778
-
779
- // During hydration, attempt to read serialized data from SSR
780
- var had_hydration_data = false;
781
- var hydration_value;
782
- /** @type {string[] | undefined} */
783
- var hydration_deps;
784
-
785
- if (hydrating) {
786
- var script_id = get_track_async_script_id(hash);
787
- var script_el = document.getElementById(script_id);
788
- if (script_el) {
789
- var envelope = JSON.parse(/** @type {string} */ (script_el.textContent));
790
- script_el.remove();
791
-
792
- if (envelope.ok) {
793
- had_hydration_data = true;
794
- hydration_value =
795
- envelope.payload === undefined ? envelope.value : revive(envelope.payload);
796
- hydration_deps = envelope.deps;
797
- } else {
798
- // trigger the catch block
799
- throw new Error(envelope.error?.message ?? 'Unknown server error');
800
- }
801
- }
802
- }
803
-
804
- var t = tracked(had_hydration_data ? hydration_value : SUSPENSE_PENDING, target_block, hash);
805
-
806
- // Capture the call-site block for boundary lookups. target_block is the
807
- // component's block (passed by compiler), but the actual try/pending/catch
808
- // boundary is an ancestor of active_block (the block executing trackAsync).
809
- var call_site_block = /** @type {Block} */ (active_block);
810
-
811
- var version = 0;
812
- /** @type {AbortController | null} */
813
- var abort_controller = null;
814
- var request_id = 0;
815
- /** @type {Block | null} */
816
- var boundary = null;
817
-
818
- // TODO: decide if instead of insisting on pending, we create our own boundary
819
- // we currently require a pending block upstream but we could also
820
- // create a try/pending/catch boundary at mount and hydration like
821
- // we do on the server so that there is always a boundary present.
822
- // It can handle global pending when none were provided.
823
- // Not sure about the catch boundary because if none were provided,
824
- // the whole app for any error will be unmounted with the catch block rendered
825
-
826
- // Find boundary from the call-site block.
827
- boundary = get_pending_boundary(active_block);
828
- if (boundary === null) {
829
- throw new Error('Missing parent `try { ... } pending { ... }` statement');
830
- }
831
-
832
- // If we hydrated with resolved data, the SSR already completed this request.
833
- // Otherwise mark a pending request on the boundary for the client-side run.
834
- if (!had_hydration_data) {
835
- request_id = begin_boundary_request(boundary);
836
- }
837
-
838
- pre_effect(() => {
839
- if (had_hydration_data) {
840
- // First run after hydration: skip fn() entirely (the SSR already
841
- // produced the resolved value) and instead register the direct
842
- // dependencies from the serialized deps list so future dep changes
843
- // trigger a re-run via the normal async path.
844
- had_hydration_data = false;
845
- if (hydration_deps !== undefined) {
846
- for (var i = 0; i < hydration_deps.length; i++) {
847
- var dep_ref = track_hash_reference.get(hydration_deps[i]);
848
- if (dep_ref !== undefined) {
849
- get(dep_ref);
850
- }
851
- }
852
- }
853
- return;
854
- }
855
-
856
- var current_version = ++version;
857
-
858
- // Abort previous in-flight request
859
- if (abort_controller !== null && abort_controller.signal.aborted === false) {
860
- abort_controller.abort(TRACKED_UPDATED);
861
- }
862
- abort_controller = null;
863
-
864
- // Manage boundary request: replace if in-flight, or begin new if previous completed
865
- if (request_id > 0 && boundary !== null) {
866
- request_id = replace_boundary_request(boundary, request_id);
867
- } else if (boundary !== null) {
868
- request_id = begin_boundary_request(boundary);
869
- }
870
-
871
- // Set to pending before calling fn() in case it's sync.
872
- if (t.__v !== SUSPENSE_PENDING) {
873
- update_tracked_value_clock(t, SUSPENSE_PENDING);
874
- }
875
-
876
- // Temporarily allow mutations so set() doesn't throw inside the pre-effect
877
- var previous_is_mutating_allowed = is_mutating_allowed;
878
- is_mutating_allowed = true;
879
-
880
- var result;
881
- try {
882
- result = fn();
883
- } catch (e) {
884
- is_mutating_allowed = previous_is_mutating_allowed;
885
- if (e === ASYNC_DERIVED_READ_THROWN) {
886
- // A dependency is still pending or rejected (e.g. chained trackAsync).
887
- // Check if any dependency is rejected — if so, propagate rejection.
888
- var dep = active_dependency;
889
- while (dep !== null) {
890
- if (dep.t.__v === SUSPENSE_REJECTED) {
891
- update_tracked_value_clock(t, SUSPENSE_REJECTED);
892
- complete_deferred_boundaries(t, false);
893
- if (request_id > 0 && boundary !== null) {
894
- complete_boundary_request(boundary, request_id, false);
895
- request_id = 0;
896
- }
897
- return;
898
- }
899
- dep = dep.n;
900
- }
901
- // Dependencies are pending, not rejected — register deferred
902
- // rejection so that if the boundary goes to catch mode, this
903
- // tracked value is also set to REJECTED.
904
- if (request_id > 0 && boundary !== null) {
905
- register_boundary_deferred(boundary, request_id, () => {
906
- update_tracked_value_clock(t, SUSPENSE_REJECTED);
907
- });
908
- }
909
- return;
910
- }
911
- throw e;
912
- }
913
- is_mutating_allowed = previous_is_mutating_allowed;
914
-
915
- // Check if the result is async
916
- var previous_tracking = tracking;
917
- tracking = false;
918
- var async_result = get_async_track_result(result);
919
- tracking = previous_tracking;
920
-
921
- if (async_result === null) {
922
- // Sync result
923
- update_tracked_value_clock(t, result);
924
- if (request_id > 0 && boundary !== null) {
925
- complete_boundary_request(boundary, request_id);
926
- request_id = 0;
927
- }
928
- return;
929
- }
930
-
931
- // Capture per-invocation so async closures (rejection handler, teardown)
932
- // have a stable reference. The shared abort_controller is only read
933
- // synchronously at the top of the pre_effect to abort the previous request.
934
- var current_abort_controller = async_result.abort_controller;
935
- abort_controller = current_abort_controller;
936
-
937
- async_result.promise.then(
938
- (resolved) => {
939
- if (current_version !== version) {
940
- // stale
941
- return;
942
- }
943
- update_tracked_value_clock(t, resolved);
944
- complete_deferred_boundaries(t);
945
- if (request_id > 0 && boundary !== null) {
946
- complete_boundary_request(boundary, request_id);
947
- request_id = 0;
948
- }
949
- },
950
- (error) => {
951
- if (current_version !== version) return; // stale
952
-
953
- var is_internal_abort =
954
- error === TRACKED_UPDATED || current_abort_controller?.signal?.reason === TRACKED_UPDATED;
955
- if (is_internal_abort) {
956
- // Internal abort (superseded by a new request) — don't set rejected
957
- if (request_id > 0 && boundary !== null) {
958
- complete_boundary_request(boundary, request_id, false);
959
- request_id = 0;
960
- }
961
- complete_deferred_boundaries(t, false);
962
- return;
963
- }
964
-
965
- update_tracked_value_clock(t, SUSPENSE_REJECTED);
966
- complete_deferred_boundaries(t, false);
967
-
968
- // Route error to catch boundary
969
- var boundary_with_catch = get_boundary_with_catch(call_site_block);
970
- if (boundary_with_catch !== null) {
971
- handle_boundary_error(boundary_with_catch, error);
972
- }
973
-
974
- if (request_id > 0 && boundary !== null) {
975
- var should_show_resolved =
976
- boundary_with_catch === boundary || boundary === null ? false : true;
977
- complete_boundary_request(boundary, request_id, should_show_resolved);
978
- request_id = 0;
979
- }
980
- },
981
- );
982
-
983
- return () => {
984
- // Teardown: abort in-flight request when block is destroyed
985
- if (current_abort_controller !== null && current_abort_controller.signal.aborted === false) {
986
- current_abort_controller.abort(TRACKED_UPDATED);
987
- }
988
- };
989
- });
990
-
991
- return t;
992
- }
993
-
994
764
  /**
995
765
  * @param {(Derived | Tracked) | (() => any)} t
996
766
  * @returns {boolean}
@@ -1258,7 +1028,8 @@ function is_tracking_dirty(tracking) {
1258
1028
  export function is_block_dirty(block) {
1259
1029
  var flags = block.f;
1260
1030
 
1261
- if ((flags & (ROOT_BLOCK | BRANCH_BLOCK)) !== 0) {
1031
+ // A branch never re-runs, except a list item that carries its render block.
1032
+ if ((flags & ITEM_BLOCK) === 0 && (flags & (ROOT_BLOCK | BRANCH_BLOCK)) !== 0) {
1262
1033
  return false;
1263
1034
  }
1264
1035
  if ((flags & BLOCK_HAS_RUN) === 0) {
@@ -1420,9 +1191,7 @@ function flush_microtasks() {
1420
1191
 
1421
1192
  flush_count++;
1422
1193
  if (flush_count > 1001) {
1423
- throw new Error(
1424
- 'Maximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state.',
1425
- );
1194
+ update_depth_exceeded();
1426
1195
  }
1427
1196
  var pending = queue;
1428
1197
  queue = create_queue();
@@ -1584,9 +1353,7 @@ export function get_tracked(tracked) {
1584
1353
  */
1585
1354
  export function set(tracked, value) {
1586
1355
  if (!is_mutating_allowed) {
1587
- throw new Error(
1588
- 'Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation',
1589
- );
1356
+ set_in_derived();
1590
1357
  }
1591
1358
 
1592
1359
  var old_value = tracked.__v;
@@ -1851,9 +1618,9 @@ export function scope() {
1851
1618
  * @param {string} [err]
1852
1619
  * @returns {Block | never}
1853
1620
  */
1854
- export function safe_scope(err = 'Cannot access outside of a component context') {
1621
+ export function safe_scope(err) {
1855
1622
  if (active_scope === null) {
1856
- throw new Error(err);
1623
+ scope_orphan(err);
1857
1624
  }
1858
1625
 
1859
1626
  return /** @type {Block} */ (active_scope);
@@ -0,0 +1,47 @@
1
+ import { get_first_child } from './operations.js';
2
+ import { set_ns_parser, template } from './template.js';
3
+
4
+ /**
5
+ * Create fragment with proper namespace using Svelte's wrapping approach
6
+ * @param {string} content
7
+ * @param {'svg' | 'math'} ns
8
+ * @returns {DocumentFragment}
9
+ */
10
+ function from_namespace(content, ns) {
11
+ var wrapped = `<${ns}>${content}</${ns}>`;
12
+
13
+ var elem = document.createElement('template');
14
+ elem.innerHTML = wrapped;
15
+ var fragment = elem.content;
16
+
17
+ var root = /** @type {Element} */ (get_first_child(fragment));
18
+ var result = document.createDocumentFragment();
19
+
20
+ var first;
21
+ while ((first = get_first_child(root))) {
22
+ result.appendChild(/** @type {Node} */ (first));
23
+ }
24
+
25
+ return result;
26
+ }
27
+
28
+ /**
29
+ * Makes namespaced template parsing available: called by every path that can
30
+ * clone a template into the SVG or MathML namespace, so an app without one
31
+ * never loads it.
32
+ */
33
+ export function install_ns_templates() {
34
+ set_ns_parser(from_namespace);
35
+ }
36
+
37
+ /**
38
+ * `template()` for content compiled in the SVG or MathML namespace.
39
+ * @param {string} content
40
+ * @param {number} [flags]
41
+ * @param {number} [count]
42
+ * @returns {() => Node}
43
+ */
44
+ export function template_ns(content, flags, count) {
45
+ set_ns_parser(from_namespace);
46
+ return template(content, flags, count);
47
+ }