ripple 0.3.127 → 0.4.0

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 (192) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/package.json +10 -4
  3. package/src/runtime/element.js +23 -6
  4. package/src/runtime/index-client.js +81 -41
  5. package/src/runtime/index-server.js +1 -0
  6. package/src/runtime/internal/client/blocks.js +192 -114
  7. package/src/runtime/internal/client/component.js +4 -30
  8. package/src/runtime/internal/client/composite.js +12 -13
  9. package/src/runtime/internal/client/constants.js +14 -5
  10. package/src/runtime/internal/client/context.js +12 -19
  11. package/src/runtime/internal/client/css.js +7 -9
  12. package/src/runtime/internal/client/events.js +178 -90
  13. package/src/runtime/internal/client/expression.js +185 -124
  14. package/src/runtime/internal/client/for.js +616 -264
  15. package/src/runtime/internal/client/head.js +10 -5
  16. package/src/runtime/internal/client/html.js +1 -0
  17. package/src/runtime/internal/client/hydration.js +152 -10
  18. package/src/runtime/internal/client/if.js +239 -50
  19. package/src/runtime/internal/client/index.js +9 -13
  20. package/src/runtime/internal/client/operations.js +44 -51
  21. package/src/runtime/internal/client/portal.js +163 -85
  22. package/src/runtime/internal/client/render.js +44 -25
  23. package/src/runtime/internal/client/rpc.js +3 -3
  24. package/src/runtime/internal/client/runtime.js +799 -576
  25. package/src/runtime/internal/client/selector.js +77 -0
  26. package/src/runtime/internal/client/template.js +133 -158
  27. package/src/runtime/internal/client/transport.js +38 -0
  28. package/src/runtime/internal/client/try.js +465 -426
  29. package/src/runtime/internal/client/types.d.ts +65 -21
  30. package/src/runtime/internal/client/utils.js +1 -3
  31. package/src/runtime/internal/server/dynamic.js +20 -21
  32. package/src/runtime/internal/server/index.js +327 -275
  33. package/src/runtime/internal/server/rpc.js +3 -2
  34. package/src/runtime/internal/server/transport.js +16 -0
  35. package/src/runtime/transport.js +21 -0
  36. package/tests/client/__snapshots__/for.test.tsrx.snap +0 -5
  37. package/tests/client/array/array.copy-within.test.tsrx +12 -12
  38. package/tests/client/array/array.derived.test.tsrx +46 -46
  39. package/tests/client/array/array.iteration.test.tsrx +10 -10
  40. package/tests/client/array/array.mutations.test.tsrx +20 -20
  41. package/tests/client/array/array.static.test.tsrx +11 -11
  42. package/tests/client/array/array.to-methods.test.tsrx +6 -6
  43. package/tests/client/async-suspend.test.tsrx +74 -75
  44. package/tests/client/basic/basic.attributes.test.tsrx +86 -83
  45. package/tests/client/basic/basic.collections.test.tsrx +12 -12
  46. package/tests/client/basic/basic.components.test.tsrx +99 -25
  47. package/tests/client/basic/basic.errors.test.tsrx +18 -18
  48. package/tests/client/basic/basic.events.test.tsrx +42 -42
  49. package/tests/client/basic/basic.get-set.test.tsrx +8 -8
  50. package/tests/client/basic/basic.reactivity.test.tsrx +97 -29
  51. package/tests/client/basic/basic.rendering.test.tsrx +25 -25
  52. package/tests/client/basic/basic.utilities.test.tsrx +3 -3
  53. package/tests/client/boundaries.test.tsrx +8 -8
  54. package/tests/client/boxed-locals.test.tsrx +452 -0
  55. package/tests/client/compiler/__snapshots__/compiler.assignments.test.tsrx.snap +6 -6
  56. package/tests/client/compiler/compiler.assignments.test.tsrx +9 -73
  57. package/tests/client/compiler/compiler.attributes.test.tsrx +23 -23
  58. package/tests/client/compiler/compiler.basic.test.tsrx +15 -60
  59. package/tests/client/compiler/compiler.tracked-access.test.tsrx +18 -43
  60. package/tests/client/compiler/compiler.try-in-function.test.tsrx +2 -2
  61. package/tests/client/composite/__snapshots__/composite.render.test.tsrx.snap +0 -1
  62. package/tests/client/composite/composite.dynamic-components.test.tsrx +77 -6
  63. package/tests/client/composite/composite.props.test.tsrx +149 -12
  64. package/tests/client/composite/composite.reactivity.test.tsrx +51 -52
  65. package/tests/client/composite/composite.render.test.tsrx +26 -9
  66. package/tests/client/css/scoped-styles.test.tsrx +8 -8
  67. package/tests/client/date.test.tsrx +42 -42
  68. package/tests/client/derived-release.test.tsrx +370 -0
  69. package/tests/client/dynamic-elements.test.tsrx +45 -45
  70. package/tests/client/events.test.tsrx +97 -71
  71. package/tests/client/for-selector.test.tsrx +221 -0
  72. package/tests/client/for-single-root-items.test.tsrx +118 -3
  73. package/tests/client/for.test.tsrx +332 -31
  74. package/tests/client/head.test.tsrx +19 -19
  75. package/tests/client/html.test.tsrx +11 -9
  76. package/tests/client/if-block.test.tsrx +80 -0
  77. package/tests/client/map.test.tsrx +16 -16
  78. package/tests/client/media-query.test.tsrx +7 -7
  79. package/tests/client/portal.test.tsrx +224 -26
  80. package/tests/client/primitive-text.test.tsrx +50 -0
  81. package/tests/client/prop-kind-switch.test.tsrx +141 -0
  82. package/tests/client/ref.test.tsrx +17 -17
  83. package/tests/client/return.test.tsrx +26 -3
  84. package/tests/client/root-boundary.test.tsrx +42 -0
  85. package/tests/client/scoped-flush.test.tsrx +8 -8
  86. package/tests/client/set.test.tsrx +6 -6
  87. package/tests/client/svg.test.tsrx +11 -11
  88. package/tests/client/switch.test.tsrx +44 -44
  89. package/tests/client/track-async-hydration.test.tsrx +4 -4
  90. package/tests/client/try.test.tsrx +151 -83
  91. package/tests/client/tsx.test.tsrx +43 -40
  92. package/tests/client/url/url.derived.test.tsrx +6 -6
  93. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +8 -8
  94. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +10 -10
  95. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +10 -10
  96. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +18 -18
  97. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +2 -2
  98. package/tests/hydration/basic.test.js +74 -1
  99. package/tests/hydration/build-components.js +22 -1
  100. package/tests/hydration/compiled/client/basic.js +1142 -713
  101. package/tests/hydration/compiled/client/composite.js +151 -100
  102. package/tests/hydration/compiled/client/events.js +190 -150
  103. package/tests/hydration/compiled/client/for.js +1472 -1126
  104. package/tests/hydration/compiled/client/fragment-cursor.js +2341 -0
  105. package/tests/hydration/compiled/client/head.js +255 -199
  106. package/tests/hydration/compiled/client/hmr.js +54 -41
  107. package/tests/hydration/compiled/client/html-in-template.js +54 -39
  108. package/tests/hydration/compiled/client/html.js +1633 -1263
  109. package/tests/hydration/compiled/client/if-children.js +420 -270
  110. package/tests/hydration/compiled/client/if-fragment-controlflow.js +363 -317
  111. package/tests/hydration/compiled/client/if.js +229 -219
  112. package/tests/hydration/compiled/client/mixed-control-flow.js +420 -400
  113. package/tests/hydration/compiled/client/nested-control-flow.js +1326 -1255
  114. package/tests/hydration/compiled/client/portal.js +87 -95
  115. package/tests/hydration/compiled/client/reactivity.js +140 -83
  116. package/tests/hydration/compiled/client/return.js +28 -19
  117. package/tests/hydration/compiled/client/streaming.js +390 -306
  118. package/tests/hydration/compiled/client/switch.js +258 -266
  119. package/tests/hydration/compiled/client/track-async-serialization.js +448 -280
  120. package/tests/hydration/compiled/client/try.js +173 -129
  121. package/tests/hydration/compiled/client/typed-text.js +71 -0
  122. package/tests/hydration/compiled/fixtures/money.js +10 -0
  123. package/tests/hydration/compiled/server/basic.js +123 -6
  124. package/tests/hydration/compiled/server/composite.js +16 -30
  125. package/tests/hydration/compiled/server/events.js +19 -19
  126. package/tests/hydration/compiled/server/for.js +109 -49
  127. package/tests/hydration/compiled/server/fragment-cursor.js +1687 -0
  128. package/tests/hydration/compiled/server/head.js +20 -28
  129. package/tests/hydration/compiled/server/hmr.js +2 -2
  130. package/tests/hydration/compiled/server/html.js +71 -247
  131. package/tests/hydration/compiled/server/if-children.js +107 -15
  132. package/tests/hydration/compiled/server/if.js +11 -15
  133. package/tests/hydration/compiled/server/mixed-control-flow.js +8 -8
  134. package/tests/hydration/compiled/server/portal.js +14 -22
  135. package/tests/hydration/compiled/server/reactivity.js +14 -46
  136. package/tests/hydration/compiled/server/streaming.js +19 -19
  137. package/tests/hydration/compiled/server/switch.js +8 -8
  138. package/tests/hydration/compiled/server/track-async-serialization.js +150 -23
  139. package/tests/hydration/compiled/server/try.js +8 -8
  140. package/tests/hydration/compiled/server/typed-text.js +17 -0
  141. package/tests/hydration/components/basic.tsrx +44 -8
  142. package/tests/hydration/components/composite.tsrx +2 -2
  143. package/tests/hydration/components/events.tsrx +25 -25
  144. package/tests/hydration/components/for.tsrx +115 -67
  145. package/tests/hydration/components/fragment-cursor.tsrx +650 -0
  146. package/tests/hydration/components/head.tsrx +16 -16
  147. package/tests/hydration/components/hmr.tsrx +2 -2
  148. package/tests/hydration/components/html.tsrx +7 -7
  149. package/tests/hydration/components/if-children.tsrx +72 -25
  150. package/tests/hydration/components/if.tsrx +18 -18
  151. package/tests/hydration/components/mixed-control-flow.tsrx +11 -11
  152. package/tests/hydration/components/portal.tsrx +3 -3
  153. package/tests/hydration/components/reactivity.tsrx +16 -16
  154. package/tests/hydration/components/streaming.tsrx +20 -20
  155. package/tests/hydration/components/switch.tsrx +20 -20
  156. package/tests/hydration/components/text-types.ts +4 -0
  157. package/tests/hydration/components/track-async-serialization.tsrx +80 -26
  158. package/tests/hydration/components/try.tsrx +8 -8
  159. package/tests/hydration/components/typed-text.tsrx +22 -0
  160. package/tests/hydration/fixtures/money.js +10 -0
  161. package/tests/hydration/for.test.js +36 -0
  162. package/tests/hydration/fragment-cursor.test.js +111 -0
  163. package/tests/hydration/if-children.test.js +24 -0
  164. package/tests/hydration/streaming.test.js +19 -0
  165. package/tests/hydration/track-async-serialization.test.js +87 -2
  166. package/tests/hydration/tsconfig.text-types.json +10 -0
  167. package/tests/hydration/typed-text.test.js +34 -0
  168. package/tests/server/await.test.tsrx +3 -3
  169. package/tests/server/basic.attributes.test.tsrx +42 -42
  170. package/tests/server/basic.components.test.tsrx +64 -8
  171. package/tests/server/basic.test.tsrx +34 -32
  172. package/tests/server/compiler.test.tsrx +1 -70
  173. package/tests/server/composite.props.test.tsrx +6 -6
  174. package/tests/server/dynamic-elements.test.tsrx +31 -8
  175. package/tests/server/for.test.tsrx +94 -0
  176. package/tests/server/head.test.tsrx +11 -11
  177. package/tests/server/scoped-styles.test.tsrx +2 -2
  178. package/tests/server/streaming-ssr.test.tsrx +48 -43
  179. package/tests/server/track-async-serialization.test.tsrx +204 -21
  180. package/tests/server/try.test.tsrx +48 -48
  181. package/tests/types/transport.ts +40 -0
  182. package/tests/utils/vite-plugin-config.test.js +23 -0
  183. package/types/index.d.ts +48 -7
  184. package/types/server.d.ts +3 -0
  185. package/types/transport.d.ts +15 -0
  186. package/src/runtime/internal/client/switch.js +0 -113
  187. package/src/utils/errors.js +0 -13
  188. package/tests/client/lazy-array.test.tsrx +0 -31
  189. package/tests/client/lazy-destructuring.test.tsrx +0 -438
  190. package/tests/client/tracked-index-access.test.tsrx +0 -113
  191. package/tests/server/lazy-destructuring.test.tsrx +0 -595
  192. package/tests/server/tracked-index-access.test.tsrx +0 -76
