@sveltejs/kit 2.26.0 → 2.27.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 (56) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -2
  3. package/src/core/adapt/builder.js +6 -1
  4. package/src/core/config/options.js +4 -0
  5. package/src/core/generate_manifest/index.js +3 -0
  6. package/src/core/postbuild/analyse.js +25 -1
  7. package/src/core/postbuild/fallback.js +2 -1
  8. package/src/core/postbuild/prerender.js +41 -10
  9. package/src/core/sync/create_manifest_data/index.js +35 -1
  10. package/src/core/sync/write_server.js +4 -2
  11. package/src/core/sync/write_types/index.js +12 -5
  12. package/src/exports/index.js +1 -1
  13. package/src/exports/internal/index.js +3 -1
  14. package/src/exports/internal/remote-functions.js +21 -0
  15. package/src/exports/public.d.ts +162 -2
  16. package/src/exports/vite/build/build_remote.js +129 -0
  17. package/src/exports/vite/dev/index.js +7 -0
  18. package/src/exports/vite/index.js +123 -8
  19. package/src/exports/vite/module_ids.js +3 -2
  20. package/src/exports/vite/preview/index.js +3 -1
  21. package/src/runtime/app/navigation.js +1 -0
  22. package/src/runtime/app/server/index.js +2 -0
  23. package/src/runtime/app/server/remote/command.js +91 -0
  24. package/src/runtime/app/server/remote/form.js +124 -0
  25. package/src/runtime/app/server/remote/index.js +4 -0
  26. package/src/runtime/app/server/remote/prerender.js +163 -0
  27. package/src/runtime/app/server/remote/query.js +115 -0
  28. package/src/runtime/app/server/remote/shared.js +153 -0
  29. package/src/runtime/client/client.js +107 -39
  30. package/src/runtime/client/fetcher.js +1 -1
  31. package/src/runtime/client/remote-functions/command.js +71 -0
  32. package/src/runtime/client/remote-functions/form.svelte.js +312 -0
  33. package/src/runtime/client/remote-functions/index.js +4 -0
  34. package/src/runtime/client/remote-functions/prerender.svelte.js +166 -0
  35. package/src/runtime/client/remote-functions/query.svelte.js +219 -0
  36. package/src/runtime/client/remote-functions/shared.svelte.js +143 -0
  37. package/src/runtime/client/types.d.ts +2 -0
  38. package/src/runtime/server/data/index.js +6 -4
  39. package/src/runtime/server/event-state.js +41 -0
  40. package/src/runtime/server/index.js +12 -3
  41. package/src/runtime/server/page/actions.js +1 -1
  42. package/src/runtime/server/page/index.js +10 -3
  43. package/src/runtime/server/page/load_data.js +18 -12
  44. package/src/runtime/server/page/render.js +31 -5
  45. package/src/runtime/server/page/serialize_data.js +1 -1
  46. package/src/runtime/server/remote.js +237 -0
  47. package/src/runtime/server/respond.js +57 -36
  48. package/src/runtime/shared.js +61 -0
  49. package/src/types/global-private.d.ts +2 -0
  50. package/src/types/internal.d.ts +51 -4
  51. package/src/types/synthetic/$env+static+private.md +1 -1
  52. package/src/utils/routing.js +1 -1
  53. package/src/version.js +1 -1
  54. package/types/index.d.ts +266 -3
  55. package/types/index.d.ts.map +14 -1
  56. /package/src/{runtime → utils}/hash.js +0 -0
