ripple 0.4.2 → 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 (94) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/package.json +9 -3
  3. package/src/jsx-runtime.d.ts +2 -1
  4. package/src/runtime/array.js +15 -9
  5. package/src/runtime/index-client.js +25 -7
  6. package/src/runtime/index-server.js +1 -0
  7. package/src/runtime/internal/client/attributes.js +313 -0
  8. package/src/runtime/internal/client/blocks.js +4 -14
  9. package/src/runtime/internal/client/component.js +2 -5
  10. package/src/runtime/internal/client/composite.js +14 -6
  11. package/src/runtime/internal/client/constants.js +6 -6
  12. package/src/runtime/internal/client/context.js +3 -2
  13. package/src/runtime/internal/client/css.js +11 -26
  14. package/src/runtime/internal/client/errors.js +188 -0
  15. package/src/runtime/internal/client/events.js +79 -34
  16. package/src/runtime/internal/client/expression.js +21 -116
  17. package/src/runtime/internal/client/for.js +108 -232
  18. package/src/runtime/internal/client/head.js +4 -3
  19. package/src/runtime/internal/client/html.js +5 -3
  20. package/src/runtime/internal/client/hydrate.js +484 -0
  21. package/src/runtime/internal/client/hydration-enabled.js +7 -0
  22. package/src/runtime/internal/client/hydration.js +16 -81
  23. package/src/runtime/internal/client/if.js +13 -9
  24. package/src/runtime/internal/client/index.js +14 -6
  25. package/src/runtime/internal/client/operations.js +11 -7
  26. package/src/runtime/internal/client/portal.js +3 -2
  27. package/src/runtime/internal/client/render.js +42 -325
  28. package/src/runtime/internal/client/root-boundary-enabled.js +7 -0
  29. package/src/runtime/internal/client/runtime.js +87 -363
  30. package/src/runtime/internal/client/template-ns.js +47 -0
  31. package/src/runtime/internal/client/template.js +63 -140
  32. package/src/runtime/internal/client/track-async.js +356 -0
  33. package/src/runtime/internal/client/try.js +133 -319
  34. package/src/runtime/internal/client/types.d.ts +48 -24
  35. package/src/runtime/internal/server/index.js +31 -29
  36. package/src/runtime/internal/server/prerender.js +27 -0
  37. package/src/server/index.js +1 -0
  38. package/src/utils/class-name.js +28 -0
  39. package/tests/client/basic/basic.errors.test.tsrx +3 -3
  40. package/tests/client/compiler/__snapshots__/compiler.typescript.test.tsrx.snap +0 -2
  41. package/tests/client/compiler/compiler.basic.test.tsrx +2 -2
  42. package/tests/client/compiler/compiler.tracked-access.test.tsrx +3 -3
  43. package/tests/client/composite/composite.props.test.tsrx +10 -10
  44. package/tests/client/derived-release.test.tsrx +3 -3
  45. package/tests/client/for-item.test.tsrx +39 -0
  46. package/tests/client/for-selector.test.tsrx +95 -0
  47. package/tests/hydration/compiled/client/basic.js +87 -87
  48. package/tests/hydration/compiled/client/composite.js +5 -5
  49. package/tests/hydration/compiled/client/events.js +40 -48
  50. package/tests/hydration/compiled/client/for.js +162 -181
  51. package/tests/hydration/compiled/client/fragment-cursor.js +521 -289
  52. package/tests/hydration/compiled/client/head.js +22 -22
  53. package/tests/hydration/compiled/client/hmr.js +4 -4
  54. package/tests/hydration/compiled/client/html-in-template.js +5 -5
  55. package/tests/hydration/compiled/client/html.js +152 -162
  56. package/tests/hydration/compiled/client/if-children.js +58 -53
  57. package/tests/hydration/compiled/client/if-fragment-controlflow.js +39 -39
  58. package/tests/hydration/compiled/client/if.js +26 -26
  59. package/tests/hydration/compiled/client/mixed-control-flow.js +56 -68
  60. package/tests/hydration/compiled/client/nested-control-flow.js +139 -271
  61. package/tests/hydration/compiled/client/portal.js +10 -10
  62. package/tests/hydration/compiled/client/reactivity.js +29 -29
  63. package/tests/hydration/compiled/client/return.js +2 -2
  64. package/tests/hydration/compiled/client/streaming.js +33 -35
  65. package/tests/hydration/compiled/client/switch.js +25 -25
  66. package/tests/hydration/compiled/client/track-async-serialization.js +41 -41
  67. package/tests/hydration/compiled/client/try.js +15 -15
  68. package/tests/hydration/compiled/client/typed-text.js +7 -7
  69. package/tests/hydration/compiled/server/basic.js +6 -6
  70. package/tests/hydration/compiled/server/events.js +8 -8
  71. package/tests/hydration/compiled/server/for.js +22 -22
  72. package/tests/hydration/compiled/server/fragment-cursor.js +188 -52
  73. package/tests/hydration/compiled/server/head.js +6 -6
  74. package/tests/hydration/compiled/server/hmr.js +1 -1
  75. package/tests/hydration/compiled/server/html.js +1 -1
  76. package/tests/hydration/compiled/server/if-children.js +9 -9
  77. package/tests/hydration/compiled/server/if.js +5 -5
  78. package/tests/hydration/compiled/server/mixed-control-flow.js +4 -4
  79. package/tests/hydration/compiled/server/portal.js +1 -1
  80. package/tests/hydration/compiled/server/reactivity.js +9 -9
  81. package/tests/hydration/compiled/server/streaming.js +9 -9
  82. package/tests/hydration/compiled/server/switch.js +4 -4
  83. package/tests/hydration/compiled/server/track-async-serialization.js +16 -16
  84. package/tests/hydration/compiled/server/try.js +4 -4
  85. package/tests/hydration/compiled/server/typed-text.js +1 -1
  86. package/tests/hydration/components/fragment-cursor.tsrx +60 -0
  87. package/tests/hydration/components/track-async-serialization.tsrx +2 -2
  88. package/tests/hydration/fragment-cursor.test.js +34 -1
  89. package/tests/hydration/track-async-serialization.test.js +1 -1
  90. package/tests/server/basic.components.test.tsrx +3 -3
  91. package/tests/server/track-async-serialization.test.tsrx +6 -6
  92. package/tests/utils/tracked-types.test.js +3 -3
  93. package/types/index.d.ts +8 -8
  94. 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,11 +22,8 @@ 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,