@@ -1,4 +1,4 @@
1
- /** @import { Block, Component, Dependency, BlockWithTryBoundaryAndCatch, DeferredTrackedEntry } from '#client' */
1
+ /** @import { AppendIntoAnchor, Block, Component, Dependency, BlockWithTryBoundaryAndCatch, DeferredTrackedEntry } from '#client' */
2
2
  /** @import { NAMESPACE_URI } from './constants.js' */
3
3
  /** @typedef {TrackedValue} Tracked */
4
4
  /** @typedef {DerivedValue} Derived */
@@ -18,50 +18,51 @@ import {
18
18
  DERIVED,
19
19
  COMPUTED_PROPERTY,
20
20
  CONTAINS_TEARDOWN,
21
- CONTAINS_UPDATE,
22
21
  DESTROYED,
23
22
  EFFECT_BLOCK,
23
+ FOR_BLOCK,
24
24
  PAUSED,
25
25
  PRE_EFFECT_BLOCK,
26
26
  ROOT_BLOCK,
27
27
  TRACKED,
28
28
  UNINITIALIZED,
29
29
  REF_PROP,
30
- TRACKED_OBJECT,
31
30
  DEFAULT_NAMESPACE,
32
31
  TRACKED_UPDATED,
33
32
  SUSPENSE_PENDING,
34
33
  SUSPENSE_REJECTED,
35
34
  TRY_BLOCK,
36
35
  DIRECT_CHILD_BLOCK,
37
- UPDATE_SOURCE,
36
+ SCHEDULED,
37
+ SELECTOR,
38
+ IF_BLOCK,
39
+ RELEASED,
40
+ RENDER_ENTRY,
41
+ CREATES_DERIVEDS,
38
42
  } from './constants.js';
39
43
  import {
40
44
  begin_boundary_request,
41
45
  complete_boundary_request,
42
46
  get_boundary_with_catch,
43
47
  get_pending_boundary,
48
+ handle_boundary_error,
44
49
  register_boundary_deferred,
45
50
  register_boundary_paused_block,
46
51
  replace_boundary_request,
47
52
  } from './try.js';
48
53
  import { is_ripple_object } from './utils.js';
54
+ import { render_value } from './expression.js';
55
+ import { throw_invalid_component_type } from './component.js';
49
56
 
50
57
  import {
51
- iterable_array_from,
52
58
  define_property,
53
59
  get_descriptor,
54
- get_own_property_symbols,
55
60
  is_array,
56
61
  object_keys,
57
62
  } from '@tsrx/core/runtime/language-helpers';
58
63
  import { get_async_track_result } from '../../../utils/async.js';
59
- import {
60
- throw_tracked_index_reference_error,
61
- throw_tracked_index_value_error,
62
- } from '../../../utils/errors.js';
63
64
  import { get_track_async_script_id } from '../../../utils/track-async-serialization.js';
64
- import * as devalue from 'devalue';
65
+ import { revive } from './transport.js';
65
66
  import { hydrating, track_hash_reference } from './hydration.js';
66
67
  import { create_ref_prop as create_core_ref_prop } from '@tsrx/core/runtime/ref';
67
68
 
@@ -81,8 +82,25 @@ export let active_namespace = DEFAULT_NAMESPACE;
81
82
  /** @type {boolean} */
82
83
  export let is_mutating_allowed = true;
83
84
 
84
- /** @type {Map<Tracked | Derived, any>} */
85
- var old_values = new Map();
85
+ /**
86
+ * Tracked values written during the current flush that hold their previous
87
+ * value in `o`, for teardowns that read them; released when the flush ends.
88
+ * @type {(Tracked | Derived)[]}
89
+ */
90
+ var old_value_holders = [];
91
+ /** Nesting depth of running flushes; old values are released at depth zero. */
92
+ var flush_depth = 0;
93
+
94
+ /** @returns {void} */
95
+ function release_old_values() {
96
+ var holders = old_value_holders;
97
+ var length = holders.length;
98
+ if (length === 0) return;
99
+ for (var i = 0; i < length; i++) {
100
+ holders[i].o = UNINITIALIZED;
101
+ }
102
+ holders.length = 0;
103
+ }
86
104
 
87
105
  // Used for controlling the flush of blocks
88
106
  /** @type {number} */
@@ -92,14 +110,18 @@ let scheduler_mode = FLUSH_MICROTASK;
92
110
  let is_micro_task_queued = false;
93
111
  /** @type {number} */
94
112
  let clock = 0;
95
- /** @type {Block[]} */
96
- let queued_root_blocks = [];
97
- // Flush normally only scans the subtree of each directly-scheduled block, since a
98
- // tracked's subscribers live inside the subtree of the block that owns it. If a
99
- // tracked is ever read from outside its owner's subtree (e.g. smuggled across
100
- // sibling subtrees via a module-level variable), that invariant breaks, so we
101
- // permanently fall back to scanning the whole root tree to stay correct.
102
- let disable_scoped_flush = false;
113
+ /**
114
+ * Blocks scheduled for the next flush. `sorted` tracks whether they were
115
+ * pushed in creation order, so the common case avoids a sort.
116
+ * @typedef {{ blocks: Block[]; sorted: boolean; last: number }} Queue
117
+ */
118
+ /** @type {Queue} */
119
+ let queue = create_queue();
120
+
121
+ /** @returns {Queue} */
122
+ function create_queue() {
123
+ return { blocks: [], sorted: true, last: 0 };
124
+ }
103
125
  /** @type {(() => void)[]} */
104
126
  let queued_microtasks = [];
105
127
  /** @type {number} */
