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
@@ -6,9 +6,10 @@ import {
6
6
  TEMPLATE_SVG_NAMESPACE,
7
7
  TEMPLATE_MATHML_NAMESPACE,
8
8
  } from '../../../constants.js';
9
- import { hydrate_append, hydrate_node, hydrating } from './hydration.js';
10
- import { create_text, get_first_child, get_next_sibling, is_firefox } from './operations.js';
9
+ import { H, hydrate_node, hydrating } from './hydration.js';
10
+ import { create_text, get_first_child, is_firefox } from './operations.js';
11
11
  import { active_block, active_namespace } from './runtime.js';
12
+ import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
12
13
 
13
14
  /**
14
15
  * Assigns start and end nodes to the active block's state.
@@ -30,92 +31,46 @@ export function assign_nodes(start, end) {
30
31
  }
31
32
 
32
33
  /**
33
- * Creates a DocumentFragment from an HTML string.
34
- * @param {string} html - The HTML string.
35
- * @param {boolean} use_svg_namespace - Whether to use SVG namespace.
36
- * @param {boolean} use_mathml_namespace - Whether to use MathML namespace.
37
- * @returns {DocumentFragment}
34
+ * Parses content that belongs in the SVG or MathML namespace. Installed by
35
+ * `template-ns.js` when a template or a dynamic element needs it, so an app
36
+ * with HTML templates only ships neither.
37
+ * @type {((html: string, ns: 'svg' | 'math') => DocumentFragment) | null}
38
38
  */
39
- export function create_fragment_from_html(
40
- html,
41
- use_svg_namespace = false,
42
- use_mathml_namespace = false,
43
- ) {
44
- if (use_svg_namespace) {
45
- return from_namespace(html, 'svg');
46
- }
47
- if (use_mathml_namespace) {
48
- return from_namespace(html, 'math');
49
- }
50
- var elem = document.createElement('template');
51
- elem.innerHTML = html;
52
- return elem.content;
53
- }
39
+ var parse_ns = null;
54
40
 
55
41
  /**
56
- * The hydration path of a {@link template} instance: adopts the server node(s)
57
- * at the cursor instead of cloning. Kept out of the clone path so a client-only
58
- * mount never compiles it.
59
- * @param {boolean} is_fragment
60
- * @param {number} count
61
- * @returns {Node}
42
+ * @param {(html: string, ns: 'svg' | 'math') => DocumentFragment} fn
62
43
  */