@@ -41,17 +32,8 @@ import {
41
32
  RENDER_ENTRY,
42
33
  CREATES_DERIVEDS,
43
34
  } from './constants.js';
44
- import {
45
- begin_boundary_request,
46
- complete_boundary_request,
47
- get_boundary_with_catch,
48
- get_pending_boundary,
49
- handle_boundary_error,
50
- register_boundary_deferred,
51
- register_boundary_paused_block,
52
- replace_boundary_request,
53
- } from './try.js';
54
35
  import { is_ripple_object } from './utils.js';
36
+ import { scope_orphan, set_in_derived, track_orphan, update_depth_exceeded } from './errors.js';
55
37
  import { render_value } from './expression.js';
56
38
  import { throw_invalid_component_type } from './component.js';
57
39
 
@@ -61,10 +43,8 @@ import {
61
43
  is_array,
62
44
  object_keys,
63
45
  } from '@tsrx/core/runtime/language-helpers';
64
- import { get_async_track_result } from '../../../utils/async.js';
65
- import { get_track_async_script_id } from '../../../utils/track-async-serialization.js';
66
- import { revive } from './transport.js';
67
46
  import { hydrating, track_hash_reference } from './hydration.js';
47
+ import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
68
48
  import { create_ref_prop as create_core_ref_prop } from '@tsrx/core/runtime/ref';
69
49
 
