svelte 5.43.15 → 5.44.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.
- package/compiler/index.js +1 -1
- package/package.json +2 -1
- package/src/index-client.js +1 -0
- package/src/index-server.js +2 -0
- package/src/internal/client/dev/inspect.js +2 -2
- package/src/internal/client/dev/tracing.js +0 -57
- package/src/internal/client/errors.js +21 -4
- package/src/internal/client/hydratable.js +33 -0
- package/src/internal/client/proxy.js +3 -2
- package/src/internal/client/reactivity/batch.js +1 -1
- package/src/internal/client/reactivity/deriveds.js +2 -2
- package/src/internal/client/reactivity/sources.js +4 -3
- package/src/internal/client/runtime.js +4 -3
- package/src/internal/client/warnings.js +12 -0
- package/src/internal/server/dev.js +10 -0
- package/src/internal/server/errors.js +66 -0
- package/src/internal/server/hydratable.js +136 -0
- package/src/internal/server/render-context.js +74 -0
- package/src/internal/server/renderer.js +76 -10
- package/src/internal/server/warnings.js +24 -1
- package/src/internal/shared/dev.js +65 -0
- package/src/internal/shared/errors.js +17 -0
- package/src/internal/shared/utils.js +1 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +4 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "svelte",
|
|
3
3
|
"description": "Cybernetically enhanced web apps",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "5.
|
|
5
|
+
"version": "5.44.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"types": "./types/index.d.ts",
|
|
8
8
|
"engines": {
|
|
@@ -163,6 +163,7 @@
|
|
|
163
163
|
"aria-query": "^5.3.1",
|
|
164
164
|
"axobject-query": "^4.1.0",
|
|
165
165
|
"clsx": "^2.1.1",
|
|
166
|
+
"devalue": "^5.5.0",
|
|
166
167
|
"esm-env": "^1.2.1",
|
|
167
168
|
"esrap": "^2.1.0",
|
|
168
169
|
"is-reference": "^3.0.3",
|
package/src/index-client.js
CHANGED
|
@@ -249,6 +249,7 @@ export {
|
|
|
249
249
|
hasContext,
|
|
250
250
|
setContext
|
|
251
251
|
} from './internal/client/context.js';
|
|
252
|
+
export { hydratable } from './internal/client/hydratable.js';
|
|
252
253
|
export { hydrate, mount, unmount } from './internal/client/render.js';
|
|
253
254
|
export { tick, untrack, settled } from './internal/client/runtime.js';
|
|
254
255
|
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';
|
package/src/index-server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { UNINITIALIZED } from '../../../constants.js';
|
|
|
2
2
|
import { snapshot } from '../../shared/clone.js';
|
|
3
3
|
import { eager_effect, render_effect, validate_effect } from '../reactivity/effects.js';
|
|
4
4
|
import { untrack } from '../runtime.js';
|
|
5
|
-
import {
|
|
5
|
+
import { get_error } from '../../shared/dev.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @param {() => any[]} get_value
|
|
@@ -33,7 +33,7 @@ export function inspect(get_value, inspector, show_stack = false) {
|
|
|
33
33
|
inspector(...snap);
|
|
34
34
|
|
|
35
35
|
if (!initial) {
|
|
36
|
-
const stack =
|
|
36
|
+
const stack = get_error('$inspect(...)');
|
|
37
37
|
// eslint-disable-next-line no-console
|
|
38
38
|
|
|
39
39
|
if (stack) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/** @import { Derived, Reaction, Value } from '#client' */
|
|
2
2
|
import { UNINITIALIZED } from '../../../constants.js';
|
|
3
3
|
import { snapshot } from '../../shared/clone.js';
|
|
4
|
-
import { define_property } from '../../shared/utils.js';
|
|
5
4
|
import { DERIVED, ASYNC, PROXY_PATH_SYMBOL, STATE_SYMBOL } from '#client/constants';
|
|
6
5
|
import { effect_tracking } from '../reactivity/effects.js';
|
|
7
6
|
import { active_reaction, untrack } from '../runtime.js';
|
|
@@ -131,62 +130,6 @@ export function trace(label, fn) {
|
|
|
131
130
|
}
|
|
132
131
|
}
|
|
133
132
|
|
|
134
|
-
/**
|
|
135
|
-
* @param {string} label
|
|
136
|
-
* @returns {Error & { stack: string } | null}
|
|
137
|
-
*/
|
|
138
|
-
export function get_stack(label) {
|
|
139
|
-
// @ts-ignore stackTraceLimit doesn't exist everywhere
|
|
140
|
-
const limit = Error.stackTraceLimit;
|
|
141
|
-
|
|
142
|
-
// @ts-ignore
|
|
143
|
-
Error.stackTraceLimit = Infinity;
|
|
144
|
-
let error = Error();
|
|
145
|
-
|
|
146
|
-
// @ts-ignore
|
|
147
|
-
Error.stackTraceLimit = limit;
|
|
148
|
-
|
|
149
|
-
const stack = error.stack;
|
|
150
|
-
|
|
151
|
-
if (!stack) return null;
|
|
152
|
-
|
|
153
|
-
const lines = stack.split('\n');
|
|
154
|
-
const new_lines = ['\n'];
|
|
155
|
-
|
|
156
|
-
for (let i = 0; i < lines.length; i++) {
|
|
157
|
-
const line = lines[i];
|
|
158
|
-
const posixified = line.replaceAll('\\', '/');
|
|
159
|
-
|
|
160
|
-
if (line === 'Error') {
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (line.includes('validate_each_keys')) {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (posixified.includes('svelte/src/internal') || posixified.includes('node_modules/.vite')) {
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
new_lines.push(line);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (new_lines.length === 1) {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
define_property(error, 'stack', {
|
|
180
|
-
value: new_lines.join('\n')
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
define_property(error, 'name', {
|
|
184
|
-
value: label
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
return /** @type {Error & { stack: string }} */ (error);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
133
|
/**
|
|
191
134
|
* @param {Value} source
|
|
192
135
|
* @param {string} label
|
|
@@ -230,18 +230,18 @@ export function effect_update_depth_exceeded() {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
/**
|
|
233
|
-
* Cannot use `
|
|
233
|
+
* Cannot use `flushSync` inside an effect
|
|
234
234
|
* @returns {never}
|
|
235
235
|
*/
|
|
236
|
-
export function
|
|
236
|
+
export function flush_sync_in_effect() {
|
|
237
237
|
if (DEV) {
|
|
238
|
-
const error = new Error(`
|
|
238
|
+
const error = new Error(`flush_sync_in_effect\nCannot use \`flushSync\` inside an effect\nhttps://svelte.dev/e/flush_sync_in_effect`);
|
|
239
239
|
|
|
240
240
|
error.name = 'Svelte error';
|
|
241
241
|
|
|
242
242
|
throw error;
|
|
243
243
|
} else {
|
|
244
|
-
throw new Error(`https://svelte.dev/e/
|
|
244
|
+
throw new Error(`https://svelte.dev/e/flush_sync_in_effect`);
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
@@ -293,6 +293,23 @@ export function get_abort_signal_outside_reaction() {
|
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Expected to find a hydratable with key `%key%` during hydration, but did not.
|
|
298
|
+
* @param {string} key
|
|
299
|
+
* @returns {never}
|
|
300
|
+
*/
|
|
301
|
+
export function hydratable_missing_but_required(key) {
|
|
302
|
+
if (DEV) {
|
|
303
|
+
const error = new Error(`hydratable_missing_but_required\nExpected to find a hydratable with key \`${key}\` during hydration, but did not.\nhttps://svelte.dev/e/hydratable_missing_but_required`);
|
|
304
|
+
|
|
305
|
+
error.name = 'Svelte error';
|
|
306
|
+
|
|
307
|
+
throw error;
|
|
308
|
+
} else {
|
|
309
|
+
throw new Error(`https://svelte.dev/e/hydratable_missing_but_required`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
296
313
|
/**
|
|
297
314
|
* Failed to hydrate the application
|
|
298
315
|
* @returns {never}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { async_mode_flag } from '../flags/index.js';
|
|
2
|
+
import { hydrating } from './dom/hydration.js';
|
|
3
|
+
import * as w from './warnings.js';
|
|
4
|
+
import * as e from './errors.js';
|
|
5
|
+
import { DEV } from 'esm-env';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @template T
|
|
9
|
+
* @param {string} key
|
|
10
|
+
* @param {() => T} fn
|
|
11
|
+
* @returns {T}
|
|
12
|
+
*/
|
|
13
|
+
export function hydratable(key, fn) {
|
|
14
|
+
if (!async_mode_flag) {
|
|
15
|
+
e.experimental_async_required('hydratable');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (hydrating) {
|
|
19
|
+
const store = window.__svelte?.h;
|
|
20
|
+
|
|
21
|
+
if (store?.has(key)) {
|
|
22
|
+
return /** @type {T} */ (store.get(key));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (DEV) {
|
|
26
|
+
e.hydratable_missing_but_required(key);
|
|
27
|
+
} else {
|
|
28
|
+
w.hydratable_missing_but_expected(key);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return fn();
|
|
33
|
+
}
|
|
@@ -25,7 +25,8 @@ import {
|
|
|
25
25
|
import { PROXY_PATH_SYMBOL, STATE_SYMBOL } from '#client/constants';
|
|
26
26
|
import { UNINITIALIZED } from '../../constants.js';
|
|
27
27
|
import * as e from './errors.js';
|
|
28
|
-
import {
|
|
28
|
+
import { tag } from './dev/tracing.js';
|
|
29
|
+
import { get_error } from '../shared/dev.js';
|
|
29
30
|
import { tracing_mode_flag } from '../flags/index.js';
|
|
30
31
|
|
|
31
32
|
// TODO move all regexes into shared module?
|
|
@@ -53,7 +54,7 @@ export function proxy(value) {
|
|
|
53
54
|
var is_proxied_array = is_array(value);
|
|
54
55
|
var version = source(0);
|
|
55
56
|
|
|
56
|
-
var stack = DEV && tracing_mode_flag ?
|
|
57
|
+
var stack = DEV && tracing_mode_flag ? get_error('created at') : null;
|
|
57
58
|
var parent_version = update_version;
|
|
58
59
|
|
|
59
60
|
/**
|
|
@@ -29,7 +29,7 @@ import * as e from '../errors.js';
|
|
|
29
29
|
import * as w from '../warnings.js';
|
|
30
30
|
import { async_effect, destroy_effect, effect_tracking, teardown } from './effects.js';
|
|
31
31
|
import { eager_effects, internal_set, set_eager_effects, source } from './sources.js';
|
|
32
|
-
import {
|
|
32
|
+
import { get_error } from '../../shared/dev.js';
|
|
33
33
|
import { async_mode_flag, tracing_mode_flag } from '../../flags/index.js';
|
|
34
34
|
import { Boundary } from '../dom/blocks/boundary.js';
|
|
35
35
|
import { component_context } from '../context.js';
|
|
@@ -84,7 +84,7 @@ export function derived(fn) {
|
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
if (DEV && tracing_mode_flag) {
|
|
87
|
-
signal.created =
|
|
87
|
+
signal.created = get_error('created at');
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
return signal;
|
|
@@ -34,7 +34,8 @@ import {
|
|
|
34
34
|
} from '#client/constants';
|
|
35
35
|
import * as e from '../errors.js';
|
|
36
36
|
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
|
|
37
|
-
import {
|
|
37
|
+
import { tag_proxy } from '../dev/tracing.js';
|
|
38
|
+
import { get_error } from '../../shared/dev.js';
|
|
38
39
|
import { component_context, is_runes } from '../context.js';
|
|
39
40
|
import { Batch, batch_values, eager_block_effects, schedule_effect } from './batch.js';
|
|
40
41
|
import { proxy } from '../proxy.js';
|
|
@@ -78,7 +79,7 @@ export function source(v, stack) {
|
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
if (DEV && tracing_mode_flag) {
|
|
81
|
-
signal.created = stack ??
|
|
82
|
+
signal.created = stack ?? get_error('created at');
|
|
82
83
|
signal.updated = null;
|
|
83
84
|
signal.set_during_effect = false;
|
|
84
85
|
signal.trace = null;
|
|
@@ -196,7 +197,7 @@ export function internal_set(source, value) {
|
|
|
196
197
|
source.updated.set('', { error: /** @type {any} */ (null), count });
|
|
197
198
|
|
|
198
199
|
if (tracing_mode_flag || count > 5) {
|
|
199
|
-
const error =
|
|
200
|
+
const error = get_error('updated at');
|
|
200
201
|
|
|
201
202
|
if (error !== null) {
|
|
202
203
|
let entry = source.updated.get(error.stack);
|
|
@@ -33,7 +33,8 @@ import {
|
|
|
33
33
|
update_derived
|
|
34
34
|
} from './reactivity/deriveds.js';
|
|
35
35
|
import { async_mode_flag, tracing_mode_flag } from '../flags/index.js';
|
|
36
|
-
import { tracing_expressions
|
|
36
|
+
import { tracing_expressions } from './dev/tracing.js';
|
|
37
|
+
import { get_error } from '../shared/dev.js';
|
|
37
38
|
import {
|
|
38
39
|
component_context,
|
|
39
40
|
dev_current_component_function,
|
|
@@ -554,7 +555,7 @@ export function get(signal) {
|
|
|
554
555
|
// if (!tracking && !untracking && !was_read) {
|
|
555
556
|
// w.await_reactivity_loss(/** @type {string} */ (signal.label));
|
|
556
557
|
|
|
557
|
-
// var trace =
|
|
558
|
+
// var trace = get_error('traced at');
|
|
558
559
|
// // eslint-disable-next-line no-console
|
|
559
560
|
// if (trace) console.warn(trace);
|
|
560
561
|
// }
|
|
@@ -573,7 +574,7 @@ export function get(signal) {
|
|
|
573
574
|
if (signal.trace) {
|
|
574
575
|
signal.trace();
|
|
575
576
|
} else {
|
|
576
|
-
var trace =
|
|
577
|
+
var trace = get_error('traced at');
|
|
577
578
|
|
|
578
579
|
if (trace) {
|
|
579
580
|
var entry = tracing_expressions.entries.get(signal);
|
|
@@ -87,6 +87,18 @@ export function event_handler_invalid(handler, suggestion) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Expected to find a hydratable with key `%key%` during hydration, but did not.
|
|
92
|
+
* @param {string} key
|
|
93
|
+
*/
|
|
94
|
+
export function hydratable_missing_but_expected(key) {
|
|
95
|
+
if (DEV) {
|
|
96
|
+
console.warn(`%c[svelte] hydratable_missing_but_expected\n%cExpected to find a hydratable with key \`${key}\` during hydration, but did not.\nhttps://svelte.dev/e/hydratable_missing_but_expected`, bold, normal);
|
|
97
|
+
} else {
|
|
98
|
+
console.warn(`https://svelte.dev/e/hydratable_missing_but_expected`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
90
102
|
/**
|
|
91
103
|
* The `%attribute%` attribute on `%html%` changed its value between server and client renders. The client value, `%value%`, will be ignored in favour of the server value
|
|
92
104
|
* @param {string} attribute
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
is_tag_valid_with_ancestor,
|
|
5
5
|
is_tag_valid_with_parent
|
|
6
6
|
} from '../../html-tree-validation.js';
|
|
7
|
+
import { get_stack } from '../shared/dev.js';
|
|
7
8
|
import { set_ssr_context, ssr_context } from './context.js';
|
|
8
9
|
import * as e from './errors.js';
|
|
9
10
|
import { Renderer } from './renderer.js';
|
|
@@ -98,3 +99,12 @@ export function validate_snippet_args(renderer) {
|
|
|
98
99
|
e.invalid_snippet_arguments();
|
|
99
100
|
}
|
|
100
101
|
}
|
|
102
|
+
|
|
103
|
+
export function get_user_code_location() {
|
|
104
|
+
const stack = get_stack();
|
|
105
|
+
|
|
106
|
+
return stack
|
|
107
|
+
.filter((line) => line.trim().startsWith('at '))
|
|
108
|
+
.map((line) => line.replace(/\((.*):\d+:\d+\)$/, (_, file) => `(${file})`))
|
|
109
|
+
.join('\n');
|
|
110
|
+
}
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
export * from '../shared/errors.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* The node API `AsyncLocalStorage` is not available, but is required to use async server rendering.
|
|
7
|
+
* @returns {never}
|
|
8
|
+
*/
|
|
9
|
+
export function async_local_storage_unavailable() {
|
|
10
|
+
const error = new Error(`async_local_storage_unavailable\nThe node API \`AsyncLocalStorage\` is not available, but is required to use async server rendering.\nhttps://svelte.dev/e/async_local_storage_unavailable`);
|
|
11
|
+
|
|
12
|
+
error.name = 'Svelte error';
|
|
13
|
+
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
/**
|
|
6
18
|
* Encountered asynchronous work while rendering synchronously.
|
|
7
19
|
* @returns {never}
|
|
@@ -26,6 +38,48 @@ export function html_deprecated() {
|
|
|
26
38
|
throw error;
|
|
27
39
|
}
|
|
28
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Attempted to set `hydratable` with key `%key%` twice with different values.
|
|
43
|
+
*
|
|
44
|
+
* %stack%
|
|
45
|
+
* @param {string} key
|
|
46
|
+
* @param {string} stack
|
|
47
|
+
* @returns {never}
|
|
48
|
+
*/
|
|
49
|
+
export function hydratable_clobbering(key, stack) {
|
|
50
|
+
const error = new Error(`hydratable_clobbering\nAttempted to set \`hydratable\` with key \`${key}\` twice with different values.
|
|
51
|
+
|
|
52
|
+
${stack}\nhttps://svelte.dev/e/hydratable_clobbering`);
|
|
53
|
+
|
|
54
|
+
error.name = 'Svelte error';
|
|
55
|
+
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Failed to serialize `hydratable` data for key `%key%`.
|
|
61
|
+
*
|
|
62
|
+
* `hydratable` can serialize anything [`uneval` from `devalue`](https://npmjs.com/package/uneval) can, plus Promises.
|
|
63
|
+
*
|
|
64
|
+
* Cause:
|
|
65
|
+
* %stack%
|
|
66
|
+
* @param {string} key
|
|
67
|
+
* @param {string} stack
|
|
68
|
+
* @returns {never}
|
|
69
|
+
*/
|
|
70
|
+
export function hydratable_serialization_failed(key, stack) {
|
|
71
|
+
const error = new Error(`hydratable_serialization_failed\nFailed to serialize \`hydratable\` data for key \`${key}\`.
|
|
72
|
+
|
|
73
|
+
\`hydratable\` can serialize anything [\`uneval\` from \`devalue\`](https://npmjs.com/package/uneval) can, plus Promises.
|
|
74
|
+
|
|
75
|
+
Cause:
|
|
76
|
+
${stack}\nhttps://svelte.dev/e/hydratable_serialization_failed`);
|
|
77
|
+
|
|
78
|
+
error.name = 'Svelte error';
|
|
79
|
+
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
|
|
29
83
|
/**
|
|
30
84
|
* `%name%(...)` is not available on the server
|
|
31
85
|
* @param {string} name
|
|
@@ -36,5 +90,17 @@ export function lifecycle_function_unavailable(name) {
|
|
|
36
90
|
|
|
37
91
|
error.name = 'Svelte error';
|
|
38
92
|
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Could not resolve `render` context.
|
|
98
|
+
* @returns {never}
|
|
99
|
+
*/
|
|
100
|
+
export function server_context_required() {
|
|
101
|
+
const error = new Error(`server_context_required\nCould not resolve \`render\` context.\nhttps://svelte.dev/e/server_context_required`);
|
|
102
|
+
|
|
103
|
+
error.name = 'Svelte error';
|
|
104
|
+
|
|
39
105
|
throw error;
|
|
40
106
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/** @import { HydratableLookupEntry } from '#server' */
|
|
2
|
+
import { async_mode_flag } from '../flags/index.js';
|
|
3
|
+
import { get_render_context } from './render-context.js';
|
|
4
|
+
import * as e from './errors.js';
|
|
5
|
+
import * as devalue from 'devalue';
|
|
6
|
+
import { get_stack } from '../shared/dev.js';
|
|
7
|
+
import { DEV } from 'esm-env';
|
|
8
|
+
import { get_user_code_location } from './dev.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @template T
|
|
12
|
+
* @param {string} key
|
|
13
|
+
* @param {() => T} fn
|
|
14
|
+
* @returns {T}
|
|
15
|
+
*/
|
|
16
|
+
export function hydratable(key, fn) {
|
|
17
|
+
if (!async_mode_flag) {
|
|
18
|
+
e.experimental_async_required('hydratable');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { hydratable } = get_render_context();
|
|
22
|
+
|
|
23
|
+
let entry = hydratable.lookup.get(key);
|
|
24
|
+
|
|
25
|
+
if (entry !== undefined) {
|
|
26
|
+
if (DEV) {
|
|
27
|
+
const comparison = compare(key, entry, encode(key, fn()));
|
|
28
|
+
comparison.catch(() => {});
|
|
29
|
+
hydratable.comparisons.push(comparison);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return /** @type {T} */ (entry.value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const value = fn();
|
|
36
|
+
|
|
37
|
+
entry = encode(key, value, hydratable.unresolved_promises);
|
|
38
|
+
hydratable.lookup.set(key, entry);
|
|
39
|
+
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} key
|
|
45
|
+
* @param {any} value
|
|
46
|
+
* @param {Map<Promise<any>, string>} [unresolved]
|
|
47
|
+
*/
|
|
48
|
+
function encode(key, value, unresolved) {
|
|
49
|
+
/** @type {HydratableLookupEntry} */
|
|
50
|
+
const entry = { value, serialized: '' };
|
|
51
|
+
|
|
52
|
+
if (DEV) {
|
|
53
|
+
entry.stack = get_user_code_location();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let uid = 1;
|
|
57
|
+
|
|
58
|
+
entry.serialized = devalue.uneval(entry.value, (value, uneval) => {
|
|
59
|
+
if (value instanceof Promise) {
|
|
60
|
+
const p = value
|
|
61
|
+
.then((v) => `r(${uneval(v)})`)
|
|
62
|
+
.catch((devalue_error) =>
|
|
63
|
+
e.hydratable_serialization_failed(
|
|
64
|
+
key,
|
|
65
|
+
serialization_stack(entry.stack, devalue_error?.stack)
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// prevent unhandled rejections from crashing the server
|
|
70
|
+
p.catch(() => {});
|
|
71
|
+
|
|
72
|
+
// track which promises are still resolving when render is complete
|
|
73
|
+
unresolved?.set(p, key);
|
|
74
|
+
p.finally(() => unresolved?.delete(p));
|
|
75
|
+
|
|
76
|
+
// we serialize promises as `"${i}"`, because it's impossible for that string
|
|
77
|
+
// to occur 'naturally' (since the quote marks would have to be escaped)
|
|
78
|
+
const placeholder = `"${uid++}"`;
|
|
79
|
+
|
|
80
|
+
(entry.promises ??= []).push(
|
|
81
|
+
p.then((s) => {
|
|
82
|
+
entry.serialized = entry.serialized.replace(placeholder, s);
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return placeholder;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return entry;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {string} key
|
|
95
|
+
* @param {HydratableLookupEntry} a
|
|
96
|
+
* @param {HydratableLookupEntry} b
|
|
97
|
+
*/
|
|
98
|
+
async function compare(key, a, b) {
|
|
99
|
+
// note: these need to be loops (as opposed to Promise.all) because
|
|
100
|
+
// additional promises can get pushed to them while we're awaiting
|
|
101
|
+
// an earlier one
|
|
102
|
+
for (const p of a?.promises ?? []) {
|
|
103
|
+
await p;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const p of b?.promises ?? []) {
|
|
107
|
+
await p;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (a.serialized !== b.serialized) {
|
|
111
|
+
const a_stack = /** @type {string} */ (a.stack);
|
|
112
|
+
const b_stack = /** @type {string} */ (b.stack);
|
|
113
|
+
|
|
114
|
+
const stack =
|
|
115
|
+
a_stack === b_stack
|
|
116
|
+
? `Occurred at:\n${a_stack}`
|
|
117
|
+
: `First occurrence at:\n${a_stack}\n\nSecond occurrence at:\n${b_stack}`;
|
|
118
|
+
|
|
119
|
+
e.hydratable_clobbering(key, stack);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {string | undefined} root_stack
|
|
125
|
+
* @param {string | undefined} uneval_stack
|
|
126
|
+
*/
|
|
127
|
+
function serialization_stack(root_stack, uneval_stack) {
|
|
128
|
+
let out = '';
|
|
129
|
+
if (root_stack) {
|
|
130
|
+
out += root_stack + '\n';
|
|
131
|
+
}
|
|
132
|
+
if (uneval_stack) {
|
|
133
|
+
out += 'Caused by:\n' + uneval_stack + '\n';
|
|
134
|
+
}
|
|
135
|
+
return out || '<missing stack trace>';
|
|
136
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// @ts-ignore -- we don't include node types in the production build
|
|
2
|
+
/** @import { AsyncLocalStorage } from 'node:async_hooks' */
|
|
3
|
+
/** @import { RenderContext } from '#server' */
|
|
4
|
+
|
|
5
|
+
import { deferred } from '../shared/utils.js';
|
|
6
|
+
import * as e from './errors.js';
|
|
7
|
+
|
|
8
|
+
/** @type {Promise<void> | null} */
|
|
9
|
+
let current_render = null;
|
|
10
|
+
|
|
11
|
+
/** @type {RenderContext | null} */
|
|
12
|
+
let context = null;
|
|
13
|
+
|
|
14
|
+
/** @returns {RenderContext} */
|
|
15
|
+
export function get_render_context() {
|
|
16
|
+
const store = context ?? als?.getStore();
|
|
17
|
+
|
|
18
|
+
if (!store) {
|
|
19
|
+
e.server_context_required();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return store;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @template T
|
|
27
|
+
* @param {() => Promise<T>} fn
|
|
28
|
+
* @returns {Promise<T>}
|
|
29
|
+
*/
|
|
30
|
+
export async function with_render_context(fn) {
|
|
31
|
+
context = {
|
|
32
|
+
hydratable: {
|
|
33
|
+
lookup: new Map(),
|
|
34
|
+
comparisons: [],
|
|
35
|
+
unresolved_promises: new Map()
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (in_webcontainer()) {
|
|
40
|
+
const { promise, resolve } = deferred();
|
|
41
|
+
const previous_render = current_render;
|
|
42
|
+
current_render = promise;
|
|
43
|
+
await previous_render;
|
|
44
|
+
return fn().finally(resolve);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
if (als === null) {
|
|
49
|
+
e.async_local_storage_unavailable();
|
|
50
|
+
}
|
|
51
|
+
return als.run(context, fn);
|
|
52
|
+
} finally {
|
|
53
|
+
context = null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @type {AsyncLocalStorage<RenderContext | null> | null} */
|
|
58
|
+
let als = null;
|
|
59
|
+
|
|
60
|
+
export async function init_render_context() {
|
|
61
|
+
if (als !== null) return;
|
|
62
|
+
try {
|
|
63
|
+
// @ts-ignore -- we don't include node types in the production build
|
|
64
|
+
const { AsyncLocalStorage } = await import('node:async_hooks');
|
|
65
|
+
als = new AsyncLocalStorage();
|
|
66
|
+
} catch {}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// this has to be a function because rollup won't treeshake it if it's a constant
|
|
70
|
+
function in_webcontainer() {
|
|
71
|
+
// @ts-ignore -- this will fail when we run typecheck because we exclude node types
|
|
72
|
+
// eslint-disable-next-line n/prefer-global/process
|
|
73
|
+
return !!globalThis.process?.versions?.webcontainer;
|
|
74
|
+
}
|