63
- function hydrate_template(is_fragment, count) {
64
- var node = /** @type {Node} */ (hydrate_node);
65
- var end = is_fragment ? hydrate_fragment_end(node, count) : node;
66
- // assign_nodes, inline: the per-node hydration path stays free of helpers.
67
- var block = /** @type {Block} */ (active_block);
68
- var s = block.s;
69
- if (s === null) {
70
- block.s = { start: node, end };
71
- } else if (s.start === null) {
72
- s.start = node;
73
- s.end = end;
74
- }
75
- return node;
44
+ export function set_ns_parser(fn) {
45
+ parse_ns = fn;
76
46
  }
77
47
 
78
48
  /**
79
- * The last top-level node of a hydrated fragment template. Walks using the
80
- * compiler-provided hop count so hydration never parses template HTML.
81
- * @param {Node} start
82
- * @param {number} count
83
- * @returns {Node}
49
+ * Creates a DocumentFragment from an HTML string.
50
+ * @param {string} html - The HTML string.
51
+ * @param {string} ns - `'svg'`, `'math'`, or `''` for HTML.
52
+ * @returns {DocumentFragment}
84
53
  */
85
- function hydrate_fragment_end(start, count) {
86
- var end = start;
87
-
88
- for (var i = 1; i < count; i++) {
89
- var next = get_next_sibling(end);
90
-
91
- while (next !== null && next.nodeType === Node.COMMENT_NODE) {
92
- next = get_next_sibling(next);
93
- }
94
-
95
- if (next === null) {
96
- break;
97
- }
98
-
99
- end = next;
54
+ export function create_fragment_from_html(html, ns = '') {
55
+ if (ns !== '') {
56
+ return /** @type {NonNullable<typeof parse_ns>} */ (parse_ns)(
57
+ html,
58
+ /** @type {'svg' | 'math'} */ (ns),
59
+ );
100
60
  }
101
-
102
- return end;
61
+ var elem = document.createElement('template');
62
+ elem.innerHTML = html;
63
+ return elem.content;
103
64
  }
104
65
 
105
66
  /**
106
- * A template's parsed content and the flags that shaped it, shared by every
107
- * clone of one `template()` instance.
67
+ * A template's content and flags, shared by every clone of one `template()`
68
+ * instance, with the parsed node cached for the namespace it was parsed in.
108
69
  * @typedef {{
109
- * content: string;
110
- * is_fragment: boolean;
111
- * use_import_node: boolean;
112
- * use_svg_namespace: boolean;
113
- * use_mathml_namespace: boolean;
114
- * is_comment: boolean;
115
- * has_start: boolean;
116
- * node: Node | DocumentFragment | undefined;
117
- * node_svg: boolean;
118
- * node_mathml: boolean;
70
+ * c: string;
71
+ * f: number;
72
+ * n: Node | DocumentFragment | undefined;
73
+ * ns: string | undefined;
119
74
  * }} TemplateState
120
75
  */
121
76
 
@@ -126,24 +81,33 @@ function hydrate_fragment_end(start, count) {
126
81
  * @returns {Node}
127
82
  */
128
83
  function clone_template(t) {
129
- var is_fragment = t.is_fragment;
130
- var is_comment = t.is_comment;
131
- var node = t.node;
132
- // If using runtime namespace, check active_namespace
133
- var svg = !is_comment && (t.use_svg_namespace || active_namespace === 'svg');
134
- var mathml = !is_comment && (t.use_mathml_namespace || active_namespace === 'mathml');
135
-
136
- if (node === undefined || t.node_svg !== svg || t.node_mathml !== mathml) {
137
- node = create_fragment_from_html(t.has_start ? t.content : '<!>' + t.content, svg, mathml);
84
+ var flags = t.f;
85
+ var content = t.c;
86
+ var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0;
87
+ // The namespace the content parses in: a static one from the flags, else
88
+ // the active namespace (a comment placeholder needs none).
89
+ var ns =
90
+ content === '<!>'
91
+ ? ''
92
+ : (flags & TEMPLATE_SVG_NAMESPACE) !== 0 || active_namespace === 'svg'
93
+ ? 'svg'
94
+ : (flags & TEMPLATE_MATHML_NAMESPACE) !== 0 || active_namespace === 'mathml'
95
+ ? 'math'
96
+ : '';
97
+ var node = t.n;
98
+
99
+ if (node === undefined || t.ns !== ns) {
100
+ // Content that opens with a placeholder comment gets one more in
101
+ // front, so the first child the compiler navigates to is the element.
102
+ node = create_fragment_from_html(content.startsWith('<!>') ? '<!>' + content : content, ns);
138
103
  if (!is_fragment) node = /** @type {Node} */ (get_first_child(node));
139
- t.node = node;
140
- t.node_svg = svg;
141
- t.node_mathml = mathml;
104
+ t.n = node;
105
+ t.ns = ns;
142
106
  }
143
107
 
144
108
  /** @type {DocumentFragment | Node} */
145
109
  var clone =
146
- t.use_import_node || is_firefox
110
+ (flags & TEMPLATE_USE_IMPORT_NODE) !== 0 || is_firefox
147
111
  ? document.importNode(/** @type {Node} */ (node), true)
148
112
  : /** @type {Node} */ (node).cloneNode(true);
149
113
 
@@ -155,15 +119,7 @@ function clone_template(t) {
155
119
  end = /** @type {Node} */ (/** @type {DocumentFragment} */ (clone).lastChild);
156
120
  }
157
121
 
158
- // assign_nodes, inline: every template clone records its range.
159
- var block = /** @type {Block} */ (active_block);
160
- var s = block.s;
161
- if (s === null) {
162
- block.s = { start, end };
163
- } else if (s.start === null) {
164
- s.start = start;
165
- s.end = end;
166
- }
122
+ assign_nodes(start, end);
167
123
 
168
124
  return clone;
169
125
  }
@@ -175,24 +131,15 @@ function clone_template(t) {
175
131
  * @param {number} [count] - Pre-calculated count of top-level nodes (for fragments). When provided, avoids runtime parsing.
176
132
  * @returns {() => Node}
177
133
  */
178
- export function template(content, flags, count = 1) {
134
+ export function template(content, flags = 0, count = 1) {
179
135
  var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0;
180
- var is_comment = content === '<!>';
181
136
  /** @type {TemplateState} */
182
- var t = {
183
- content,
184
- is_fragment,
185
- use_import_node: (flags & TEMPLATE_USE_IMPORT_NODE) !== 0,
186
- use_svg_namespace: (flags & TEMPLATE_SVG_NAMESPACE) !== 0,
187
- use_mathml_namespace: (flags & TEMPLATE_MATHML_NAMESPACE) !== 0,
188
- is_comment,
189
- has_start: !is_comment && !content.startsWith('<!>'),
190
- node: undefined,
191
- node_svg: false,
192
- node_mathml: false,
193
- };
194
-
195
- return () => (hydrating ? hydrate_template(is_fragment, count) : clone_template(t));
137
+ var t = { c: content, f: flags, n: undefined, ns: undefined };
138
+
139
+ return () =>
140
+ HYDRATION && hydrating
141
+ ? /** @type {import('./hydrate.js').HydrationRuntime} */ (H).t(is_fragment, count)
142
+ : clone_template(t);
196
143
  }
197
144
 
198
145
  /**
@@ -201,8 +148,8 @@ export function template(content, flags, count = 1) {
201
148
  * @param {Node} dom - The DOM node to append.
202
149
  */
203
150
  export function append(anchor, dom) {
204
- if (hydrating) {
205
- hydrate_append(anchor, dom);
151
+ if (HYDRATION && hydrating) {
152
+ /** @type {import('./hydrate.js').HydrationRuntime} */ (H).a(anchor, dom);
206
153
  return;
207
154
  }
208
155
  if (/** @type {AppendIntoAnchor} */ (anchor).into === true) {
@@ -217,7 +164,7 @@ export function append(anchor, dom) {
217
164
  }
218
165
 
219
166
  export function text(data = '') {
220
- if (hydrating) {
167
+ if (HYDRATION && hydrating) {
221
168
  assign_nodes(/** @type {Node} */ (hydrate_node), /** @type {Node} */ (hydrate_node));
222
169
  return /** @type {Node} */ (hydrate_node);
223
170
  }
@@ -225,27 +172,3 @@ export function text(data = '') {
225
172
  assign_nodes(node, node);
226
173
  return node;
227
174
  }
228
-
229
- /**
230
- * Create fragment with proper namespace using Svelte's wrapping approach
231
- * @param {string} content
232
- * @param {'svg' | 'math'} ns
233
- * @returns {DocumentFragment}
234
- */
235
- function from_namespace(content, ns = 'svg') {
236
- var wrapped = `<${ns}>${content}</${ns}>`;
237
-
238
- var elem = document.createElement('template');
239
- elem.innerHTML = wrapped;
240
- var fragment = elem.content;
241
-
242
- var root = /** @type {Element} */ (get_first_child(fragment));
243
- var result = document.createDocumentFragment();
244
-
245
- var first;
246
- while ((first = get_first_child(root))) {
247
- result.appendChild(/** @type {Node} */ (first));
248
- }
249
-
250
- return result;
251
- }
@@ -0,0 +1,356 @@
1
+ /** @import { Block, DeferredTrackedEntry, Tracked } from '#client' */
2
+
3
+ import { pause_block, pre_effect } from './blocks.js';
4
+ import {
5
+ ASYNC_DERIVED_READ_THROWN,
6
+ DIRECT_CHILD_BLOCK,
7
+ SUSPENSE_PENDING,
8
+ SUSPENSE_REJECTED,
9
+ TRACKED,
10
+ TRACKED_UPDATED,
11
+ TRY_BLOCK,
12
+ } from './constants.js';
13
+ import { hydrating, track_hash_reference } from './hydration.js';
14
+ import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
15
+ import {
16
+ active_block,
17
+ active_component,
18
+ active_dependency,
19
+ get,
20
+ is_mutating_allowed,
21
+ set_mutating_allowed,
22
+ set_pending_read_handler,
23
+ set_tracking,
24
+ tracked,
25
+ tracking,
26
+ update_tracked_value_clock,
27
+ } from './runtime.js';
28
+ import { is_ripple_object } from './utils.js';
29
+ import {
30
+ begin_boundary_request,
31
+ complete_boundary_request,
32
+ get_boundary_with_catch,
33
+ get_pending_boundary,
34
+ handle_boundary_error,
35
+ register_boundary_deferred,
36
+ register_boundary_paused_block,
37
+ replace_boundary_request,
38
+ } from './try.js';
39
+ import { get_async_track_result } from '../../../utils/async.js';
40
+ import { get_track_async_script_id } from '../../../utils/track-async-serialization.js';
41
+ import { revive } from './transport.js';
42
+ import {
43
+ pending_read_direct,
44
+ track_async_argument,
45
+ track_async_boundary,
46
+ track_async_orphan,
47
+ } from './errors.js';
48
+
49
+ /**
50
+ * `trackAsync()` and everything a pending async read sets in motion: boundary
51
+ * requests, paused blocks, deferred rejections. The runtime reaches it through
52
+ * a handler the first `trackAsync()` installs, so an application without one
53
+ * ships none of it.
54
+ */
55
+
56
+ var installed = false;
57
+
58
+ /**
59
+ * Complete all deferred boundary requests registered on a tracked value.
60
+ * @param {Tracked} t
61
+ * @param {boolean} [show_resolved=true]
62
+ */
63
+ function complete_deferred_boundaries(t, show_resolved = true) {
64
+ if (t.d !== null) {
65
+ for (var i = 0; i < t.d.length; i++) {
66
+ var entry = t.d[i];
67
+ complete_boundary_request(entry.b, entry.r, show_resolved);
68
+ }
69
+ t.d = null;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * A block's run read a pending async value (it threw
75
+ * `ASYNC_DERIVED_READ_THROWN`, and the read was registered as a dependency):
76
+ * the block pauses under its pending boundary until the value settles, and
77
+ * a read straight from a component or boundary body is an error.
78
+ * @param {Block} block
79
+ */
80
+ function handle_pending_read(block) {
81
+ var is_component_direct = active_component?.b === block;
82
+ var is_try_fn_block =
83
+ block.p !== null && (block.p.f & TRY_BLOCK) !== 0 && (block.f & DIRECT_CHILD_BLOCK) !== 0;
84
+
85
+ if (is_component_direct || is_try_fn_block) {
86
+ pending_read_direct(is_component_direct);
87
+ }
88
+
89
+ var boundary = get_pending_boundary(block);
90
+ if (boundary === null) {
91
+ return;
92
+ }
93
+ pause_block(block);
94
+ register_boundary_paused_block(boundary, block);
95
+
96
+ // Register deferred boundary completions for async tracked deps.
97
+ // This handles the case where a child boundary reads a tracked value
98
+ // whose resolution is managed by a different (parent) boundary.
99
+ var dep = block.d;
100
+ while (dep !== null) {
101
+ var dep_tracked = /** @type {Tracked} */ (dep.t);
102
+ if (
103
+ (dep_tracked.__v === SUSPENSE_PENDING || dep_tracked.__v === SUSPENSE_REJECTED) &&
104
+ (dep_tracked.f & TRACKED) !== 0
105
+ ) {
106
+ var deferred_req = begin_boundary_request(boundary);
107
+ var entry = /** @type {DeferredTrackedEntry} */ ({ b: boundary, r: deferred_req });
108
+ if (dep_tracked.d === null) {
109
+ dep_tracked.d = [entry];
110
+ } else {
111
+ dep_tracked.d.push(entry);
112
+ }
113
+ }
114
+ dep = dep.n;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * @param {any} fn
120
+ * @param {Block} b
121
+ * @param {string} hash - Unique hash for SSR serialization/hydration
122
+ * @returns {Tracked | void}
123
+ */
124
+ export function track_async(fn, b, hash) {
125
+ if (!installed) {
126
+ installed = true;
127
+ set_pending_read_handler(handle_pending_read);
128
+ }
129
+
130
+ if (is_ripple_object(fn)) {
131
+ return fn;
132
+ }
133
+
134
+ var target_block = b || active_block;
135
+ if (target_block === null) {
136
+ track_async_orphan();
137
+ }
138
+
139
+ if (typeof fn !== 'function') {
140
+ track_async_argument();
141
+ }
142
+
143
+ // During hydration, attempt to read serialized data from SSR
144
+ var had_hydration_data = false;
145
+ var hydration_value;
146
+ /** @type {string[] | undefined} */
147
+ var hydration_deps;
148
+
149
+ if (HYDRATION && hydrating) {
150
+ var script_id = get_track_async_script_id(hash);
151
+ var script_el = document.getElementById(script_id);
152
+ if (script_el) {
153
+ var envelope = JSON.parse(/** @type {string} */ (script_el.textContent));
154
+ script_el.remove();
155
+
156
+ if (envelope.ok) {
157
+ had_hydration_data = true;
158
+ hydration_value =
159
+ envelope.payload === undefined ? envelope.value : revive(envelope.payload);
160
+ hydration_deps = envelope.deps;
161
+ } else {
162
+ // trigger the catch block
163
+ throw new Error(envelope.error?.message ?? 'Unknown server error');
164
+ }
165
+ }
166
+ }
167
+
168
+ var t = tracked(had_hydration_data ? hydration_value : SUSPENSE_PENDING, target_block, hash);
169
+
170
+ // Capture the call-site block for boundary lookups. target_block is the
171
+ // component's block (passed by compiler), but the actual try/pending/catch
172
+ // boundary is an ancestor of active_block (the block executing trackAsync).
173
+ var call_site_block = /** @type {Block} */ (active_block);
174
+
175
+ var version = 0;
176
+ /** @type {AbortController | null} */
177
+ var abort_controller = null;
178
+ var request_id = 0;
179
+ /** @type {Block | null} */
180
+ var boundary = null;
181
+
182
+ // TODO: decide if instead of insisting on pending, we create our own boundary
183
+ // we currently require a pending block upstream but we could also
184
+ // create a try/pending/catch boundary at mount and hydration like
185
+ // we do on the server so that there is always a boundary present.
186
+ // It can handle global pending when none were provided.
187
+ // Not sure about the catch boundary because if none were provided,
188
+ // the whole app for any error will be unmounted with the catch block rendered
189
+
190
+ // Find boundary from the call-site block.
191
+ boundary = get_pending_boundary(active_block);
192
+ if (boundary === null) {
193
+ track_async_boundary();
194
+ }
195
+
196
+ // If we hydrated with resolved data, the SSR already completed this request.
197
+ // Otherwise mark a pending request on the boundary for the client-side run.
198
+ if (!had_hydration_data) {
199
+ request_id = begin_boundary_request(boundary);
200
+ }
201
+
202
+ pre_effect(() => {
203
+ if (had_hydration_data) {
204
+ // First run after hydration: skip fn() entirely (the SSR already
205
+ // produced the resolved value) and instead register the direct
206
+ // dependencies from the serialized deps list so future dep changes
207
+ // trigger a re-run via the normal async path.
208
+ had_hydration_data = false;
209
+ if (hydration_deps !== undefined) {
210
+ for (var i = 0; i < hydration_deps.length; i++) {
211
+ var dep_ref = track_hash_reference.get(hydration_deps[i]);
212
+ if (dep_ref !== undefined) {
213
+ get(dep_ref);
214
+ }
215
+ }
216
+ }
217
+ return;
218
+ }
219
+
220
+ var current_version = ++version;
221
+
222
+ // Abort previous in-flight request
223
+ if (abort_controller !== null && abort_controller.signal.aborted === false) {
224
+ abort_controller.abort(TRACKED_UPDATED);
225
+ }
226
+ abort_controller = null;
227
+
228
+ // Manage boundary request: replace if in-flight, or begin new if previous completed
229
+ if (request_id > 0 && boundary !== null) {
230
+ request_id = replace_boundary_request(boundary, request_id);
231
+ } else if (boundary !== null) {
232
+ request_id = begin_boundary_request(boundary);
233
+ }
234
+
235
+ // Set to pending before calling fn() in case it's sync.
236
+ if (t.__v !== SUSPENSE_PENDING) {
237
+ update_tracked_value_clock(t, SUSPENSE_PENDING);
238
+ }
239
+
240
+ // Temporarily allow mutations so set() doesn't throw inside the pre-effect
241
+ var previous_is_mutating_allowed = is_mutating_allowed;
242
+ set_mutating_allowed(true);
243
+
244
+ var result;
245
+ try {
246
+ result = fn();
247
+ } catch (e) {
248
+ set_mutating_allowed(previous_is_mutating_allowed);
249
+ if (e === ASYNC_DERIVED_READ_THROWN) {
250
+ // A dependency is still pending or rejected (e.g. chained trackAsync).
251
+ // Check if any dependency is rejected — if so, propagate rejection.
252
+ var dep = active_dependency;
253
+ while (dep !== null) {
254
+ if (dep.t.__v === SUSPENSE_REJECTED) {
255
+ update_tracked_value_clock(t, SUSPENSE_REJECTED);
256
+ complete_deferred_boundaries(t, false);
257
+ if (request_id > 0 && boundary !== null) {
258
+ complete_boundary_request(boundary, request_id, false);
259
+ request_id = 0;
260
+ }
261
+ return;
262
+ }
263
+ dep = dep.n;
264
+ }
265
+ // Dependencies are pending, not rejected — register deferred
266
+ // rejection so that if the boundary goes to catch mode, this
267
+ // tracked value is also set to REJECTED.
268
+ if (request_id > 0 && boundary !== null) {
269
+ register_boundary_deferred(boundary, request_id, () => {
270
+ update_tracked_value_clock(t, SUSPENSE_REJECTED);
271
+ });
272
+ }
273
+ return;
274
+ }
275
+ throw e;
276
+ }
277
+ set_mutating_allowed(previous_is_mutating_allowed);
278
+
279
+ // Check if the result is async
280
+ var previous_tracking = tracking;
281
+ set_tracking(false);
282
+ var async_result = get_async_track_result(result);
283
+ set_tracking(previous_tracking);
284
+
285
+ if (async_result === null) {
286
+ // Sync result
287
+ update_tracked_value_clock(t, result);
288
+ if (request_id > 0 && boundary !== null) {
289
+ complete_boundary_request(boundary, request_id);
290
+ request_id = 0;
291
+ }
292
+ return;
293
+ }
294
+
295
+ // Capture per-invocation so async closures (rejection handler, teardown)
296
+ // have a stable reference. The shared abort_controller is only read
297
+ // synchronously at the top of the pre_effect to abort the previous request.
298
+ var current_abort_controller = async_result.abort_controller;
299
+ abort_controller = current_abort_controller;
300
+
301
+ async_result.promise.then(
302
+ (resolved) => {
303
+ if (current_version !== version) {
304
+ // stale
305
+ return;
306
+ }
307
+ update_tracked_value_clock(t, resolved);
308
+ complete_deferred_boundaries(t);
309
+ if (request_id > 0 && boundary !== null) {
310
+ complete_boundary_request(boundary, request_id);
311
+ request_id = 0;
312
+ }
313
+ },
314
+ (error) => {
315
+ if (current_version !== version) return; // stale
316
+
317
+ var is_internal_abort =
318
+ error === TRACKED_UPDATED || current_abort_controller?.signal?.reason === TRACKED_UPDATED;
319
+ if (is_internal_abort) {
320
+ // Internal abort (superseded by a new request) — don't set rejected
321
+ if (request_id > 0 && boundary !== null) {
322
+ complete_boundary_request(boundary, request_id, false);
323
+ request_id = 0;
324
+ }
325
+ complete_deferred_boundaries(t, false);
326
+ return;
327
+ }
328
+
329
+ update_tracked_value_clock(t, SUSPENSE_REJECTED);
330
+ complete_deferred_boundaries(t, false);
331
+
332
+ // Route error to catch boundary
333
+ var boundary_with_catch = get_boundary_with_catch(call_site_block);
334
+ if (boundary_with_catch !== null) {
335
+ handle_boundary_error(boundary_with_catch, error);
336
+ }
337
+
338
+ if (request_id > 0 && boundary !== null) {
339
+ var should_show_resolved =
340
+ boundary_with_catch === boundary || boundary === null ? false : true;
341
+ complete_boundary_request(boundary, request_id, should_show_resolved);
342
+ request_id = 0;
343
+ }
344
+ },
345
+ );
346
+
347
+ return () => {
348
+ // Teardown: abort in-flight request when block is destroyed
349
+ if (current_abort_controller !== null && current_abort_controller.signal.aborted === false) {
350
+ current_abort_controller.abort(TRACKED_UPDATED);
351
+ }
352
+ };
353
+ });
354
+
355
+ return t;
356
+ }