70
50
  const FLUSH_MICROTASK = 0;
@@ -130,7 +110,7 @@ let flush_count = 0;
130
110
  /** @type {(() => void)[]} */
131
111
  var queued_post_block_flush = [];
132
112
  /** @type {null | Dependency} */
133
- let active_dependency = null;
113
+ export let active_dependency = null;
134
114
 
135
115
  export let tracking = false;
136
116
  export let teardown = false;
@@ -170,6 +150,28 @@ export function set_tracking(value) {
170
150
  tracking = value;
171
151
  }
172
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
+
173
175
  /**
174
176
  * @param {Block} block
175
177
  */
@@ -236,7 +238,7 @@ function update_derived(computed) {
236
238
  * @param {Tracked} tracked
237
239
  * @param {any} value
238
240
  */
239
- function update_tracked_value_clock(tracked, value) {
241
+ export function update_tracked_value_clock(tracked, value) {
240
242
  tracked.__v = value;
241
243
  tracked.c = increment_clock();
242
244
  mark_subscribers(tracked);
@@ -306,76 +308,51 @@ function run_derived(computed) {
306
308
  }
307
309
  }
308
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
+
309
326
  /**
310
327
  * @param {unknown} error
311
328
  * @param {Block} block
312
329
  * @returns {BlockWithTryBoundaryAndCatch}
313
330
  */
314
331
  export function handle_error(error, block) {
315
- var boundary_with_catch = get_boundary_with_catch(block);
316
- if (boundary_with_catch !== null) {
317
- handle_boundary_error(boundary_with_catch, error);
318
- 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;
319
335
  }
320
-
321
- throw error;
336
+ return boundary_with_catch;
322
337
  }
323
338
 
324
339
  /**
325
340
  * The error path of {@link run_block}, kept out of that hot function so a
326
341
  * healthy mount never compiles it: routes real errors to the nearest catch
327
- * boundary and pauses a block that read a pending async value under its
328
- * 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).
329
345
  * @param {unknown} error
330
346
  * @param {Block} block
331
347
  */
332
348
  function handle_run_error(error, block) {
333
- var is_component_direct = false;
334
- var is_try_fn_block = false;
335
349
  finish_dependencies(block, active_dependency);
336
- // When a derived read throws ASYNC_DERIVED_READ_THROWN, it means the
337
- // derived is still SUSPENSE_PENDING. The dependency was already registered,
338
- // so we swallow the throw and let the parent continue processing. When
339
- // the derived settles, the block will be dirty and rerun automatically.
340
350
  if (error !== ASYNC_DERIVED_READ_THROWN) {
341
351
  handle_error(error, block);
342
- } else if (
343
- // pending async tracked was read outside allowed blocks
344
- (is_component_direct = active_component?.b === block) ||
345
- (is_try_fn_block =
346
- block.p !== null && (block.p.f & TRY_BLOCK) !== 0 && (block.f & DIRECT_CHILD_BLOCK) !== 0)
347
- ) {
348
- throw new Error(
349
- `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.`,
350
- );
352
+ } else if (pending_read !== null) {
353
+ pending_read(block);
351
354
  } else {
352
- // pending async tracked was read and threw ASYNC_DERIVED_READ_THROWN
353
- var boundary = get_pending_boundary(block);
354
- if (boundary !== null) {
355
- pause_block(block);
356
- register_boundary_paused_block(boundary, block);
357
-
358
- // Register deferred boundary completions for async tracked deps.
359
- // This handles the case where a child boundary reads a tracked value
360
- // whose resolution is managed by a different (parent) boundary.
361
- var dep = block.d;
362
- while (dep !== null) {
363
- var dep_tracked = /** @type {Tracked} */ (dep.t);
364
- if (
365
- (dep_tracked.__v === SUSPENSE_PENDING || dep_tracked.__v === SUSPENSE_REJECTED) &&
366
- (dep_tracked.f & TRACKED) !== 0
367
- ) {
368
- var deferred_req = begin_boundary_request(boundary);
369
- var entry = /** @type {DeferredTrackedEntry} */ ({ b: boundary, r: deferred_req });
370
- if (dep_tracked.d === null) {
371
- dep_tracked.d = [entry];
372
- } else {
373
- dep_tracked.d.push(entry);
374
- }
375
- }
376
- dep = dep.n;
377
- }
378
- }
355
+ throw error;
379
356
  }
380
357
  }
381
358
 
@@ -421,11 +398,12 @@ function register_teardown(block, teardown) {
421
398
  * dependencies a nested `item` records on it. Errors are handled as
422
399
  * `run_block` handles them.
423
400
  * @param {Block} block
424
- * @param {(anchor: any, value: any) => void} fn
401
+ * @param {(anchor: any, value: any, index?: any, key?: any) => void} fn
425
402
  * @param {any} anchor
426
403
  * @param {any} value
404
+ * @param {any} [key] a keyed list item's key (see `create_item`)
427
405
  */
428
- export function run_branch(block, fn, anchor, value) {
406
+ export function run_branch(block, fn, anchor, value, key) {
429
407
  var previous_block = active_block;
430
408
  var previous_reaction = active_reaction;
431
409
  var previous_tracking = tracking;
@@ -438,7 +416,7 @@ export function run_branch(block, fn, anchor, value) {
438
416
  active_component = block.co;
439
417
  tracking = false;
440
418
  active_dependency = null;
441
- fn(anchor, value);
419
+ fn(anchor, value, undefined, key);
442
420
  if (active_dependency !== null) {
443
421
  block.d = active_dependency;
444
422
  }
@@ -514,21 +492,6 @@ export function run_block(block, first_run = false) {
514
492
 
515
493
  var empty_get_set = { get: undefined, set: undefined };
516
494
 
517
- /**
518
- * Complete all deferred boundary requests registered on a tracked value.
519
- * @param {Tracked} t
520
- * @param {boolean} [show_resolved=true]
521
- */
522
- function complete_deferred_boundaries(t, show_resolved = true) {
523
- if (t.d !== null) {
524
- for (var i = 0; i < t.d.length; i++) {
525
- var entry = t.d[i];
526
- complete_boundary_request(entry.b, entry.r, show_resolved);
527
- }
528
- t.d = null;
529
- }
530
- }
531
-
532
495
  class TrackedValue {
533
496
  /**
534
497
  * @param {any} v
@@ -564,17 +527,6 @@ class TrackedValue {
564
527
  set value(v) {
565
528
  set(this, v);
566
529
  }
567
- /**
568
- * A read-only view: a derived over this value, for a receiver that should
569
- * read but not write it. Equivalent to `track(() => tracked.value)`. The
570
- * view is owned by the block that creates it, not by this value's block, so
571
- * a view made in a component is released with that component even when the
572
- * value outlives it.
573
- * @returns {Derived}
574
- */
575
- readOnly() {
576
- return derived(() => get_tracked(this), /** @type {Block} */ (active_block));
577
- }
578
530
  }
579
531
 
580
532
  class DerivedValue {
@@ -618,17 +570,6 @@ class DerivedValue {
618
570
  set value(v) {
619
571
  set(this, v);
620
572
  }
621
- /**
622
- * A read-only view (see `TrackedValue#readOnly`). A derived without a
623
- * setter is already read-only and is returned as is; a writable one is
624
- * wrapped in a derived that follows it.
625
- * @returns {Derived}
626
- */
627
- readOnly() {
628
- return this.a.set === undefined
629
- ? this
630
- : derived(() => get_derived(this), /** @type {Block} */ (active_block));
631
- }
632
573
  }
633
574
 
634
575
  if (DEV) {
@@ -649,7 +590,7 @@ export function tracked(v, block, hash, get, set) {
649
590
  var t = /** @type {Tracked} */ (
650
591
  new TrackedValue(v, block || active_block, get || set ? { get, set } : empty_get_set, hash)
651
592
  );
652
- if (hydrating && hash !== undefined) {
593
+ if (HYDRATION && hydrating && hash !== undefined) {
653
594
  track_hash_reference.set(hash, t);
654
595
  }
655
596
  return t;
@@ -680,7 +621,7 @@ export function derived(fn, block, hash, get, set) {
680
621
  created.push(d);
681
622
  }
682
623
  }
683
- if (hydrating && hash !== undefined) {
624
+ if (HYDRATION && hydrating && hash !== undefined) {
684
625
  track_hash_reference.set(hash, d);
685
626
  }
686
627
  return d;
@@ -774,6 +715,30 @@ function keep_deriveds(block, kept) {
774
715
  block.f |= CREATES_DERIVEDS;
775
716
  }
776
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
+
777
742
  /**
778
743
  * @param {any} v
779
744
  * @param {Block} b
@@ -787,7 +752,7 @@ export function track(v, b, hash, get, set) {
787
752
  return v;
788
753
  }
789
754
  if (b === null) {
790
- throw new TypeError('track() requires a valid component context');
755
+ track_orphan();
791
756
  }
792
757
 
793
758
  if (typeof v === 'function') {
@@ -796,243 +761,6 @@ export function track(v, b, hash, get, set) {
796
761
  return tracked(v, b, hash, get, set);
797
762
  }
798
763
 
799
- /**
800
- * @param {any} fn
801
- * @param {Block} b
802
- * @param {string} hash - Unique hash for SSR serialization/hydration
803
- * @returns {Tracked | void}
804
- */
805
- export function track_async(fn, b, hash) {
806
- if (is_ripple_object(fn)) {
807
- return fn;
808
- }
809
-
810
- var target_block = b || active_block;
811
- if (target_block === null) {
812
- throw new TypeError('trackAsync() requires a valid component context');
813
- }
814
-
815
- if (typeof fn !== 'function') {
816
- throw new TypeError(
817
- 'trackAsync() only accepts function arguments that return a promise or an object with a promise property',
818
- );
819
- }
820
-
821
- // During hydration, attempt to read serialized data from SSR
822
- var had_hydration_data = false;
823
- var hydration_value;
824
- /** @type {string[] | undefined} */
825
- var hydration_deps;
826
-
827
- if (hydrating) {
828
- var script_id = get_track_async_script_id(hash);
829
- var script_el = document.getElementById(script_id);
830
- if (script_el) {
831
- var envelope = JSON.parse(/** @type {string} */ (script_el.textContent));
832
- script_el.remove();
833
-
834
- if (envelope.ok) {
835
- had_hydration_data = true;
836
- hydration_value =
837
- envelope.payload === undefined ? envelope.value : revive(envelope.payload);
838
- hydration_deps = envelope.deps;
839
- } else {
840
- // trigger the catch block
841
- throw new Error(envelope.error?.message ?? 'Unknown server error');
842
- }
843
- }
844
- }
845
-
846
- var t = tracked(had_hydration_data ? hydration_value : SUSPENSE_PENDING, target_block, hash);
847
-
848
- // Capture the call-site block for boundary lookups. target_block is the
849
- // component's block (passed by compiler), but the actual try/pending/catch
850
- // boundary is an ancestor of active_block (the block executing trackAsync).
851
- var call_site_block = /** @type {Block} */ (active_block);
852
-
853
- var version = 0;
854
- /** @type {AbortController | null} */
855
- var abort_controller = null;
856
- var request_id = 0;
857
- /** @type {Block | null} */
858
- var boundary = null;
859
-
860
- // TODO: decide if instead of insisting on pending, we create our own boundary
861
- // we currently require a pending block upstream but we could also
862
- // create a try/pending/catch boundary at mount and hydration like
863
- // we do on the server so that there is always a boundary present.
864
- // It can handle global pending when none were provided.
865
- // Not sure about the catch boundary because if none were provided,
866
- // the whole app for any error will be unmounted with the catch block rendered
867
-
868
- // Find boundary from the call-site block.
869
- boundary = get_pending_boundary(active_block);
870
- if (boundary === null) {
871
- throw new Error('Missing parent `try { ... } pending { ... }` statement');
872
- }
873
-
874
- // If we hydrated with resolved data, the SSR already completed this request.
875
- // Otherwise mark a pending request on the boundary for the client-side run.
876
- if (!had_hydration_data) {
877
- request_id = begin_boundary_request(boundary);
878
- }
879
-
880
- pre_effect(() => {
881
- if (had_hydration_data) {
882
- // First run after hydration: skip fn() entirely (the SSR already
883
- // produced the resolved value) and instead register the direct
884
- // dependencies from the serialized deps list so future dep changes
885
- // trigger a re-run via the normal async path.
886
- had_hydration_data = false;
887
- if (hydration_deps !== undefined) {
888
- for (var i = 0; i < hydration_deps.length; i++) {
889
- var dep_ref = track_hash_reference.get(hydration_deps[i]);
890
- if (dep_ref !== undefined) {
891
- get(dep_ref);
892
- }
893
- }
894
- }
895
- return;
896
- }
897
-
898
- var current_version = ++version;
899
-
900
- // Abort previous in-flight request
901
- if (abort_controller !== null && abort_controller.signal.aborted === false) {
902
- abort_controller.abort(TRACKED_UPDATED);
903
- }
904
- abort_controller = null;
905
-
906
- // Manage boundary request: replace if in-flight, or begin new if previous completed
907
- if (request_id > 0 && boundary !== null) {
908
- request_id = replace_boundary_request(boundary, request_id);
909
- } else if (boundary !== null) {
910
- request_id = begin_boundary_request(boundary);
911
- }
912
-
913
- // Set to pending before calling fn() in case it's sync.
914
- if (t.__v !== SUSPENSE_PENDING) {
915
- update_tracked_value_clock(t, SUSPENSE_PENDING);
916
- }
917
-
918
- // Temporarily allow mutations so set() doesn't throw inside the pre-effect
919
- var previous_is_mutating_allowed = is_mutating_allowed;
920
- is_mutating_allowed = true;
921
-
922
- var result;
923
- try {
924
- result = fn();
925
- } catch (e) {
926
- is_mutating_allowed = previous_is_mutating_allowed;
927
- if (e === ASYNC_DERIVED_READ_THROWN) {
928
- // A dependency is still pending or rejected (e.g. chained trackAsync).
929
- // Check if any dependency is rejected — if so, propagate rejection.
930
- var dep = active_dependency;
931
- while (dep !== null) {
932
- if (dep.t.__v === SUSPENSE_REJECTED) {
933
- update_tracked_value_clock(t, SUSPENSE_REJECTED);
934
- complete_deferred_boundaries(t, false);
935
- if (request_id > 0 && boundary !== null) {
936
- complete_boundary_request(boundary, request_id, false);
937
- request_id = 0;
938
- }
939
- return;
940
- }
941
- dep = dep.n;
942
- }
943
- // Dependencies are pending, not rejected — register deferred
944
- // rejection so that if the boundary goes to catch mode, this
945
- // tracked value is also set to REJECTED.
946
- if (request_id > 0 && boundary !== null) {
947
- register_boundary_deferred(boundary, request_id, () => {
948
- update_tracked_value_clock(t, SUSPENSE_REJECTED);
949
- });
950
- }
951
- return;
952
- }
953
- throw e;
954
- }
955
- is_mutating_allowed = previous_is_mutating_allowed;
956
-
957
- // Check if the result is async
958
- var previous_tracking = tracking;
959
- tracking = false;
960
- var async_result = get_async_track_result(result);
961
- tracking = previous_tracking;
962
-
963
- if (async_result === null) {
964
- // Sync result
965
- update_tracked_value_clock(t, result);
966
- if (request_id > 0 && boundary !== null) {
967
- complete_boundary_request(boundary, request_id);
968
- request_id = 0;
969
- }
970
- return;
971
- }
972
-
973
- // Capture per-invocation so async closures (rejection handler, teardown)
974
- // have a stable reference. The shared abort_controller is only read
975
- // synchronously at the top of the pre_effect to abort the previous request.
976
- var current_abort_controller = async_result.abort_controller;
977
- abort_controller = current_abort_controller;
978
-
979
- async_result.promise.then(
980
- (resolved) => {
981
- if (current_version !== version) {
982
- // stale
983
- return;
984
- }
985
- update_tracked_value_clock(t, resolved);
986
- complete_deferred_boundaries(t);
987
- if (request_id > 0 && boundary !== null) {
988
- complete_boundary_request(boundary, request_id);
989
- request_id = 0;
990
- }
991
- },
992
- (error) => {
993
- if (current_version !== version) return; // stale
994
-
995
- var is_internal_abort =
996
- error === TRACKED_UPDATED || current_abort_controller?.signal?.reason === TRACKED_UPDATED;
997
- if (is_internal_abort) {
998
- // Internal abort (superseded by a new request) — don't set rejected
999
- if (request_id > 0 && boundary !== null) {
1000
- complete_boundary_request(boundary, request_id, false);
1001
- request_id = 0;
1002
- }
1003
- complete_deferred_boundaries(t, false);
1004
- return;
1005
- }
1006
-
1007
- update_tracked_value_clock(t, SUSPENSE_REJECTED);
1008
- complete_deferred_boundaries(t, false);
1009
-
1010
- // Route error to catch boundary
1011
- var boundary_with_catch = get_boundary_with_catch(call_site_block);
1012
- if (boundary_with_catch !== null) {
1013
- handle_boundary_error(boundary_with_catch, error);
1014
- }
1015
-
1016
- if (request_id > 0 && boundary !== null) {
1017
- var should_show_resolved =
1018
- boundary_with_catch === boundary || boundary === null ? false : true;
1019
- complete_boundary_request(boundary, request_id, should_show_resolved);
1020
- request_id = 0;
1021
- }
1022
- },
1023
- );
1024
-
1025
- return () => {
1026
- // Teardown: abort in-flight request when block is destroyed
1027
- if (current_abort_controller !== null && current_abort_controller.signal.aborted === false) {
1028
- current_abort_controller.abort(TRACKED_UPDATED);
1029
- }
1030
- };
1031
- });
1032
-
1033
- return t;
1034
- }
1035
-
1036
764
  /**
1037
765
  * @param {(Derived | Tracked) | (() => any)} t
1038
766
  * @returns {boolean}
@@ -1463,9 +1191,7 @@ function flush_microtasks() {
1463
1191
 
1464
1192
  flush_count++;
1465
1193
  if (flush_count > 1001) {
1466
- throw new Error(
1467
- 'Maximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state.',
1468
- );
1194
+ update_depth_exceeded();
1469
1195
  }
1470
1196
  var pending = queue;
1471
1197
  queue = create_queue();
@@ -1627,9 +1353,7 @@ export function get_tracked(tracked) {
1627
1353
  */
1628
1354
  export function set(tracked, value) {
1629
1355
  if (!is_mutating_allowed) {
1630
- throw new Error(
1631
- 'Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation',
1632
- );
1356
+ set_in_derived();
1633
1357
  }
1634
1358
 
1635
1359
  var old_value = tracked.__v;
@@ -1894,9 +1618,9 @@ export function scope() {
1894
1618
  * @param {string} [err]
1895
1619
  * @returns {Block | never}
1896
1620
  */
1897
- export function safe_scope(err = 'Cannot access outside of a component context') {
1621
+ export function safe_scope(err) {
1898
1622
  if (active_scope === null) {
1899
- throw new Error(err);
1623
+ scope_orphan(err);
1900
1624
  }
1901
1625
 
1902
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
+ }