@@ -163,7 +185,10 @@ export function run_teardown(block) {
163
185
  active_reaction = null;
164
186
  tracking = false;
165
187
  teardown = true;
166
- fn.call(null);
188
+ // Runtime-internal teardowns keyed on block state get it as their
189
+ // argument, so they need no per-block closure; user effects have
190
+ // null state.
191
+ fn.call(null, block.s);
167
192
  } finally {
168
193
  active_block = previous_block;
169
194
  active_reaction = previous_reaction;
@@ -213,6 +238,7 @@ function update_derived(computed) {
213
238
  function update_tracked_value_clock(tracked, value) {
214
239
  tracked.__v = value;
215
240
  tracked.c = increment_clock();
241
+ mark_subscribers(tracked);
216
242
  }
217
243
 
218
244
  /**
@@ -252,11 +278,11 @@ function run_derived(computed) {
252
278
 
253
279
  var value = computed.fn();
254
280
 
255
- computed.d = active_dependency;
281
+ finish_dependencies(computed, active_dependency);
256
282
 
257
283
  return value;
258
284
  } catch (error) {
259
- computed.d = active_dependency;
285
+ finish_dependencies(computed, active_dependency);
260
286
  if (error === ASYNC_DERIVED_READ_THROWN) {
261
287
  // Check if any dependency is rejected — if so, propagate rejection
262
288
  var dep = active_dependency;
@@ -287,7 +313,7 @@ function run_derived(computed) {
287
313
  export function handle_error(error, block) {
288
314
  var boundary_with_catch = get_boundary_with_catch(block);
289
315
  if (boundary_with_catch !== null) {
290
- boundary_with_catch.s.c(error);
316
+ handle_boundary_error(boundary_with_catch, error);
291
317
  return boundary_with_catch;
292
318
  }
293
319
 
@@ -295,93 +321,152 @@ export function handle_error(error, block) {
295
321
  }
296
322
 
297
323
  /**
324
+ * The error path of {@link run_block}, kept out of that hot function so a
325
+ * 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.
328
+ * @param {unknown} error
298
329
  * @param {Block} block
299
330
  */
300
- export function run_block(block) {
331
+ function handle_run_error(error, block) {
332
+ var is_component_direct = false;
333
+ var is_try_fn_block = false;
334
+ 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
+ if (error !== ASYNC_DERIVED_READ_THROWN) {
340
+ 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
+ );
350
+ } 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
+ }
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Clears what a block's previous run left behind before it runs again. A
383
+ * list's children are all item branches, so there is nothing to sweep; an if
384
+ * block's children are its branch, which it replaces itself only when the
385
+ * condition changes. Returns the deriveds the previous run created, to be
386
+ * released once the new run has destroyed whatever read them (an if block
387
+ * swaps its branch inside its own run).
388
+ * @param {Block} block
389
+ * @returns {Derived[] | null}
390
+ */
391
+ function prepare_rerun(block) {
392
+ if ((block.f & (FOR_BLOCK | IF_BLOCK)) === 0) {
393
+ destroy_non_branch_children(block);
394
+ }
395
+ run_teardown(block);
396
+ return (block.f & CREATES_DERIVEDS) !== 0 ? take_created_deriveds(block) : null;
397
+ }
398
+
399
+ /**
400
+ * @param {Block} block
401
+ * @param {(state?: any) => void} teardown
402
+ */
403
+ function register_teardown(block, teardown) {
404
+ block.t = teardown;
405
+ /** @type {Block | null} */
406
+ var current = block;
407
+
408
+ while (current !== null && (current.f & CONTAINS_TEARDOWN) === 0) {
409
+ current.f ^= CONTAINS_TEARDOWN;
410
+ current = current.p;
411
+ }
412
+ }
413
+
414
+ /**
415
+ * @param {Block} block
416
+ * @param {boolean} [first_run] true when the block has no children, teardown,
417
+ * or dependencies yet, so that cleanup can be skipped
418
+ */
419
+ export function run_block(block, first_run = false) {
301
420
  var previous_block = active_block;
302
421
  var previous_reaction = active_reaction;
303
422
  var previous_tracking = tracking;
304
423
  var previous_dependency = active_dependency;
305
424
  var previous_component = active_component;
425
+ /** @type {Derived[] | null} */
426
+ var previous_deriveds = null;
306
427
 
307
428
  try {
308
429
  active_block = block;
309
430
  active_reaction = block;
310
431
  active_component = block.co;
311
432
 
312
- destroy_non_branch_children(block);
313
- run_teardown(block);
433
+ if (!first_run) {
434
+ previous_deriveds = prepare_rerun(block);
435
+ }
314
436
 
315
437
  tracking = (block.f & (ROOT_BLOCK | BRANCH_BLOCK)) === 0;
316
438
  active_dependency = null;
317
439
  var res = block.fn(block.s);
318
440
 
319
441
  if (typeof res === 'function') {
320
- block.t = res;
321
- /** @type {Block | null} */
322
- let current = block;
323
-
324
- while (current !== null && (current.f & CONTAINS_TEARDOWN) === 0) {
325
- current.f ^= CONTAINS_TEARDOWN;
326
- current = current.p;
327
- }
442
+ register_teardown(block, res);
328
443
  }
329
444
 
330
- block.d = active_dependency;
331
- } catch (error) {
332
- var is_component_direct = false;
333
- var is_try_fn_block = false;
334
- block.d = 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
- if (error !== ASYNC_DERIVED_READ_THROWN) {
340
- 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
- );
350
- } 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
- }
445
+ if (block.d === null) {
446
+ // First run (or no dependencies so far): nothing stale to unlink.
447
+ if (active_dependency !== null) {
448
+ block.d = active_dependency;
377
449
  }
450
+ } else {
451
+ finish_dependencies(block, active_dependency);
378
452
  }
453
+ } catch (error) {
454
+ handle_run_error(error, block);
379
455
  } finally {
380
456
  active_block = previous_block;
381
457
  active_reaction = previous_reaction;
382
458
  tracking = previous_tracking;
383
459
  active_dependency = previous_dependency;
384
460
  active_component = previous_component;
461
+
462
+ // The previous run's deriveds, whether this run finished or threw, and
463
+ // after this run's dependencies replaced the old ones (so a derived the
464
+ // old run read is not kept alive by that stale link). Those still read
465
+ // stay recorded under the block for its next rerun or destruction: a
466
+ // rerun that keeps the same branch must not lose them.
467
+ if (previous_deriveds !== null) {
468
+ keep_deriveds(block, release_deriveds(previous_deriveds));
469
+ }
385
470
  }
386
471
  }
387
472
 