@@ -0,0 +1,163 @@
1
+ /** @import { RemoteResource, RemotePrerenderFunction } from '@sveltejs/kit' */
2
+ /** @import { RemotePrerenderInputsGenerator, RemoteInfo, MaybePromise } from 'types' */
3
+ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
4
+ import { error, json } from '@sveltejs/kit';
5
+ import { DEV } from 'esm-env';
6
+ import { getRequestEvent } from '../event.js';
7
+ import { create_remote_cache_key, stringify, stringify_remote_arg } from '../../../shared.js';
8
+ import { app_dir, base } from '__sveltekit/paths';
9
+ import {
10
+ check_experimental,
11
+ create_validator,
12
+ get_response,
13
+ parse_remote_response,
14
+ run_remote_function
15
+ } from './shared.js';
16
+ import { get_event_state } from '../../../server/event-state.js';
17
+
18
+ /**
19
+ * Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a `fetch` call.
20
+ *
21
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#prerender) for full documentation.
22
+ *
23
+ * @template Output
24
+ * @overload
25
+ * @param {() => MaybePromise<Output>} fn
26
+ * @param {{ inputs?: RemotePrerenderInputsGenerator<void>, dynamic?: boolean }} [options]
27
+ * @returns {RemotePrerenderFunction<void, Output>}
28
+ * @since 2.27
29
+ */
30
+ /**
31
+ * Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a `fetch` call.
32
+ *
33
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#prerender) for full documentation.
34
+ *
35
+ * @template Input
36
+ * @template Output
37
+ * @overload
38
+ * @param {'unchecked'} validate
39
+ * @param {(arg: Input) => MaybePromise<Output>} fn
40
+ * @param {{ inputs?: RemotePrerenderInputsGenerator<Input>, dynamic?: boolean }} [options]
41
+ * @returns {RemotePrerenderFunction<Input, Output>}
42
+ * @since 2.27
43
+ */
44
+ /**
45
+ * Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a `fetch` call.
46
+ *
47
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#prerender) for full documentation.
48
+ *
49
+ * @template {StandardSchemaV1} Schema
50
+ * @template Output
51
+ * @overload
52
+ * @param {Schema} schema
53
+ * @param {(arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>} fn
54
+ * @param {{ inputs?: RemotePrerenderInputsGenerator<StandardSchemaV1.InferOutput<Schema>>, dynamic?: boolean }} [options]
55
+ * @returns {RemotePrerenderFunction<StandardSchemaV1.InferOutput<Schema>, Output>}
56
+ * @since 2.27
57
+ */
58
+ /**
59
+ * @template Input
60
+ * @template Output
61
+ * @param {any} validate_or_fn
62
+ * @param {any} [fn_or_options]
63
+ * @param {{ inputs?: RemotePrerenderInputsGenerator<Input>, dynamic?: boolean }} [maybe_options]
64
+ * @returns {RemotePrerenderFunction<Input, Output>}
65
+ * @since 2.27
66
+ */
67
+ /*@__NO_SIDE_EFFECTS__*/
68
+ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
69
+ check_experimental('prerender');
70
+
71
+ const maybe_fn = typeof fn_or_options === 'function' ? fn_or_options : undefined;
72
+
73
+ /** @type {typeof maybe_options} */
74
+ const options = maybe_options ?? (maybe_fn ? undefined : fn_or_options);
75
+
76
+ /** @type {(arg?: Input) => MaybePromise<Output>} */
77
+ const fn = maybe_fn ?? validate_or_fn;
78
+
79
+ /** @type {(arg?: any) => MaybePromise<Input>} */
80
+ const validate = create_validator(validate_or_fn, maybe_fn);
81
+
82
+ /** @type {RemoteInfo} */
83
+ const __ = {
84
+ type: 'prerender',
85
+ id: '',
86
+ name: '',
87
+ has_arg: !!maybe_fn,
88
+ inputs: options?.inputs,
89
+ dynamic: options?.dynamic
90
+ };
91
+
92
+ /** @type {RemotePrerenderFunction<Input, Output> & { __: RemoteInfo }} */
93
+ const wrapper = (arg) => {
94
+ /** @type {Promise<Output> & Partial<RemoteResource<Output>>} */
95
+ const promise = (async () => {
96
+ const event = getRequestEvent();
97
+ const state = get_event_state(event);
98
+ const payload = stringify_remote_arg(arg, state.transport);
99
+ const id = __.id;
100
+ const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ''}`;
101
+
102
+ if (!state.prerendering && !DEV && !event.isRemoteRequest) {
103
+ try {
104
+ return await get_response(id, arg, event, async () => {
105
+ // TODO adapters can provide prerendered data more efficiently than
106
+ // fetching from the public internet
107
+ const response = await fetch(new URL(url, event.url.origin).href);
108
+
109
+ if (!response.ok) {
110
+ throw new Error('Prerendered response not found');
111
+ }
112
+
113
+ const prerendered = await response.json();
114
+
115
+ if (prerendered.type === 'error') {
116
+ error(prerendered.status, prerendered.error);
117
+ }
118
+
119
+ // TODO can we redirect here?
120
+
121
+ (state.remote_data ??= {})[create_remote_cache_key(id, payload)] = prerendered.result;
122
+ return parse_remote_response(prerendered.result, state.transport);
123
+ });
124
+ } catch {
125
+ // not available prerendered, fallback to normal function
126
+ }
127
+ }
128
+
129
+ if (state.prerendering?.remote_responses.has(url)) {
130
+ return /** @type {Promise<any>} */ (state.prerendering.remote_responses.get(url));
131
+ }
132
+
133
+ const promise = get_response(id, arg, event, () =>
134
+ run_remote_function(event, false, arg, validate, fn)
135
+ );
136
+
137
+ if (state.prerendering) {
138
+ state.prerendering.remote_responses.set(url, promise);
139
+ }
140
+
141
+ const result = await promise;
142
+
143
+ if (state.prerendering) {
144
+ const body = { type: 'result', result: stringify(result, state.transport) };
145
+ state.prerendering.dependencies.set(url, {
146
+ body: JSON.stringify(body),
147
+ response: json(body)
148
+ });
149
+ }
150
+
151
+ // TODO this is missing error/loading/current/status
152
+ return result;
153
+ })();
154
+
155
+ promise.catch(() => {});
156
+
157
+ return /** @type {RemoteResource<Output>} */ (promise);
158
+ };
159
+
160
+ Object.defineProperty(wrapper, '__', { value: __ });
161
+
162
+ return wrapper;
163
+ }
@@ -0,0 +1,115 @@
1
+ /** @import { RemoteQuery, RemoteQueryFunction } from '@sveltejs/kit' */
2
+ /** @import { RemoteInfo, MaybePromise } from 'types' */
3
+ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
4
+ import { getRequestEvent } from '../event.js';
5
+ import { create_remote_cache_key, stringify_remote_arg } from '../../../shared.js';
6
+ import { prerendering } from '__sveltekit/environment';
7
+ import {
8
+ check_experimental,
9
+ create_validator,
10
+ get_response,
11
+ run_remote_function
12
+ } from './shared.js';
13
+ import { get_event_state } from '../../../server/event-state.js';
14
+
15
+ /**
16
+ * Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
17
+ *
18
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
19
+ *
20
+ * @template Output
21
+ * @overload
22
+ * @param {() => MaybePromise<Output>} fn
23
+ * @returns {RemoteQueryFunction<void, Output>}
24
+ * @since 2.27
25
+ */
26
+ /**
27
+ * Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
28
+ *
29
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
30
+ *
31
+ * @template Input
32
+ * @template Output
33
+ * @overload
34
+ * @param {'unchecked'} validate
35
+ * @param {(arg: Input) => MaybePromise<Output>} fn
36
+ * @returns {RemoteQueryFunction<Input, Output>}
37
+ * @since 2.27
38
+ */
39
+ /**
40
+ * Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
41
+ *
42
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
43
+ *
44
+ * @template {StandardSchemaV1} Schema
45
+ * @template Output
46
+ * @overload
47
+ * @param {Schema} schema
48
+ * @param {(arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>} fn
49
+ * @returns {RemoteQueryFunction<StandardSchemaV1.InferOutput<Schema>, Output>}
50
+ * @since 2.27
51
+ */
52
+ /**
53
+ * @template Input
54
+ * @template Output
55
+ * @param {any} validate_or_fn
56
+ * @param {(args?: Input) => MaybePromise<Output>} [maybe_fn]
57
+ * @returns {RemoteQueryFunction<Input, Output>}
58
+ * @since 2.27
59
+ */
60
+ /*@__NO_SIDE_EFFECTS__*/
61
+ export function query(validate_or_fn, maybe_fn) {
62
+ check_experimental('query');
63
+
64
+ /** @type {(arg?: Input) => Output} */
65
+ const fn = maybe_fn ?? validate_or_fn;
66
+
67
+ /** @type {(arg?: any) => MaybePromise<Input>} */
68
+ const validate = create_validator(validate_or_fn, maybe_fn);
69
+
70
+ /** @type {RemoteInfo} */
71
+ const __ = { type: 'query', id: '', name: '' };
72
+
73
+ /** @type {RemoteQueryFunction<Input, Output> & { __: RemoteInfo }} */
74
+ const wrapper = (arg) => {
75
+ if (prerendering) {
76
+ throw new Error(
77
+ `Cannot call query '${__.name}' while prerendering, as prerendered pages need static data. Use 'prerender' from $app/server instead`
78
+ );
79
+ }
80
+
81
+ const event = getRequestEvent();
82
+
83
+ /** @type {Promise<any> & Partial<RemoteQuery<any>>} */
84
+ const promise = get_response(__.id, arg, event, () =>
85
+ run_remote_function(event, false, arg, validate, fn)
86
+ );
87
+
88
+ promise.catch(() => {});
89
+
90
+ promise.refresh = async () => {
91
+ const event = getRequestEvent();
92
+ const state = get_event_state(event);
93
+ const refreshes = state?.refreshes;
94
+
95
+ if (!refreshes) {
96
+ throw new Error(
97
+ `Cannot call refresh on query '${__.name}' because it is not executed in the context of a command/form remote function`
98
+ );
99
+ }
100
+
101
+ const cache_key = create_remote_cache_key(__.id, stringify_remote_arg(arg, state.transport));
102
+ refreshes[cache_key] = await /** @type {Promise<any>} */ (promise);
103
+ };
104
+
105
+ promise.withOverride = () => {
106
+ throw new Error(`Cannot call '${__.name}.withOverride()' on the server`);
107
+ };
108
+
109
+ return /** @type {RemoteQuery<Output>} */ (promise);
110
+ };
111
+
112
+ Object.defineProperty(wrapper, '__', { value: __ });
113
+
114
+ return wrapper;
115
+ }
@@ -0,0 +1,153 @@
1
+ /** @import { RequestEvent } from '@sveltejs/kit' */
2
+ /** @import { ServerHooks, MaybePromise } from 'types' */
3
+ import { parse } from 'devalue';
4
+ import { error } from '@sveltejs/kit';
5
+ import { getRequestEvent, with_event } from '../event.js';
6
+ import { create_remote_cache_key, stringify_remote_arg } from '../../../shared.js';
7
+ import { EVENT_STATE, get_event_state } from '../../../server/event-state.js';
8
+
9
+ /**
10
+ * @param {any} validate_or_fn
11
+ * @param {(arg?: any) => any} [maybe_fn]
12
+ * @returns {(arg?: any) => MaybePromise<any>}
13
+ */
14
+ export function create_validator(validate_or_fn, maybe_fn) {
15
+ // prevent functions without validators being called with arguments
16
+ if (!maybe_fn) {
17
+ return (arg) => {
18
+ if (arg !== undefined) {
19
+ error(400, 'Bad Request');
20
+ }
21
+ };
22
+ }
23
+
24
+ // if 'unchecked', pass input through without validating
25
+ if (validate_or_fn === 'unchecked') {
26
+ return (arg) => arg;
27
+ }
28
+
29
+ // use https://standardschema.dev validator if provided
30
+ if ('~standard' in validate_or_fn) {
31
+ return async (arg) => {
32
+ // Get event before async validation to ensure it's available in server environments without AsyncLocalStorage, too
33
+ const event = getRequestEvent();
34
+ const state = get_event_state(event);
35
+ const validate = validate_or_fn['~standard'].validate;
36
+
37
+ const result = await validate(arg);
38
+
39
+ // if the `issues` field exists, the validation failed
40
+ if (result.issues) {
41
+ error(
42
+ 400,
43
+ await state.handleValidationError({
44
+ ...result,
45
+ event
46
+ })
47
+ );
48
+ }
49
+
50
+ return result.value;
51
+ };
52
+ }
53
+
54
+ throw new Error(
55
+ 'Invalid validator passed to remote function. Expected "unchecked" or a Standard Schema (https://standardschema.dev)'
56
+ );
57
+ }
58
+
59
+ /**
60
+ * In case of a single remote function call, just returns the result.
61
+ *
62
+ * In case of a full page reload, returns the response for a remote function call,
63
+ * either from the cache or by invoking the function.
64
+ * Also saves an uneval'ed version of the result for later HTML inlining for hydration.
65
+ *
66
+ * @template {MaybePromise<any>} T
67
+ * @param {string} id
68
+ * @param {any} arg
69
+ * @param {RequestEvent} event
70
+ * @param {() => Promise<T>} get_result
71
+ * @returns {Promise<T>}
72
+ */
73
+ export function get_response(id, arg, event, get_result) {
74
+ const state = get_event_state(event);
75
+ const cache_key = create_remote_cache_key(id, stringify_remote_arg(arg, state.transport));
76
+
77
+ return ((state.remote_data ??= {})[cache_key] ??= get_result());
78
+ }
79
+
80
+ /** @param {string} feature */
81
+ export function check_experimental(feature) {
82
+ if (!__SVELTEKIT_EXPERIMENTAL__REMOTE_FUNCTIONS__) {
83
+ throw new Error(
84
+ `Cannot use \`${feature}\` from \`$app/server\` without the experimental flag set to true. Please set kit.experimental.remoteFunctions to \`true\` in your config.`
85
+ );
86
+ }
87
+ }
88
+
89
+ /**
90
+ * @param {any} data
91
+ * @param {ServerHooks['transport']} transport
92
+ */
93
+ export function parse_remote_response(data, transport) {
94
+ /** @type {Record<string, any>} */
95
+ const revivers = {};
96
+ for (const key in transport) {
97
+ revivers[key] = transport[key].decode;
98
+ }
99
+
100
+ return parse(data, revivers);
101
+ }
102
+
103
+ /**
104
+ * Like `with_event` but removes things from `event` you cannot see/call in remote functions, such as `setHeaders`.
105
+ * @template T
106
+ * @param {RequestEvent} event
107
+ * @param {boolean} allow_cookies
108
+ * @param {any} arg
109
+ * @param {(arg: any) => any} validate
110
+ * @param {(arg?: any) => T} fn
111
+ */
112
+ export async function run_remote_function(event, allow_cookies, arg, validate, fn) {
113
+ /** @type {RequestEvent} */
114
+ const cleansed = {
115
+ ...event,
116
+ // @ts-expect-error this isn't part of the public `RequestEvent` type
117
+ [EVENT_STATE]: event[EVENT_STATE],
118
+ setHeaders: () => {
119
+ throw new Error('setHeaders is not allowed in remote functions');
120
+ },
121
+ cookies: {
122
+ ...event.cookies,
123
+ set: (name, value, opts) => {
124
+ if (!allow_cookies) {
125
+ throw new Error('Cannot set cookies in `query` or `prerender` functions');
126
+ }
127
+
128
+ if (opts.path && !opts.path.startsWith('/')) {
129
+ throw new Error('Cookies set in remote functions must have an absolute path');
130
+ }
131
+
132
+ return event.cookies.set(name, value, opts);
133
+ },
134
+ delete: (name, opts) => {
135
+ if (!allow_cookies) {
136
+ throw new Error('Cannot delete cookies in `query` or `prerender` functions');
137
+ }
138
+
139
+ if (opts.path && !opts.path.startsWith('/')) {
140
+ throw new Error('Cookies deleted in remote functions must have an absolute path');
141
+ }
142
+
143
+ return event.cookies.delete(name, opts);
144
+ }
145
+ },
146
+ route: { id: null },
147
+ url: new URL(event.url.origin)
148
+ };
149
+
150
+ // In two parts, each with_event, so that runtimes without async local storage can still get the event at the start of the function
151
+ const validated = await with_event(cleansed, () => validate(arg));
152
+ return with_event(cleansed, () => fn(validated));
153
+ }
@@ -46,7 +46,6 @@ import { page, update, navigating } from './state.svelte.js';
46
46
  import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