@@ -406,11 +491,11 @@ class TrackedValue {
406
491
  /**
407
492
  * @param {any} v
408
493
  * @param {Block} block
409
- * @param {{ get?: Function; set?: Function }} a
494
+ * @param {{ get?: Function; set?: Function | true }} a
410
495
  * @param {string} [hash]
411
496
  */
412
497
  constructor(v, block, a, hash) {
413
- /** @type {{ get?: Function; set?: Function }} */
498
+ /** @type {{ get?: Function; set?: Function | true }} */
414
499
  this.a = a;
415
500
  /** @type {Block} */
416
501
  this.b = block;
@@ -420,22 +505,14 @@ class TrackedValue {
420
505
  this.d = null;
421
506
  /** @type {number} */
422
507
  this.f = TRACKED;
423
- /** @type {string | undefined} */
508
+ /** @type {any} hydration hash, or the key of a selector match flag */
424
509
  this.h = hash;
510
+ /** @type {Dependency | null} */
511
+ this.sb = null;
425
512
  /** @type {any} */
426
513
  this.__v = v;
427
- }
428
- /** @returns {any} */
429
- get [0]() {
430
- return throw_tracked_index_value_error();
431
- }
432
- /** @param {any} v */
433
- set [0](v) {
434
- throw_tracked_index_value_error();
435
- }
436
- /** @returns {Tracked} */
437
- get [1]() {
438
- return throw_tracked_index_reference_error();
514
+ /** @type {any} previous value while a flush with teardowns is running */
515
+ this.o = UNINITIALIZED;
439
516
  }
440
517
  /** @returns {any} */
441
518
  get value() {
@@ -445,14 +522,16 @@ class TrackedValue {
445
522
  set value(v) {
446
523
  set(this, v);
447
524
  }
448
- /** @returns {2} */
449
- get length() {
450
- return 2;
451
- }
452
- /** @returns {Iterator<any | Tracked>} */
453
- *[Symbol.iterator]() {
454
- yield get_tracked(this);
455
- yield this;
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));
456
535
  }
457
536
  }
458
537
 
@@ -460,11 +539,11 @@ class DerivedValue {
460
539
  /**
461
540
  * @param {Function} fn
462
541
  * @param {Block} block
463
- * @param {{ get?: Function; set?: Function }} a
542
+ * @param {{ get?: Function; set?: Function | true }} a
464
543
  * @param {string} [hash]
465
544
  */
466
545
  constructor(fn, block, a, hash) {
467
- /** @type {{ get?: Function; set?: Function }} */
546
+ /** @type {{ get?: Function; set?: Function | true }} */
468
547
  this.a = a;
469
548
  /** @type {Block} */
470
549
  this.b = block;
@@ -482,20 +561,12 @@ class DerivedValue {
482
561
  this.fn = fn;
483
562
  /** @type {string | undefined} */
484
563
  this.h = hash;
564
+ /** @type {Dependency | null} */
565
+ this.sb = null;
485
566
  /** @type {any} */
486
567
  this.__v = UNINITIALIZED;
487
- }
488
- /** @returns {any} */
489
- get [0]() {
490
- return throw_tracked_index_value_error();
491
- }
492
- /** @param {any} v */
493
- set [0](v) {
494
- throw_tracked_index_value_error();
495
- }
496
- /** @returns {Derived} */
497
- get [1]() {
498
- return throw_tracked_index_reference_error();
568
+ /** @type {any} previous value while a flush with teardowns is running */
569
+ this.o = UNINITIALIZED;
499
570
  }
500
571
  /** @returns {any} */
501
572
  get value() {
@@ -505,14 +576,16 @@ class DerivedValue {
505
576
  set value(v) {
506
577
  set(this, v);
507
578
  }
508
- /** @returns {2} */
509
- get length() {
510
- return 2;
511
- }
512
- /** @returns {Iterator<any | Derived>} */
513
- *[Symbol.iterator]() {
514
- yield get_derived(this);
515
- yield this;
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));
516
589
  }
517
590
  }
518
591
 
@@ -527,7 +600,7 @@ if (DEV) {
527
600
  * @param {Block} block
528
601
  * @param {string} [hash]
529
602
  * @param {(value: any) => any} [get]
530
- * @param {(next: any, prev: any) => any} [set]
603
+ * @param {((next: any, prev: any) => any) | true} [set]
531
604
  * @returns {Tracked}
532
605
  */
533
606
  export function tracked(v, block, hash, get, set) {
@@ -545,25 +618,126 @@ export function tracked(v, block, hash, get, set) {
545
618
  * @param {Block} block
546
619
  * @param {string} [hash]
547
620
  * @param {(value: any) => any} [get]
548
- * @param {(next: any, prev: any) => any} [set]
621
+ * @param {((next: any, prev: any) => any) | true} [set]
549
622
  * @returns {Derived}
550
623
  */
551
624
  export function derived(fn, block, hash, get, set) {
552
625
  var d = /** @type {Derived} */ (
553
626
  new DerivedValue(fn, block || active_block, get || set ? { get, set } : empty_get_set, hash)
554
627
  );
628
+ // The run that creates a derived owns its release: when that block reruns
629
+ // or is destroyed, the derived is unsubscribed from its sources unless a
630
+ // reader that is still alive holds it (see `release_deriveds`).
631
+ var creator = active_block;
632
+ if (creator !== null) {
633
+ var created = created_deriveds.get(creator);
634
+ if (created === undefined) {
635
+ created_deriveds.set(creator, [d]);
636
+ creator.f |= CREATES_DERIVEDS;
637
+ } else {
638
+ created.push(d);
639
+ }
640
+ }
555
641
  if (hydrating && hash !== undefined) {
556
642
  track_hash_reference.set(hash, d);
557
643
  }
558
644
  return d;
559
645
  }
560
646
 
647
+ /**
648
+ * The deriveds each block's latest run created, keyed by the block. A side
649
+ * table rather than a block field: most blocks never create a derived, and
650
+ * block creation is the hot path, so blocks keep their shape. A block with an
651
+ * entry carries `CREATES_DERIVEDS` in its flags, so a rerun or destroy of any
652
+ * other block never looks here.
653
+ * @type {Map<Block, Derived[]>}
654
+ */
655
+ var created_deriveds = new Map();
656
+
657
+ /**
658
+ * Removes and returns the deriveds `block`'s latest run created, or null.
659
+ * @param {Block} block
660
+ * @returns {Derived[] | null}
661
+ */
662
+ export function take_created_deriveds(block) {
663
+ var deriveds = created_deriveds.get(block);
664
+ block.f &= ~CREATES_DERIVEDS;
665
+ if (deriveds === undefined) {
666
+ return null;
667
+ }
668
+ created_deriveds.delete(block);
669
+ return deriveds;
670
+ }
671
+
672
+ /**
673
+ * Releases the deriveds a block run created, once that run's readers are
674
+ * gone: the block reran (its previous run's readers were destroyed by the
675
+ * rerun) or was destroyed. A derived that a live reader still holds (the
676
+ * branch is still showing, or it was handed to a longer-lived scope) is
677
+ * kept. Walked newest first, so a derived read by a later one of the same
678
+ * run sees that reader unlinked before it is examined; the survivors are
679
+ * packed into the tail of the same array as they are met, which leaves them
680
+ * in creation order for the next pass, and slid to the front at the end.
681
+ * @param {Derived[]} deriveds in creation order; reused for the result
682
+ * @returns {Derived[] | null} the deriveds kept, in creation order, if any
683
+ */
684
+ export function release_deriveds(deriveds) {
685
+ var length = deriveds.length;
686
+ var kept = length;
687
+ for (var i = length - 1; i >= 0; i--) {
688
+ var derived = deriveds[i];
689
+ var alive = false;
690
+ for (var sub = derived.sb; sub !== null; sub = sub.sn) {
691
+ if ((sub.r.f & DESTROYED) === 0) {
692
+ alive = true;
693
+ break;
694
+ }
695
+ }
696
+ if (alive) {
697
+ // `kept` never drops below `i`, so this only overwrites visited slots.
698
+ deriveds[--kept] = derived;
699
+ } else {
700
+ finish_dependencies(derived, null);
701
+ destroy_computed_children(derived);
702
+ }
703
+ }
704
+ if (kept === length) {
705
+ return null;
706
+ }
707
+ if (kept !== 0) {
708
+ deriveds.copyWithin(0, kept);
709
+ deriveds.length = length - kept;
710
+ }
711
+ return deriveds;
712
+ }
713
+
714
+ /**
715
+ * Records deriveds under `block` again after a rerun kept them, ahead of
716
+ * whatever the new run created: the kept ones are older, and the release
717
+ * order (newest first) relies on the list being in creation order.
718
+ * @param {Block} block
719
+ * @param {Derived[] | null} kept
720
+ */
721
+ function keep_deriveds(block, kept) {
722
+ if (kept === null) {
723
+ return;
724
+ }
725
+ var created = created_deriveds.get(block);
726
+ if (created !== undefined) {
727
+ for (var i = 0; i < created.length; i++) {
728
+ kept.push(created[i]);
729
+ }
730
+ }
731
+ created_deriveds.set(block, kept);
732
+ block.f |= CREATES_DERIVEDS;
733
+ }
734
+
561
735
  /**
562
736
  * @param {any} v
563
737
  * @param {Block} b
564
738
  * @param {string} [hash]
565
739
  * @param {(value: any) => any} [get]
566
- * @param {(next: any, prev: any) => any} [set]
740
+ * @param {((next: any, prev: any) => any) | true} [set]
567
741
  * @returns {Tracked | Derived}
568
742
  */
569
743
  export function track(v, b, hash, get, set) {
@@ -617,7 +791,8 @@ export function track_async(fn, b, hash) {
617
791
 
618
792
  if (envelope.ok) {
619
793
  had_hydration_data = true;
620
- hydration_value = devalue.parse(envelope.payload);
794
+ hydration_value =
795
+ envelope.payload === undefined ? envelope.value : revive(envelope.payload);
621
796
  hydration_deps = envelope.deps;
622
797
  } else {
623
798
  // trigger the catch block
@@ -696,7 +871,6 @@ export function track_async(fn, b, hash) {
696
871
  // Set to pending before calling fn() in case it's sync.
697
872
  if (t.__v !== SUSPENSE_PENDING) {
698
873
  update_tracked_value_clock(t, SUSPENSE_PENDING);
699
- schedule_update(t.b);
700
874
  }
701
875
 
702
876
  // Temporarily allow mutations so set() doesn't throw inside the pre-effect
@@ -715,7 +889,6 @@ export function track_async(fn, b, hash) {
715
889
  while (dep !== null) {
716
890
  if (dep.t.__v === SUSPENSE_REJECTED) {
717
891
  update_tracked_value_clock(t, SUSPENSE_REJECTED);
718
- schedule_update(t.b);
719
892
  complete_deferred_boundaries(t, false);
720
893
  if (request_id > 0 && boundary !== null) {
721
894
  complete_boundary_request(boundary, request_id, false);
@@ -748,7 +921,6 @@ export function track_async(fn, b, hash) {
748
921
  if (async_result === null) {
749
922
  // Sync result
750
923
  update_tracked_value_clock(t, result);
751
- schedule_update(t.b);
752
924
  if (request_id > 0 && boundary !== null) {
753
925
  complete_boundary_request(boundary, request_id);
754
926
  request_id = 0;
@@ -769,7 +941,6 @@ export function track_async(fn, b, hash) {
769
941
  return;
770
942
  }
771
943
  update_tracked_value_clock(t, resolved);
772
- schedule_update(t.b);
773
944
  complete_deferred_boundaries(t);
774
945
  if (request_id > 0 && boundary !== null) {
775
946
  complete_boundary_request(boundary, request_id);
@@ -792,13 +963,12 @@ export function track_async(fn, b, hash) {
792
963
  }
793
964
 
794
965
  update_tracked_value_clock(t, SUSPENSE_REJECTED);
795
- schedule_update(t.b);
796
966
  complete_deferred_boundaries(t, false);
797
967
 
798
968
  // Route error to catch boundary
799
969
  var boundary_with_catch = get_boundary_with_catch(call_site_block);
800
970
  if (boundary_with_catch !== null) {
801
- boundary_with_catch.s.c(error);
971
+ handle_boundary_error(boundary_with_catch, error);
802
972
  }
803
973
 
804
974
  if (request_id > 0 && boundary !== null) {
@@ -865,16 +1035,190 @@ function create_dependency(tracked) {
865
1035
  if (existing !== null) {
866
1036
  reaction.d = existing.n;
867
1037
  existing.c = tracked.c;
868
- existing.t = tracked;
869
1038
  existing.n = null;
1039
+ if (existing.t !== tracked) {
1040
+ unlink_subscriber(existing);
1041
+ existing.t = tracked;
1042
+ link_subscriber(existing, tracked);
1043
+ }
870
1044
  return existing;
871
1045
  }
872
1046
 
873
- return {
1047
+ // link_subscriber, inline: the first read of every block run lands here.
1048
+ var head = tracked.sb;
1049
+ /** @type {Dependency} */
1050
+ var dependency = {
874
1051
  c: tracked.c,
875
1052
  t: tracked,
876
1053
  n: null,
1054
+ r: reaction,
1055
+ sp: null,
1056
+ sn: head,
877
1057
  };
1058
+ if (head !== null) {
1059
+ head.sp = dependency;
1060
+ }
1061
+ tracked.sb = dependency;
1062
+ return dependency;
1063
+ }
1064
+
1065
+ /**
1066
+ * @param {Dependency} dependency
1067
+ * @param {Tracked | Derived} tracked
1068
+ */
1069
+ function link_subscriber(dependency, tracked) {
1070
+ var head = tracked.sb;
1071
+ dependency.sn = head;
1072
+ if (head !== null) {
1073
+ head.sp = dependency;
1074
+ }
1075
+ tracked.sb = dependency;
1076
+ }
1077
+
1078
+ /**
1079
+ * @param {Dependency} dependency
1080
+ */
1081
+ export function unlink_subscriber(dependency) {
1082
+ var prev = dependency.sp;
1083
+ var next = dependency.sn;
1084
+ if (prev !== null) {
1085
+ prev.sn = next;
1086
+ } else {
1087
+ var tracked = dependency.t;
1088
+ // Already unlinked (a destroyed block's dependency can be pruned by a
1089
+ // write during its own teardown, then unlinked again by
1090
+ // remove_dependencies); it must not be mistaken for the list head.
1091
+ if (tracked.sb !== dependency) {
1092
+ return;
1093
+ }
1094
+ tracked.sb = next;
1095
+ if (next === null && (tracked.f & SELECTOR) !== 0) {
1096
+ release_selector_key(/** @type {Tracked} */ (tracked));
1097
+ }
1098
+ }
1099
+ if (next !== null) {
1100
+ next.sp = prev;
1101
+ }
1102
+ dependency.sp = dependency.sn = null;
1103
+ }
1104
+
1105
+ /**
1106
+ * `n` counts released keys still in the map.
1107
+ * @typedef {{ get: undefined; set: undefined; m: Map<any, Tracked>; n: number }} SelectorAccessors
1108
+ */
1109
+
1110
+ /** Released selector keys kept for reuse before the map is swept. */
1111
+ var SELECTOR_SWEEP_THRESHOLD = 1024;
1112
+
1113
+ /**
1114
+ * The last subscriber of a selector key left. The entry stays in the map so
1115
+ * a key that comes back (a list re-rendering the same items) reuses it
1116
+ * instead of allocating and inserting again; once enough released keys pile
1117
+ * up they are swept out, so the map stays bounded.
1118
+ * @param {Tracked} tracked
1119
+ */
1120
+ function release_selector_key(tracked) {
1121
+ var accessors = /** @type {SelectorAccessors} */ (tracked.a);
1122
+ tracked.f |= RELEASED;
1123
+ if (++accessors.n > SELECTOR_SWEEP_THRESHOLD) {
1124
+ var m = accessors.m;
1125
+ for (var [key, t] of m) {
1126
+ if ((t.f & RELEASED) !== 0) {
1127
+ m.delete(key);
1128
+ }
1129
+ }
1130
+ accessors.n = 0;
1131
+ }
1132
+ }
1133
+
1134
+ /**
1135
+ * A key found in the map again after its subscribers had all left.
1136
+ * @param {Tracked} tracked
1137
+ */
1138
+ export function revive_selector_key(tracked) {
1139
+ tracked.f ^= RELEASED;
1140
+ /** @type {SelectorAccessors} */ (tracked.a).n--;
1141
+ }
1142
+
1143
+ /**
1144
+ * A tracked match flag for one selector key. The selector's shared accessor
1145
+ * object carries the map and the key rides in the hash slot, so the entry can
1146
+ * be released from `unlink_subscriber` without an object per key.
1147
+ * @param {boolean} value
1148
+ * @param {Block} block
1149
+ * @param {SelectorAccessors} accessors
1150
+ * @param {any} key
1151
+ * @returns {Tracked}
1152
+ */
1153
+ export function selector_tracked(value, block, accessors, key) {
1154
+ var t = /** @type {Tracked} */ (new TrackedValue(value, block, accessors, key));
1155
+ t.f |= SELECTOR;
1156
+ return t;
1157
+ }
1158
+
1159
+ /**
1160
+ * Install the dependency chain produced by a run, unsubscribing from any
1161
+ * previous dependencies that were not read again.
1162
+ * @param {Block | Derived} reaction
1163
+ * @param {Dependency | null} dependencies
1164
+ */
1165
+ function finish_dependencies(reaction, dependencies) {
1166
+ var stale = reaction.d;
1167
+ while (stale !== null) {
1168
+ unlink_subscriber(stale);
1169
+ stale = stale.n;
1170
+ }
1171
+ reaction.d = dependencies;
1172
+ }
1173
+
1174
+ /**
1175
+ * Unsubscribe a block from every tracked value it read. Dependencies on
1176
+ * values owned by an already-destroyed block are dropped without unlinking,
1177
+ * since the owner's subscriber list dies with it.
1178
+ * @param {Block} block
1179
+ */
1180
+ export function remove_dependencies(block) {
1181
+ var dependency = block.d;
1182
+ while (dependency !== null) {
1183
+ var owner = dependency.t.b;
1184
+ if (owner === null || (owner.f & DESTROYED) === 0) {
1185
+ unlink_subscriber(dependency);
1186
+ }
1187
+ dependency = dependency.n;
1188
+ }
1189
+ block.d = null;
1190
+ }
1191
+
1192
+ /**
1193
+ * Schedule every reaction subscribed to `tracked`. Deriveds are lazy, so their
1194
+ * own subscribers are marked instead; subscribers that were destroyed without
1195
+ * unlinking are pruned here.
1196
+ * @param {Tracked | Derived} tracked
1197
+ */
1198
+ function mark_subscribers(tracked) {
1199
+ var dependency = tracked.sb;
1200
+ while (dependency !== null) {
1201
+ var next = dependency.sn;
1202
+ var reaction = dependency.r;
1203
+ var flags = reaction.f;
1204
+ if ((flags & DERIVED) !== 0) {
1205
+ var derived = /** @type {Derived} */ (reaction);
1206
+ // Marking the derived's own subscribers first prunes its destroyed
1207
+ // readers, so a derived whose owner is gone and that nothing reads any
1208
+ // more is dropped on this write; one still read elsewhere keeps
1209
+ // forwarding notifications.
1210
+ mark_subscribers(derived);
1211
+ var derived_owner = derived.b;
1212
+ if (derived_owner !== null && (derived_owner.f & DESTROYED) !== 0 && derived.sb === null) {
1213
+ unlink_subscriber(dependency);
1214
+ }
1215
+ } else if ((flags & DESTROYED) !== 0) {
1216
+ unlink_subscriber(dependency);
1217
+ } else {
1218
+ schedule_update(/** @type {Block} */ (reaction));
1219
+ }
1220
+ dependency = next;
1221
+ }
878
1222
  }
879
1223
 
880
1224
  /**
@@ -941,125 +1285,114 @@ function trigger_track_get(fn, v) {
941
1285
  }
942
1286
 
943
1287
  /**
944
- * @param {Block} root_block
1288
+ * @param {Block} a
1289
+ * @param {Block} b
1290
+ * @returns {number}
945
1291
  */
946
- function flush_updates(root_block) {
947
- /** @type {Block | null} */
948
- var current = root_block;
949
- var pre_effects = [];
950
- var other_blocks = [];
951
- var effects = [];
952
- // The nearest enclosing directly-scheduled block ("update source"). While it is
953
- // non-null we are inside a source's subtree and scan every descendant. Above
954
- // sources we only follow the CONTAINS_UPDATE routing path, so sibling subtrees
955
- // that contain no update are skipped. When scoping is disabled we treat the
956
- // whole root tree as one source — the original full-tree scan.
957
- /** @type {Block | null} */
958
- var scope_root = disable_scoped_flush ? root_block : null;
1292
+ function by_block_id(a, b) {
1293
+ return a.i - b.i;
1294
+ }
959
1295
 
960
- while (current !== null) {
961
- var flags = current.f;
962
- var on_path = (flags & CONTAINS_UPDATE) !== 0;
1296
+ /**
1297
+ * Run every scheduled block, in creation order (so a parent runs before its
1298
+ * descendants), in three phases: pre-effects, render blocks, then effects.
1299
+ * @param {Queue} pending
1300
+ */
1301
+ function flush_queue(pending) {
1302
+ var blocks = pending.blocks;
1303
+ var length = blocks.length;
963
1304
 
964
- if (on_path) {
965
- current.f ^= CONTAINS_UPDATE;
1305
+ if (length > 0) {
1306
+ if (!pending.sorted) {
1307
+ blocks.sort(by_block_id);
966
1308
  }
967
1309
 
968
- if ((flags & UPDATE_SOURCE) !== 0) {
969
- current.f ^= UPDATE_SOURCE;
970
- if (scope_root === null) {
971
- scope_root = current;
972
- }
973
- }
1310
+ var has_effects = false;
974
1311
 
975
- if ((flags & PAUSED) === 0 && (on_path || scope_root !== null)) {
976
- if ((flags & PRE_EFFECT_BLOCK) !== 0) {
977
- pre_effects.push(current);
978
- } else if ((flags & EFFECT_BLOCK) !== 0) {
979
- effects.push(current);
980
- } else {
981
- other_blocks.push(current);
1312
+ for (var i = 0; i < length; i++) {
1313
+ var block = blocks[i];
1314
+ var flags = block.f;
1315
+ block.f = flags & ~SCHEDULED;
1316
+ if ((flags & (PRE_EFFECT_BLOCK | EFFECT_BLOCK)) !== 0) {
1317
+ has_effects = true;
982
1318
  }
983
- /** @type {Block | null} */
984
- var child = current.first;
1319
+ }
985
1320
 
986
- if (child !== null) {
987
- current = child;
988
- continue;
989
- }
1321
+ // New schedules land in the queue that replaced `pending`, so the array
1322
+ // can be run in place. Effects need the three-phase ordering; a queue of
1323
+ // only render blocks (the common flush) runs straight through.
1324
+ if (has_effects) {
1325
+ run_phases(blocks, length);
1326
+ } else {
1327
+ run_phase(blocks);
990
1328
  }
991
1329
 
992
- /** @type {Block | null} */
993
- var parent = current.p;
994
- current = current.next;
1330
+ blocks.length = 0;
1331
+ pending.sorted = true;
1332
+ pending.last = 0;
1333
+ }
995
1334
 
996
- while (current === null && parent !== null) {
997
- if (parent === scope_root) {
998
- scope_root = null;
999
- }
1000
- current = parent.next;
1001
- parent = parent.p;
1335
+ if (queued_post_block_flush.length > 0) {
1336
+ var callbacks = queued_post_block_flush;
1337
+ queued_post_block_flush = [];
1338
+ for (var j = 0; j < callbacks.length; j++) {
1339
+ callbacks[j]();
1002
1340
  }
1003
1341
  }
1342
+ }
1004
1343
 
1005
- var arr_length = 0;
1344
+ /**
1345
+ * Runs a queue that contains effects in three phases: pre-effects, render
1346
+ * blocks, then effects. Kept out of {@link flush_queue} so a render-only flush
1347
+ * never compiles it.
1348
+ * @param {Block[]} blocks
1349
+ * @param {number} length
1350
+ */
1351
+ function run_phases(blocks, length) {
1352
+ /** @type {Block[]} */
1353
+ var pre_effects = [];
1354
+ /** @type {Block[]} */
1355
+ var other_blocks = [];
1356
+ /** @type {Block[]} */
1357
+ var effects = [];
1006
1358
 
1007
- // Phase 1: pre-effects (e.g. update tracked values before render blocks read them)
1008
- arr_length = pre_effects.length;
1009
- for (var i = 0; i < arr_length; i++) {
1010
- var block = pre_effects[i];
1359
+ for (var i = 0; i < length; i++) {
1360
+ var block = blocks[i];
1361
+ var flags = block.f;
1011
1362
 
1012
- try {
1013
- if ((block.f & (PAUSED | DESTROYED)) === 0 && is_block_dirty(block)) {
1014
- run_block(block);
1015
- }
1016
- } catch (error) {
1017
- handle_error(error, block);
1363
+ // A paused block is re-checked when it resumes; a destroyed one is gone.
1364
+ if ((flags & (PAUSED | DESTROYED)) !== 0) {
1365
+ continue;
1018
1366
  }
1019
- }
1020
-
1021
- // Phase 2: all other blocks except effects
1022
- arr_length = other_blocks.length;
1023
- for (var i = 0; i < arr_length; i++) {
1024
- var block = other_blocks[i];
1025
-
1026
- try {
1027
- if ((block.f & (PAUSED | DESTROYED)) === 0 && is_block_dirty(block)) {
1028
- run_block(block);
1029
- }
1030
- } catch (error) {
1031
- handle_error(error, block);
1367
+ if ((flags & PRE_EFFECT_BLOCK) !== 0) {
1368
+ pre_effects.push(block);
1369
+ } else if ((flags & EFFECT_BLOCK) !== 0) {
1370
+ effects.push(block);
1371
+ } else {
1372
+ other_blocks.push(block);
1032
1373
  }
1033
1374
  }
1034
1375
 
1035
- // Phase 3: effects
1036
- arr_length = effects.length;
1037
- for (var i = 0; i < arr_length; i++) {
1038
- var block = effects[i];
1039
-
1040
- try {
1041
- if ((block.f & (PAUSED | DESTROYED)) === 0 && is_block_dirty(block)) {
1042
- run_block(block);
1043
- }
1044
- } catch (error) {
1045
- handle_error(error, block);
1046
- }
1047
- }
1376
+ run_phase(pre_effects);
1377
+ run_phase(other_blocks);
1378
+ run_phase(effects);
1048
1379
  }
1049
1380
 
1050
1381
  /**
1051
- * @param {Block[]} root_blocks
1382
+ * @param {Block[]} blocks
1052
1383
  */
1053
- function flush_queued_root_blocks(root_blocks) {
1054
- for (let i = 0; i < root_blocks.length; i++) {
1055
- flush_updates(root_blocks[i]);
1056
- }
1384
+ function run_phase(blocks) {
1385
+ for (var i = 0; i < blocks.length; i++) {
1386
+ var block = blocks[i];
1387
+ var flags = block.f;
1057
1388
 
1058
- if (queued_post_block_flush.length > 0) {
1059
- var callbacks = queued_post_block_flush;
1060
- queued_post_block_flush = [];
1061
- for (var j = 0; j < callbacks.length; j++) {
1062
- callbacks[j]();
1389
+ try {
1390
+ if ((flags & (PAUSED | DESTROYED)) === 0 && is_block_dirty(block)) {
1391
+ // A block that has never run has no children or teardown to clear.
1392
+ run_block(block, (flags & BLOCK_HAS_RUN) === 0);
1393
+ }
1394
+ } catch (error) {
1395
+ handle_error(error, block);
1063
1396
  }
1064
1397
  }
1065
1398
  }
@@ -1091,14 +1424,21 @@ function flush_microtasks() {
1091
1424
  'Maximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state.',
1092
1425
  );
1093
1426
  }
1094
- var previous_queued_root_blocks = queued_root_blocks;
1095
- queued_root_blocks = [];
1096
- flush_queued_root_blocks(previous_queued_root_blocks);
1427
+ var pending = queue;
1428
+ queue = create_queue();
1429
+ flush_depth++;
1430
+ try {
1431
+ flush_queue(pending);
1432
+ } finally {
1433
+ flush_depth--;
1434
+ }
1097
1435
 
1098
1436
  if (!is_micro_task_queued) {
1099
1437
  flush_count = 0;
1100
1438
  }
1101
- old_values.clear();
1439
+ if (flush_depth === 0) {
1440
+ release_old_values();
1441
+ }
1102
1442
  }
1103
1443
 
1104
1444
  /**
@@ -1128,64 +1468,27 @@ export function queue_post_block_flush_callback(fn) {
1128
1468
  * @param {Block} block
1129
1469
  */
1130
1470
  export function schedule_update(block) {
1471
+ if ((block.f & SCHEDULED) !== 0) {
1472
+ return;
1473
+ }
1474
+ block.f |= SCHEDULED;
1475
+
1131
1476
  if (scheduler_mode === FLUSH_MICROTASK) {
1132
1477
  queue_microtask();
1133
1478
  }
1134
- // The scheduled block roots the subtree that flush_updates scans for dirty
1135
- // subscribers. Mark it even if it (or an ancestor) is already on a routing
1136
- // path, so the early return below still records it as a scan root.
1137
- block.f |= UPDATE_SOURCE;
1138
- let current = block;
1139
-
1140
- while (current !== null) {
1141
- var flags = current.f;
1142
- if ((flags & CONTAINS_UPDATE) !== 0) return;
1143
- current.f ^= CONTAINS_UPDATE;
1144
- if ((flags & ROOT_BLOCK) !== 0) {
1145
- break;
1146
- }
1147
- current = /** @type {Block} */ (current.p);
1148
- }
1149
1479
 
1150
- queued_root_blocks.push(current);
1480
+ var id = block.i;
1481
+ if (id < queue.last) {
1482
+ queue.sorted = false;
1483
+ }
1484
+ queue.last = id;
1485
+ queue.blocks.push(block);
1151
1486
  }
1152
1487
 
1153
1488
  /**
1154
1489
  * @param {Tracked | Derived} tracked
1155
1490
  */
1156
1491
  function register_dependency(tracked) {
1157
- if (!disable_scoped_flush && active_block !== null && active_block !== tracked.b) {
1158
- // Scoped flush only scans the owner's subtree for dirty subscribers, valid
1159
- // only while every subscriber lives inside it. The subscriber↔owner
1160
- // ancestry is structural and stable for a block's lifetime (its `.p` chain
1161
- // never changes — DOM moves don't reparent blocks), so we only need to
1162
- // verify *new* dependency edges: if this reaction already depended on
1163
- // `tracked` on its previous run (it's in the prior chain), the ancestry
1164
- // walk was already done — skip it. This keeps the (possibly deep) walk off
1165
- // the hot path for the common case of stable subscriptions.
1166
- var already_seen = false;
1167
- var prev_dep = active_reaction === null ? null : active_reaction.d;
1168
- while (prev_dep !== null) {
1169
- if (prev_dep.t === tracked) {
1170
- already_seen = true;
1171
- break;
1172
- }
1173
- prev_dep = prev_dep.n;
1174
- }
1175
-
1176
- if (!already_seen) {
1177
- var owner = tracked.b;
1178
- /** @type {Block | null} */
1179
- var node = active_block;
1180
- while (node !== null && node !== owner) {
1181
- node = node.p;
1182
- }
1183
- if (node === null) {
1184
- disable_scoped_flush = true;
1185
- }
1186
- }
1187
- }
1188
-
1189
1492
  var dependency = active_dependency;
1190
1493
 
1191
1494
  if (dependency === null) {
@@ -1247,75 +1550,26 @@ export function get(tracked) {
1247
1550
  : get_tracked(/** @type {Tracked} */ (tracked));
1248
1551
  }
1249
1552
 
1250
- /**
1251
- * @param {any} lazy
1252
- * @param {number} [index]
1253
- * @returns {any}
1254
- */
1255
- export function lazy_array_get(lazy, index = 0) {
1256
- if (is_array(lazy)) {
1257
- return lazy[index];
1258
- }
1259
- var flags = lazy.f;
1260
- if (flags === TRACKED) {
1261
- return index === 0
1262
- ? get_tracked(/** @type {Tracked} */ (lazy))
1263
- : index === 1
1264
- ? lazy
1265
- : undefined;
1266
- }
1267
- if (flags === DERIVED) {
1268
- return index === 0
1269
- ? get_derived(/** @type {Derived} */ (lazy))
1270
- : index === 1
1271
- ? lazy
1272
- : undefined;
1273
- }
1274
- return iterable_array_from(lazy, index)[0];
1275
- }
1276
-
1277
- /**
1278
- * @param {any} lazy
1279
- * @param {number} [index]
1280
- * @returns {any[]}
1281
- */
1282
- export function lazy_array_rest(lazy, index = 0) {
1283
- if (is_array(lazy)) {
1284
- return lazy.slice(index);
1285
- }
1286
- var flags = lazy.f;
1287
- if (flags === TRACKED) {
1288
- return index === 0
1289
- ? [get_tracked(/** @type {Tracked} */ (lazy)), lazy]
1290
- : index === 1
1291
- ? [lazy]
1292
- : [];
1293
- }
1294
- if (flags === DERIVED) {
1295
- return index === 0
1296
- ? [get_derived(/** @type {Derived} */ (lazy)), lazy]
1297
- : index === 1
1298
- ? [lazy]
1299
- : [];
1300
- }
1301
- return iterable_array_from(lazy, index);
1302
- }
1303
-
1304
1553
  /**
1305
1554
  * @param {Tracked} tracked
1306
1555
  */
1307
1556
  export function get_tracked(tracked) {
1308
1557
  var value = tracked.__v;
1309
1558
  if (tracking) {
1310
- register_dependency(tracked);
1559
+ // register_dependency, inline for the first read of a run.
1560
+ if (active_dependency === null) {
1561
+ active_dependency = create_dependency(tracked);
1562
+ } else {
1563
+ register_dependency(tracked);
1564
+ }
1311
1565
  }
1312
1566
 
1313
1567
  if (value === SUSPENSE_PENDING || value === SUSPENSE_REJECTED) {
1314
1568
  throw ASYNC_DERIVED_READ_THROWN;
1315
1569
  }
1316
1570
 
1317
- if (teardown && old_values.has(tracked)) {
1318
- value = old_values.get(tracked);
1571
+ if (teardown && tracked.o !== UNINITIALIZED) {
1572
+ value = tracked.o;
1319
1573
  }
1320
1574
  var get = tracked.a.get;
1321
1575
  if (get !== undefined) {
@@ -1324,57 +1578,6 @@ export function get_tracked(tracked) {
1324
1578
  return value;
1325
1579
  }
1326
1580
 
1327
- /**
1328
- * @param {any} lazy
1329
- * @param {any} value
1330
- * @param {number} [index]
1331
- * @returns {void}
1332
- */
1333
- export function lazy_array_set(lazy, value, index = 0) {
1334
- if (is_array(lazy)) {
1335
- lazy[index] = value;
1336
- return;
1337
- }
1338
- var flags = lazy.f;
1339
- if (flags === TRACKED || flags === DERIVED) {
1340
- if (index === 0) {
1341
- set(/** @type {Derived | Tracked} */ (lazy), value);
1342
- return;
1343
- }
1344
- if (index === 1) {
1345
- throw_tracked_index_reference_error();
1346
- }
1347
- return;
1348
- }
1349
- lazy[index] = value;
1350
- }
1351
-
1352
- /**
1353
- * @param {any} lazy
1354
- * @param {number} [index]
1355
- * @param {number} [d]
1356
- * @returns {number}
1357
- */
1358
- export function lazy_array_update(lazy, index = 0, d = 1) {
1359
- var value = lazy_array_get(lazy, index);
1360
- var result = d === 1 ? value++ : value--;
1361
- lazy_array_set(lazy, value, index);
1362
- return result;
1363
- }
1364
-
1365
- /**
1366
- * @param {any} lazy
1367
- * @param {number} [index]
1368
- * @param {number} [d]
1369
- * @returns {number}
1370
- */
1371
- export function lazy_array_update_pre(lazy, index = 0, d = 1) {
1372
- var value = lazy_array_get(lazy, index);
1373
- var new_value = d === 1 ? ++value : --value;
1374
- lazy_array_set(lazy, new_value, index);
1375
- return new_value;
1376
- }
1377
-
1378
1581
  /**
1379
1582
  * @param {Derived | Tracked} tracked
1380
1583
  * @param {any} value
@@ -1392,24 +1595,42 @@ export function set(tracked, value) {
1392
1595
  var tracked_block = tracked.b;
1393
1596
 
1394
1597
  if ((tracked_block.f & CONTAINS_TEARDOWN) !== 0) {
1395
- if (teardown) {
1396
- old_values.set(tracked, value);
1397
- } else {
1398
- old_values.set(tracked, old_value);
1598
+ if (tracked.o === UNINITIALIZED) {
1599
+ old_value_holders.push(tracked);
1399
1600
  }
1601
+ tracked.o = teardown ? value : old_value;
1400
1602
  }
1401
1603
 
1402
1604
  let set = tracked.a.set;
1403
- if (set !== undefined) {
1605
+ if (typeof set === 'function') {
1404
1606
  value = untrack(() => set(value, old_value));
1607
+ } else if (DEV && set === undefined && (tracked.f & DERIVED) !== 0) {
1608
+ warn_readonly_derived_write(/** @type {Derived} */ (tracked));
1405
1609
  }
1406
1610
 
1407
1611
  tracked.__v = value;
1408
1612
  tracked.c = increment_clock();
1409
- schedule_update(tracked_block);
1613
+ mark_subscribers(tracked);
1410
1614
  }
1411
1615
  }
1412
1616
 
1617
+ /** @type {WeakSet<object>} */
1618
+ var warned_derived = new WeakSet();
1619
+
1620
+ /**
1621
+ * A derived created without a setter is read-only by contract (`Derived<V>`);
1622
+ * a write still lands as a temporary value until the next recompute, so in
1623
+ * development it is reported once per derived.
1624
+ * @param {Derived} derived
1625
+ */
1626
+ function warn_readonly_derived_write(derived) {
1627
+ if (warned_derived.has(derived)) return;
1628
+ warned_derived.add(derived);
1629
+ console.warn(
1630
+ 'Writing to a read-only derived. Create it with a setter, `track(fn, undefined, true)` or `track(fn, get, set)`, when it is meant to be written.',
1631
+ );
1632
+ }
1633
+
1413
1634
  /**
1414
1635
  * @template T
1415
1636
  * @param {() => T} fn
@@ -1435,144 +1656,43 @@ export function untrack(fn) {
1435
1656
  */
1436
1657
  export function flush_sync(fn) {
1437
1658
  var previous_scheduler_mode = scheduler_mode;
1438
- var previous_queued_root_blocks = queued_root_blocks;
1659
+ var previous_queue = queue;
1439
1660
 
1661
+ flush_depth++;
1440
1662
  try {
1441
- /** @type {Block[]} */
1442
- var root_blocks = [];
1443
-
1444
1663
  scheduler_mode = FLUSH_SYNC;
1445
- queued_root_blocks = root_blocks;
1664
+ queue = create_queue();
1446
1665
  is_micro_task_queued = false;
1447
1666
 
1448
- flush_queued_root_blocks(previous_queued_root_blocks);
1667
+ // Drains previous_queue, which then stays empty for the restore below.
1668
+ flush_queue(previous_queue);
1449
1669
 
1450
1670
  var result = fn?.();
1451
1671
 
1452
- if (queued_root_blocks.length > 0 || root_blocks.length > 0) {
1453
- flush_sync();
1672
+ // Each pass runs the blocks scheduled by the previous one.
1673
+ while (queue.blocks.length > 0) {
1674
+ var pending = queue;
1675
+ queue = create_queue();
1676
+ flush_queue(pending);
1454
1677
  }
1455
1678
 
1456
1679
  flush_count = 0;
1457
1680
 
1681
+ // Old values only matter to teardowns run by the flush in progress; a
1682
+ // sync flush nested in another flush (an effect calling `flushSync`)
1683
+ // leaves them for the outer flush's remaining teardowns.
1684
+ if (flush_depth === 1) {
1685
+ release_old_values();
1686
+ }
1687
+
1458
1688
  return /** @type {T} */ (result);
1459
1689
  } finally {
1690
+ flush_depth--;
1460
1691
  scheduler_mode = previous_scheduler_mode;
1461
- queued_root_blocks = previous_queued_root_blocks;
1692
+ queue = previous_queue;
1462
1693
  }
1463
1694
  }
1464
1695
 
1465
- /**
1466
- * @param {() => Object} fn
1467
- * @returns {Object}
1468
- */
1469
- export function spread_props(fn) {
1470
- return proxy_props(fn);
1471
- }
1472
-
1473
- /**
1474
- * @param {() => Object} fn
1475
- * @returns {Object}
1476
- */
1477
- export function proxy_props(fn) {
1478
- const memo = derived(fn, /** @type {Block} */ (active_block));
1479
-
1480
- return new Proxy(
1481
- {},
1482
- {
1483
- get(_, property) {
1484
- /** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
1485
- var obj = get_derived(memo);
1486
-
1487
- // Handle array of objects/spreads (for multiple props)
1488
- if (is_array(obj)) {
1489
- // Search in reverse order (right-to-left) since later props override earlier ones
1490
- /** @type {Record<string | symbol, any>} */
1491
- var item;
1492
- for (var i = obj.length - 1; i >= 0; i--) {
1493
- item = obj[i];
1494
- if (property in item) {
1495
- return item[property];
1496
- }
1497
- }
1498
- return undefined;
1499
- }
1500
-
1501
- // Single object case
1502
- return obj[property];
1503
- },
1504
- has(_, property) {
1505
- if (property === TRACKED_OBJECT) {
1506
- return true;
1507
- }
1508
- /** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
1509
- var obj = get_derived(memo);
1510
-
1511
- // Handle array of objects/spreads
1512
- if (is_array(obj)) {
1513
- for (var i = obj.length - 1; i >= 0; i--) {
1514
- if (property in obj[i]) {
1515
- return true;
1516
- }
1517
- }
1518
- return false;
1519
- }
1520
-
1521
- return property in obj;
1522
- },
1523
- getOwnPropertyDescriptor(_, key) {
1524
- /** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
1525
- var obj = get_derived(memo);
1526
-
1527
- // Handle array of objects/spreads
1528
- if (is_array(obj)) {
1529
- /** @type {Record<string | symbol, any>} */
1530
- var item;
1531
- for (var i = obj.length - 1; i >= 0; i--) {
1532
- item = obj[i];
1533
- if (key in item) {
1534
- return get_descriptor(item, key);
1535
- }
1536
- }
1537
- return undefined;
1538
- }
1539
-
1540
- if (key in obj) {
1541
- return get_descriptor(obj, key);
1542
- }
1543
- },
1544
- ownKeys() {
1545
- /** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
1546
- var obj = get_derived(memo);
1547
- /** @type {Record<string | symbol, 1>} */
1548
- var done = {};
1549
- /** @type {(string | symbol)[]} */
1550
- var keys = [];
1551
-
1552
- // Handle array of objects/spreads
1553
- if (is_array(obj)) {
1554
- // Collect all keys from all objects, order doesn't matter
1555
- /** @type {Record<string | symbol, any>} */
1556
- var item;
1557
- for (var i = 0; i < obj.length; i++) {
1558
- item = obj[i];
1559
- for (const key of Reflect.ownKeys(item)) {
1560
- if (done[key]) {
1561
- continue;
1562
- }
1563
- done[key] = 1;
1564
- keys.push(key);
1565
- }
1566
- }
1567
- return keys;
1568
- }
1569
-
1570
- return Reflect.ownKeys(obj);
1571
- },
1572
- },
1573
- );
1574
- }
1575
-
1576
1696
  /**
1577
1697
  * @template T
1578
1698
  * @param {() => T} fn
@@ -1742,7 +1862,7 @@ export function safe_scope(err = 'Cannot access outside of a component context')
1742
1862
  export function create_component_ctx() {
1743
1863
  return {
1744
1864
  b: active_block,
1745
- c: null,
1865
+ c: active_component === null ? null : active_component.c,
1746
1866
  e: null,
1747
1867
  m: false,
1748
1868
  p: active_component,
@@ -1753,8 +1873,7 @@ export function create_component_ctx() {
1753
1873
  * @returns {void}
1754
1874
  */
1755
1875
  export function push_component() {
1756
- var component = create_component_ctx();
1757
- active_component = component;
1876
+ active_component = create_component_ctx();
1758
1877
  }
1759
1878
 
1760
1879
  /**
@@ -1763,27 +1882,168 @@ export function push_component() {
1763
1882
  export function pop_component() {
1764
1883
  var component = /** @type {Component} */ (active_component);
1765
1884
  component.m = true;
1766
- var effects = component.e;
1767
- if (effects !== null) {
1768
- var length = effects.length;
1769
- for (var i = 0; i < length; i++) {
1770
- var { b: block, fn, r: reaction } = effects[i];
1771
- var previous_block = active_block;
1772
- var previous_reaction = active_reaction;
1885
+ if (component.e !== null) {
1886
+ create_deferred_effects(component.e);
1887
+ }
1888
+ active_component = component.p;
1889
+ }
1773
1890
 
1774
- try {
1775
- active_block = block;
1776
- active_reaction = reaction;
1777
- effect(fn);
1778
- } finally {
1779
- active_block = previous_block;
1780
- active_reaction = previous_reaction;
1781
- }
1782
- }
1891
+ /**
1892
+ * Renders a component: `fn(props)` runs under a fresh component context (the
1893
+ * owner of the component's context values and deferred effects), and the
1894
+ * element it returns renders before `anchor`.
1895
+ * @param {Function} fn
1896
+ * @param {Node | AppendIntoAnchor} anchor
1897
+ * @param {Record<string, any>} props
1898
+ * @param {Block | null} [block=active_block]
1899
+ * @returns {void}
1900
+ */
1901
+ export function render_component(fn, anchor, props, block = active_block) {
1902
+ // An optional component (an undefined prop, an import that resolved to
1903
+ // nothing) renders nothing; any other non-function is a programming error.
1904
+ if (fn == null) {
1905
+ return;
1906
+ }
1907
+ if (typeof fn !== 'function') {
1908
+ throw_invalid_component_type(fn);
1909
+ }
1910
+
1911
+ // push_component, inline: one component per call is the common case.
1912
+ var parent = active_component;
1913
+ /** @type {Component} */
1914
+ var component = (active_component = {
1915
+ b: active_block,
1916
+ c: parent === null ? null : parent.c,
1917
+ e: null,
1918
+ m: false,
1919
+ p: parent,
1920
+ });
1921
+
1922
+ // A module-level component carries its render function under a symbol
1923
+ // (see the compiler): calling it directly skips the element the component
1924
+ // would return only to be rendered here.
1925
+ var render = /** @type {any} */ (fn)[RENDER_ENTRY];
1926
+ if (render !== undefined) {
1927
+ render(anchor, block, props);
1928
+ } else {
1929
+ render_value(fn(props), /** @type {ChildNode} */ (anchor), block);
1930
+ }
1931
+
1932
+ // pop_component, inline.
1933
+ component.m = true;
1934
+ if (component.e !== null) {
1935
+ create_deferred_effects(component.e);
1783
1936
  }
1784
1937
  active_component = component.p;
1785
1938
  }
1786
1939
 
1940
+ /**
1941
+ * A reaction that stands in for a block whose creation is being decided (see
1942
+ * `probe_if`). Flagged destroyed so a write during the probe prunes
1943
+ * its subscription instead of scheduling it.
1944
+ * @type {any}
1945
+ */
1946
+ var probe_reaction = { f: DESTROYED, d: null, blocks: null };
1947
+
1948
+ /**
1949
+ * Evaluates an if condition once, tracked, against a scratch reaction that is
1950
+ * never scheduled. When the evaluation recorded no dependency the condition
1951
+ * can never re-run (a block with no dependencies is never scheduled), so the
1952
+ * selected branch is rendered straight away, untracked, and true is returned:
1953
+ * the if needs no block. Otherwise the recorded links are unlinked again and
1954
+ * false is returned: the caller creates the block, whose own first run
1955
+ * evaluates the condition and subscribes it. The links are never handed to
1956
+ * the block, because a write during the block's first run (a child's setup,
1957
+ * a nested if) would prune links that still point at this destroyed reaction
1958
+ * before the block could take them over. A throwing condition (a pending
1959
+ * async read) unlinks what it recorded and rethrows.
1960
+ * @param {(x: any) => any} fn
1961
+ * @param {any} x
1962
+ * @param {Node | import('#client').AppendIntoAnchor} node
1963
+ * @returns {boolean} whether the branch was rendered without a block
1964
+ */
1965
+ export function probe_if(fn, x, node) {
1966
+ var previous_reaction = active_reaction;
1967
+ var previous_tracking = tracking;
1968
+ var previous_dependency = active_dependency;
1969
+ active_reaction = probe_reaction;
1970
+ tracking = true;
1971
+ active_dependency = null;
1972
+ /** @type {Dependency | null} */
1973
+ var recorded;
1974
+ /** @type {any} */
1975
+ var branch;
1976
+ try {
1977
+ branch = fn(x);
1978
+ recorded = active_dependency;
1979
+ } catch (error) {
1980
+ unlink_dependencies(active_dependency);
1981
+ active_reaction = previous_reaction;
1982
+ tracking = previous_tracking;
1983
+ active_dependency = previous_dependency;
1984
+ throw error;
1985
+ }
1986
+ active_reaction = previous_reaction;
1987
+ active_dependency = previous_dependency;
1988
+ if (recorded !== null) {
1989
+ unlink_dependencies(recorded);
1990
+ tracking = previous_tracking;
1991
+ return false;
1992
+ }
1993
+ // The branch renders untracked, like a branch block would; `tracking` is
1994
+ // restored by the enclosing `run_block` if the branch throws.
1995
+ if (branch !== undefined) {
1996
+ tracking = false;
1997
+ branch(node, x);
1998
+ }
1999
+ tracking = previous_tracking;
2000
+ return true;
2001
+ }
2002
+
2003
+ /**
2004
+ * @param {Dependency | null} dependency the head of a recorded chain
2005
+ */
2006
+ function unlink_dependencies(dependency) {
2007
+ while (dependency !== null) {
2008
+ unlink_subscriber(dependency);
2009
+ dependency = dependency.n;
2010
+ }
2011
+ }
2012
+
2013
+ /**
2014
+ * Calls `fn(arg)` with tracking off, so reads inside it subscribe nothing.
2015
+ * `tracking` is restored by the enclosing `run_block` if `fn` throws.
2016
+ * @param {(arg: any, context: any) => void} fn
2017
+ * @param {any} arg
2018
+ * @param {any} [context]
2019
+ */
2020
+ export function run_untracked(fn, arg, context) {
2021
+ var previous_tracking = tracking;
2022
+ tracking = false;
2023
+ fn(arg, context);
2024
+ tracking = previous_tracking;
2025
+ }
2026
+
2027
+ /**
2028
+ * Creates the effects a component registered while rendering, each under the
2029
+ * block and reaction that were active at its `effect()` call.
2030
+ * @param {any[]} effects flat triples: fn, block, reaction (see `user_effect`)
2031
+ */
2032
+ function create_deferred_effects(effects) {
2033
+ // Creating an effect block only links and schedules it, so nothing here
2034
+ // can throw between saving and restoring the active block.
2035
+ var previous_block = active_block;
2036
+ var previous_reaction = active_reaction;
2037
+ var length = effects.length;
2038
+ for (var i = 0; i < length; i += 3) {
2039
+ active_block = /** @type {Block} */ (effects[i + 1]);
2040
+ active_reaction = /** @type {Block | Derived | null} */ (effects[i + 2]);
2041
+ effect(/** @type {Function} */ (effects[i]));
2042
+ }
2043
+ active_block = previous_block;
2044
+ active_reaction = previous_reaction;
2045
+ }
2046
+
1787
2047
  /**
1788
2048
  * @template T
1789
2049
  * @param {() => T} fn
@@ -1815,40 +2075,3 @@ export function ref_prop() {
1815
2075
  export function create_ref_prop(get_ref_value, set_ref_value) {
1816
2076
  return create_core_ref_prop(() => untrack(get_ref_value), set_ref_value);
1817
2077
  }
1818
-
1819
- /**
1820
- * @template T
1821
- * @param {T | undefined} value
1822
- * @param {T} fallback
1823
- * @returns {T}
1824
- */
1825
- export function fallback(value, fallback) {
1826
- return value === undefined ? fallback : value;
1827
- }
1828
-
1829
- /**
1830
- * @param {Record<string | symbol, unknown>} obj
1831
- * @param {string[]} exclude_keys
1832
- * @returns {Record<string | symbol, unknown>}
1833
- */
1834
- export function exclude_from_object(obj, exclude_keys) {
1835
- var keys = object_keys(obj);
1836
- /** @type {Record<string | symbol, unknown>} */
1837
- var new_obj = {};
1838
-
1839
- for (const key of keys) {
1840
- if (!exclude_keys.includes(key)) {
1841
- new_obj[key] = obj[key];
1842
- }
1843
- }
1844
-
1845
- for (const symbol of get_own_property_symbols(obj)) {
1846
- var ref_fn = obj[symbol];
1847
-
1848
- if (symbol.description === REF_PROP) {
1849
- new_obj[symbol] = ref_fn;
1850
- }
1851
- }
1852
-
1853
- return new_obj;
1854
- }