47
47
 
48
48
  export { load_css };
49
-
50
49
  const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
51
50
 
52
51
  let errored = false;
@@ -174,9 +173,13 @@ let default_error_loader;
174
173
  let container;
175
174
  /** @type {HTMLElement} */
176
175
  let target;
176
+
177
177
  /** @type {import('./types.js').SvelteKitApp} */
178
178
  export let app;
179
179
 
180
+ /** @type {Record<string, any>} */
181
+ export let remote_responses;
182
+
180
183
  /** @type {Array<((url: URL) => boolean)>} */
181
184
  const invalidated = [];
182
185
 
@@ -225,7 +228,7 @@ let current = {
225
228
 
226
229
  /** this being true means we SSR'd */
227
230
  let hydrated = false;
228
- let started = false;
231
+ export let started = false;
229
232
  let autoscroll = true;
230
233
  let updating = false;
231
234
  let is_navigating = false;
@@ -256,7 +259,13 @@ let token;
256
259
  const preload_tokens = new Set();
257
260
 
258
261
  /** @type {Promise<void> | null} */
259
- let pending_invalidate;
262
+ export let pending_invalidate;
263
+
264
+ /**
265
+ * @type {Map<string, {count: number, resource: any}>}
266
+ * A map of id -> query info with all queries that currently exist in the app.
267
+ */
268
+ export const query_map = new Map();
260
269
 
261
270
  /**
262
271
  * @param {import('./types.js').SvelteKitApp} _app
@@ -279,6 +288,7 @@ export async function start(_app, _target, hydrate) {
279
288
  }
280
289
 
281
290
  app = _app;
291
+ remote_responses = hydrate?.remote ?? {};
282
292
 
283
293
  await _app.hooks.init?.();
284
294
 
@@ -339,7 +349,7 @@ export async function start(_app, _target, hydrate) {
339
349
  _start_router();
340
350
  }
341
351
 
342
- async function _invalidate() {
352
+ async function _invalidate(include_load_functions = true, reset_page_state = true) {
343
353
  // Accept all invalidations as they come, don't swallow any while another invalidation
344
354
  // is running because subsequent invalidations may make earlier ones outdated,
345
355
  // but batch multiple synchronous invalidations.
@@ -356,20 +366,36 @@ async function _invalidate() {
356
366
  // at which point the invalidation should take over and "win".
357
367
  load_cache = null;
358
368
 
359
- const navigation_result = intent && (await load_route(intent));
360
- if (!navigation_result || nav_token !== token) return;
361
-
362
- if (navigation_result.type === 'redirect') {
363
- return _goto(new URL(navigation_result.location, current.url).href, {}, 1, nav_token);
369
+ // Rerun queries
370
+ if (force_invalidation) {
371
+ query_map.forEach(({ resource }) => {
372
+ resource.refresh?.();
373
+ });
364
374
  }
365
375
 
366
- if (navigation_result.props.page) {
367
- Object.assign(page, navigation_result.props.page);
376
+ if (include_load_functions) {
377
+ const prev_state = page.state;
378
+ const navigation_result = intent && (await load_route(intent));
379
+ if (!navigation_result || nav_token !== token) return;
380
+
381
+ if (navigation_result.type === 'redirect') {
382
+ return _goto(new URL(navigation_result.location, current.url).href, {}, 1, nav_token);
383
+ }
384
+
385
+ // This is a bit hacky but allows us not having to pass that boolean around, making things harder to reason about
386
+ if (!reset_page_state) {
387
+ navigation_result.props.page.state = prev_state;
388
+ }
389
+ update(navigation_result.props.page);
390
+ current = navigation_result.state;
391
+ reset_invalidation();
392
+ root.$set(navigation_result.props);
393
+ } else {
394
+ reset_invalidation();
368
395
  }
369
- current = navigation_result.state;
370
- reset_invalidation();
371
- root.$set(navigation_result.props);
372
- update(navigation_result.props.page);
396
+
397
+ // Don't use allSettled yet because it's too new
398
+ await Promise.all([...query_map.values()].map(({ resource }) => resource)).catch(noop);
373
399
  }
374
400
 
375
401
  function reset_invalidation() {
@@ -406,7 +432,9 @@ function persist_state() {
406
432
  * @param {{}} [nav_token]
407
433
  */
408
434
  async function _goto(url, options, redirect_count, nav_token) {
409
- return navigate({
435
+ /** @type {string[]} */
436
+ let query_keys;
437
+ const result = await navigate({
410
438
  type: 'goto',
411
439
  url: resolve_url(url),
412
440
  keepfocus: options.keepFocus,
@@ -418,6 +446,7 @@ async function _goto(url, options, redirect_count, nav_token) {
418
446
  accept: () => {
419
447
  if (options.invalidateAll) {
420
448
  force_invalidation = true;
449
+ query_keys = [...query_map.keys()];
421
450
  }
422
451
 
423
452
  if (options.invalidate) {
@@ -425,6 +454,22 @@ async function _goto(url, options, redirect_count, nav_token) {
425
454
  }
426
455
  }
427
456
  });
457
+ if (options.invalidateAll) {
458
+ // TODO the ticks shouldn't be necessary, something inside Svelte itself is buggy
459
+ // when a query in a layout that still exists after page change is refreshed earlier than this
460
+ void svelte
461
+ .tick()
462
+ .then(svelte.tick)
463
+ .then(() => {
464
+ query_map.forEach(({ resource }, key) => {
465
+ // Only refresh those that already existed on the old page
466
+ if (query_keys?.includes(key)) {
467
+ resource.refresh?.();
468
+ }
469
+ });
470
+ });
471
+ }
472
+ return result;
428
473
  }
429
474
 
430
475
  /** @param {import('./types.js').NavigationIntent} intent */
@@ -1993,6 +2038,21 @@ export function invalidateAll() {
1993
2038
  return _invalidate();
1994
2039
  }
1995
2040
 
2041
+ /**
2042
+ * Causes all currently active remote functions to refresh, and all `load` functions belonging to the currently active page to re-run (unless disabled via the option argument).
2043
+ * Returns a `Promise` that resolves when the page is subsequently updated.
2044
+ * @param {{ includeLoadFunctions?: boolean }} [options]
2045
+ * @returns {Promise<void>}
2046
+ */
2047
+ export function refreshAll({ includeLoadFunctions = true } = {}) {
2048
+ if (!BROWSER) {
2049
+ throw new Error('Cannot call refreshAll() on the server');
2050
+ }
2051
+
2052
+ force_invalidation = true;
2053
+ return _invalidate(includeLoadFunctions, false);
2054
+ }
2055
+
1996
2056
  /**
1997
2057
  * Programmatically preloads the given page, which means
1998
2058
  * 1. ensuring that the code for the page is loaded, and
@@ -2174,29 +2234,7 @@ export async function applyAction(result) {
2174
2234
  }
2175
2235
 
2176
2236
  if (result.type === 'error') {
2177
- const url = new URL(location.href);
2178
-
2179
- const { branch, route } = current;
2180
- if (!route) return;
2181
-
2182
- const error_load = await load_nearest_error_page(current.branch.length, branch, route.errors);
2183
- if (error_load) {
2184
- const navigation_result = get_navigation_result_from_branch({
2185
- url,
2186
- params: current.params,
2187
- branch: branch.slice(0, error_load.idx).concat(error_load.node),
2188
- status: result.status ?? 500,
2189
- error: result.error,
2190
- route
2191
- });
2192
-
2193
- current = navigation_result.state;
2194
-
2195
- root.$set(navigation_result.props);
2196
- update(navigation_result.props.page);
2197
-
2198
- void tick().then(() => reset_focus(current.url));
2199
- }
2237
+ await set_nearest_error_page(result.error, result.status);
2200
2238
  } else if (result.type === 'redirect') {
2201
2239
  await _goto(result.location, { invalidateAll: true }, 0);
2202
2240
  } else {
@@ -2221,6 +2259,36 @@ export async function applyAction(result) {
2221
2259
  }
2222
2260
  }
2223
2261
 
2262
+ /**
2263
+ * @param {App.Error} error
2264
+ * @param {number} status
2265
+ */
2266
+ export async function set_nearest_error_page(error, status = 500) {
2267
+ const url = new URL(location.href);
2268
+
2269
+ const { branch, route } = current;
2270
+ if (!route) return;
2271
+
2272
+ const error_load = await load_nearest_error_page(current.branch.length, branch, route.errors);
2273
+ if (error_load) {
2274
+ const navigation_result = get_navigation_result_from_branch({
2275
+ url,
2276
+ params: current.params,
2277
+ branch: branch.slice(0, error_load.idx).concat(error_load.node),
2278
+ status,
2279
+ error,
2280
+ route
2281
+ });
2282
+
2283
+ current = navigation_result.state;
2284
+
2285
+ root.$set(navigation_result.props);
2286
+ update(navigation_result.props.page);
2287
+
2288
+ void tick().then(() => reset_focus(current.url));
2289
+ }
2290
+ }
2291
+
2224
2292
  function _start_router() {
2225
2293
  history.scrollRestoration = 'manual';
2226
2294
 
@@ -1,5 +1,5 @@
1
1
  import { BROWSER, DEV } from 'esm-env';
2
- import { hash } from '../hash.js';
2
+ import { hash } from '../../utils/hash.js';
3
3
  import { b64_decode } from '../utils.js';
4
4
 
5
5
  let loading = 0;