@sveltejs/kit 3.0.0-next.11 → 3.0.0-next.13

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 (84) hide show
  1. package/package.json +10 -10
  2. package/src/cli.js +8 -8
  3. package/src/constants.js +1 -1
  4. package/src/core/config/options.js +43 -35
  5. package/src/core/env.js +25 -11
  6. package/src/core/postbuild/prerender.js +3 -3
  7. package/src/core/sync/sync.js +11 -14
  8. package/src/core/sync/utils.js +21 -1
  9. package/src/core/sync/{write_non_ambient.js → write_app_types.js} +24 -23
  10. package/src/core/sync/write_client_manifest.js +4 -12
  11. package/src/core/sync/write_env.js +6 -4
  12. package/src/core/sync/write_server.js +10 -15
  13. package/src/core/sync/write_tsconfig/index.js +257 -0
  14. package/src/core/sync/write_tsconfig/utils.js +162 -0
  15. package/src/core/sync/write_types/index.js +80 -88
  16. package/src/exports/index.js +23 -12
  17. package/src/exports/internal/server/index.js +3 -1
  18. package/src/exports/internal/shared.js +4 -12
  19. package/src/exports/public.d.ts +87 -62
  20. package/src/exports/vite/build/remote.js +10 -12
  21. package/src/exports/vite/dev/index.js +113 -88
  22. package/src/exports/vite/index.js +452 -237
  23. package/src/exports/vite/module_ids.js +3 -1
  24. package/src/exports/vite/utils.js +1 -10
  25. package/src/runner.js +13 -0
  26. package/src/runtime/app/forms.js +4 -0
  27. package/src/runtime/app/manifest/index.js +1 -0
  28. package/src/runtime/app/paths/client.js +30 -30
  29. package/src/runtime/app/paths/internal/client.js +26 -0
  30. package/src/runtime/app/paths/server.js +22 -9
  31. package/src/runtime/app/paths/types.d.ts +11 -19
  32. package/src/runtime/app/server/remote/command.js +12 -7
  33. package/src/runtime/app/server/remote/form.js +37 -26
  34. package/src/runtime/app/server/remote/prerender.js +22 -11
  35. package/src/runtime/app/server/remote/query.js +5 -4
  36. package/src/runtime/app/server/remote/requested.js +4 -4
  37. package/src/runtime/app/server/remote/shared.js +48 -30
  38. package/src/runtime/app/service-worker/index.js +24 -0
  39. package/src/runtime/app/state/client.js +3 -0
  40. package/src/runtime/app/state/index.js +3 -3
  41. package/src/runtime/app/state/server.js +3 -0
  42. package/src/runtime/client/client.js +627 -311
  43. package/src/runtime/client/constants.js +2 -6
  44. package/src/runtime/client/fetcher.js +27 -17
  45. package/src/runtime/client/remote-functions/form.svelte.js +82 -58
  46. package/src/runtime/client/remote-functions/prerender.svelte.js +1 -1
  47. package/src/runtime/client/remote-functions/query/index.js +2 -2
  48. package/src/runtime/client/remote-functions/query/instance.svelte.js +2 -2
  49. package/src/runtime/client/remote-functions/query-batch.svelte.js +3 -4
  50. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +2 -2
  51. package/src/runtime/client/remote-functions/query-live/iterator.js +15 -10
  52. package/src/runtime/client/remote-functions/shared.svelte.js +15 -11
  53. package/src/runtime/client/state.svelte.js +54 -33
  54. package/src/runtime/client/types.d.ts +1 -2
  55. package/src/runtime/client/utils.js +21 -14
  56. package/src/runtime/components/root.svelte +4 -14
  57. package/src/runtime/form-utils.js +89 -54
  58. package/src/runtime/props.svelte.js +71 -0
  59. package/src/runtime/server/cookie.js +1 -1
  60. package/src/runtime/server/data/index.js +31 -28
  61. package/src/runtime/server/errors.js +1 -1
  62. package/src/runtime/server/fetch.js +4 -6
  63. package/src/runtime/server/page/actions.js +3 -3
  64. package/src/runtime/server/page/csp.js +86 -103
  65. package/src/runtime/server/page/index.js +1 -2
  66. package/src/runtime/server/page/load_data.js +15 -5
  67. package/src/runtime/server/page/render.js +46 -52
  68. package/src/runtime/server/page/respond_with_error.js +1 -3
  69. package/src/runtime/server/remote-functions.js +66 -35
  70. package/src/runtime/server/respond.js +56 -16
  71. package/src/runtime/server/utils.js +10 -0
  72. package/src/types/ambient-private.d.ts +9 -1
  73. package/src/types/ambient.d.ts +23 -28
  74. package/src/types/global-private.d.ts +12 -0
  75. package/src/types/internal.d.ts +15 -10
  76. package/src/types/private.d.ts +3 -12
  77. package/src/utils/url.js +12 -0
  78. package/src/version.js +1 -1
  79. package/types/index.d.ts +172 -155
  80. package/types/index.d.ts.map +5 -1
  81. package/src/core/sync/write_ambient.js +0 -18
  82. package/src/core/sync/write_tsconfig.js +0 -258
  83. package/src/runtime/types.d.ts +0 -8
  84. /package/src/core/sync/{write_tsconfig_test → write_tsconfig/test-app}/package.json +0 -0
@@ -6,9 +6,11 @@ export const sveltekit_env_public_client = '\0virtual:__sveltekit/env/public/cli
6
6
  export const sveltekit_env_public_server = '\0virtual:__sveltekit/env/public/server';
7
7
  export const sveltekit_env_private = '\0virtual:__sveltekit/env/private';
8
8
  export const sveltekit_env_service_worker = '\0virtual:__sveltekit/env/service-worker';
9
-
9
+ export const sveltekit_server = '\0virtual:__sveltekit/server';
10
10
  export const service_worker = '\0virtual:service-worker';
11
11
 
12
+ export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data';
13
+
12
14
  export const app_server = posixify(
13
15
  fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url))
14
16
  );
@@ -4,12 +4,7 @@ import { negotiate } from '../../utils/http.js';
4
4
  import { escape_html } from '../../utils/escape.js';
5
5
  import { stackless } from '../../utils/error.js';
6
6
  import { dedent } from '../../core/sync/utils.js';
7
- import {
8
- app_server,
9
- app_env_private,
10
- service_worker,
11
- sveltekit_env_private
12
- } from './module_ids.js';
7
+ import { app_server, app_env_private, sveltekit_env_private } from './module_ids.js';
13
8
  import { styleText } from 'node:util';
14
9
 
15
10
  /**
@@ -143,10 +138,6 @@ export function normalize_id(id, aliases, cwd) {
143
138
  return '$app/env/private';
144
139
  }
145
140
 
146
- if (id === service_worker) {
147
- return '$service-worker';
148
- }
149
-
150
141
  return posixify(id);
151
142
  }
152
143
 
package/src/runner.js ADDED
@@ -0,0 +1,13 @@
1
+ /** @import { ViteDevServer } from 'vite' */
2
+ import * as vite from 'vite';
3
+
4
+ /**
5
+ * @param {ViteDevServer} server
6
+ */
7
+ export function get_runner(server) {
8
+ if (!vite.isRunnableDevEnvironment(server.environments.ssr)) {
9
+ throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment');
10
+ }
11
+
12
+ return server.environments.ssr.runner;
13
+ }
@@ -4,6 +4,7 @@ import { noop } from '../../utils/functions.js';
4
4
  import { refreshAll } from './navigation.js';
5
5
  import { app as client_app, applyAction, handle_error } from '../client/client.js';
6
6
  import { app as server_app } from '../server/app.js';
7
+ import { notify_version } from '../client/state.svelte.js';
7
8
 
8
9
  export { applyAction };
9
10
 
@@ -201,6 +202,9 @@ export function enhance(form_element, submit = noop) {
201
202
  signal: controller.signal
202
203
  });
203
204
 
205
+ // detect new deployments from the response header
206
+ notify_version(response.headers.get('x-sveltekit-version'));
207
+
204
208
  if (response.status === 204) {
205
209
  result = { type: 'success', status: 204 };
206
210
  } else {
@@ -0,0 +1 @@
1
+ export * from '__sveltekit/manifest-data';
@@ -1,8 +1,8 @@
1
- /** @import { Asset, RouteId, RouteIdWithSearchOrHash, Pathname, PathnameWithSearchOrHash, ResolvedPathname, RouteParams } from '$app/types' */
1
+ /** @import { AssetPath, RouteId, RouteIdWithSearchOrHash, Path, PathnameWithSearchOrHash, ResolvedPathname, RouteParams } from '$app/types' */
2
2
  /** @import { ResolveArgs } from './types.js' */
3
- import { base, assets, hash_routing } from './internal/client.js';
3
+ import { base, assets, hash_routing, match_implementation } from './internal/client.js';
4
4
  import { resolve_route } from '../../../utils/routing.js';
5
- import { get_navigation_intent } from '../../client/client.js';
5
+ import { DEV } from 'esm-env';
6
6
 
7
7
  /**
8
8
  * Resolve the URL of an asset in your `static` directory, by prefixing it with [`config.paths.assets`](https://svelte.dev/docs/kit/configuration#paths) if configured, or otherwise by prefixing it with the base path.
@@ -15,15 +15,24 @@ import { get_navigation_intent } from '../../client/client.js';
15
15
  * import { asset } from '$app/paths';
16
16
  * </script>
17
17
  *
18
- * <img alt="a potato" src={asset('/potato.jpg')} />
18
+ * <img alt="a potato" src={asset('potato.jpg')} />
19
19
  * ```
20
20
  * @since 2.26
21
21
  *
22
- * @param {Asset} file
22
+ * @param {AssetPath} file
23
23
  * @returns {string}
24
24
  */
25
25
  export function asset(file) {
26
- return (assets || base) + file;
26
+ // TODO 4.0 remove this
27
+ if (file[0] === '/') {
28
+ if (DEV) {
29
+ console.warn(`\`asset('${file}')\` should now be \`asset('${file.slice(1)}')\``);
30
+ }
31
+
32
+ file = file.slice(1);
33
+ }
34
+
35
+ return (assets || base) + '/' + file;
27
36
  }
28
37
 
29
38
  const pathname_prefix = hash_routing ? '#' : '';
@@ -38,7 +47,7 @@ const pathname_prefix = hash_routing ? '#' : '';
38
47
  * import { resolve } from '$app/paths';
39
48
  *
40
49
  * // using a pathname
41
- * const resolved = resolve(`/blog/hello-world`);
50
+ * const resolved = resolve(`blog/hello-world`);
42
51
  *
43
52
  * // using a route ID plus parameters
44
53
  * const resolved = resolve('/blog/[slug]', {
@@ -52,14 +61,18 @@ const pathname_prefix = hash_routing ? '#' : '';
52
61
  * @returns {ResolvedPathname}
53
62
  */
54
63
  export function resolve(...args) {
55
- if (!args[0].startsWith('/')) {
56
- throw new Error(
57
- `Cannot use \`resolve(...)\` with a non-absolute pathname or route ID (got "${args[0]}"). ` +
58
- '`resolve` is only for internal pathnames and route IDs; external URLs should be used directly.'
59
- );
64
+ if (args[0][0] === '/') {
65
+ const [id, params] = args;
66
+
67
+ // route ID
68
+ if (id.includes('[') && !params) {
69
+ throw new Error(`Missing params for dynamic route ID ${id}`);
70
+ }
71
+
72
+ return base + pathname_prefix + resolve_route(args[0], args[1] ?? {});
60
73
  }
61
74
 
62
- return base + pathname_prefix + resolve_route(args[0], args[1] ?? {});
75
+ return base + pathname_prefix + '/' + args[0];
63
76
  }
64
77
 
65
78
  /**
@@ -69,7 +82,7 @@ export function resolve(...args) {
69
82
  * ```js
70
83
  * import { match } from '$app/paths';
71
84
  *
72
- * const route = await match('/blog/hello-world');
85
+ * const route = await match('blog/hello-world');
73
86
  *
74
87
  * if (route?.id === '/blog/[slug]') {
75
88
  * const slug = route.params.slug;
@@ -79,22 +92,9 @@ export function resolve(...args) {
79
92
  * ```
80
93
  * @since 2.52.0
81
94
  *
82
- * @param {Pathname | URL | (string & {})} url
95
+ * @param {Path | URL | (string & {})} url
83
96
  * @returns {Promise<{ [K in RouteId]: { id: K; params: RouteParams<K>; } }[RouteId] | null>}
84
97
  */
85
- export async function match(url) {
86
- if (typeof url === 'string') {
87
- url = new URL(url, location.href);
88
- }
89
-
90
- const intent = await get_navigation_intent(url, false);
91
-
92
- if (intent) {
93
- return {
94
- id: /** @type {RouteId} */ (intent.route.id),
95
- params: intent.params
96
- };
97
- }
98
-
99
- return null;
98
+ export function match(url) {
99
+ return match_implementation(url);
100
100
  }
@@ -1,6 +1,32 @@
1
+ /** @import { RouteId, Path } from '$app/types' */
1
2
  import { payload } from '../../../client/payload.js';
2
3
 
3
4
  export const base = payload.base ?? __SVELTEKIT_PATHS_BASE__;
4
5
  export const assets = payload.assets ?? base ?? __SVELTEKIT_PATHS_ASSETS__;
5
6
  export const app_dir = __SVELTEKIT_APP_DIR__;
6
7
  export const hash_routing = __SVELTEKIT_HASH_ROUTING__;
8
+
9
+ /**
10
+ * We make this configurable per-environment so that it's possible to import `$app/paths`
11
+ * into a service worker without importing the entire client
12
+ * @param {Path | URL | (string & {})} _url
13
+ * @returns {Promise<{ [K in RouteId]: { id: K; params: import('$app/types').RouteParams<K>; } }[RouteId] | null>}
14
+ */
15
+ // eslint-disable-next-line @typescript-eslint/require-await
16
+ export let match_implementation = async (_url) => {
17
+ // @ts-ignore
18
+ if (typeof ServiceWorkerGlobalScope !== 'undefined' && self instanceof ServiceWorkerGlobalScope) {
19
+ throw new Error(
20
+ 'Cannot use `match(...)` inside a service worker, as it depends on the SvelteKit client instance'
21
+ );
22
+ }
23
+
24
+ return null;
25
+ };
26
+
27
+ /**
28
+ * @param {typeof match_implementation} fn
29
+ */
30
+ export function set_match_implementation(fn) {
31
+ match_implementation = fn;
32
+ }
@@ -5,23 +5,36 @@ import { add_data_suffix } from '../../pathname.js';
5
5
  import { try_get_request_store } from '@sveltejs/kit/internal/server';
6
6
  import { manifest } from '../../server/internal.js';
7
7
  import { get_hooks } from '__SERVER__/internal.js';
8
+ import { DEV } from 'esm-env';
8
9
 
9
10
  /** @type {import('./client.js').asset} */
10
11
  export function asset(file) {
11
- // @ts-expect-error we use the `resolve` mechanism, but with the 'wrong' input
12
- return assets && assets !== base ? assets + file : resolve(file);
12
+ // TODO 4.0 remove this
13
+ if (file[0] === '/') {
14
+ if (DEV) {
15
+ console.warn(`\`asset('${file}')\` should now be \`asset('${file.slice(1)}')\``);
16
+ }
17
+
18
+ file = file.slice(1);
19
+ }
20
+
21
+ return assets !== base ? `${assets}/${file}` : resolve(file);
13
22
  }
14
23
 
15
24
  /** @type {import('./client.js').resolve} */
16
25
  export function resolve(id, params) {
17
- if (!id.startsWith('/')) {
18
- throw new Error(
19
- `Cannot use \`resolve(...)\` with a non-absolute pathname or route ID (got "${id}"). ` +
20
- '`resolve` is only for internal pathnames and route IDs; external URLs should be used directly.'
21
- );
22
- }
26
+ let resolved;
27
+
28
+ if (id[0] === '/') {
29
+ // route ID
30
+ if (id.includes('[') && !params) {
31
+ throw new Error(`Missing params for dynamic route ID ${id}`);
32
+ }
23
33
 
24
- const resolved = resolve_route(id, params ?? {});
34
+ resolved = resolve_route(id, params ?? {});
35
+ } else {
36
+ resolved = '/' + id;
37
+ }
25
38
 
26
39
  if (relative) {
27
40
  const store = try_get_request_store();
@@ -1,23 +1,15 @@
1
- import {
2
- PathnameWithSearchOrHash,
3
- RouteId,
4
- RouteIdWithSearchOrHash,
5
- RouteParams
6
- } from '$app/types';
1
+ import { RouteId, RouteParams } from '$app/types';
7
2
 
8
- type StripSearchOrHash<T extends string> = T extends `${infer Pathname}?${string}`
9
- ? Pathname
10
- : T extends `${infer Pathname}#${string}`
11
- ? Pathname
3
+ type StripSearchOrHash<T extends string> = T extends `${infer U}?${string}`
4
+ ? U
5
+ : T extends `${infer U}#${string}`
6
+ ? U
12
7
  : T;
13
8
 
14
- export type ResolveArgs<T extends RouteIdWithSearchOrHash | PathnameWithSearchOrHash> =
15
- T extends RouteId
16
- ? RouteParams<T> extends Record<string, never>
9
+ export type ResolveArgs<T> = T extends `/${string}`
10
+ ? StripSearchOrHash<T> extends infer U extends RouteId
11
+ ? RouteParams<U> extends Record<string, never>
17
12
  ? [route: T]
18
- : [route: T, params: RouteParams<T>]
19
- : StripSearchOrHash<T> extends infer U extends RouteId
20
- ? RouteParams<U> extends Record<string, never>
21
- ? [route: T]
22
- : [route: T, params: RouteParams<U>]
23
- : [route: T];
13
+ : [route: T, params: RouteParams<U>]
14
+ : [never]
15
+ : [pathname: T];
@@ -65,16 +65,21 @@ export function command(validate_or_fn, maybe_fn) {
65
65
  const wrapper = (arg) => {
66
66
  const { event, state } = get_request_store();
67
67
 
68
- if (!MUTATIVE_METHODS.includes(event.request.method)) {
69
- throw new Error(
70
- `Cannot call a command (\`${__.name}(${maybe_fn ? '...' : ''})\`) from a ${event.request.method} handler`
71
- );
68
+ if (
69
+ !MUTATIVE_METHODS.includes(event.request.method) ||
70
+ state.is_in_remote_query ||
71
+ state.is_in_remote_prerender
72
+ ) {
73
+ const violation =
74
+ state.is_in_remote_query || state.is_in_remote_prerender
75
+ ? `inside a query or prerender function`
76
+ : `from a ${event.request.method} handler`;
77
+
78
+ throw new Error(`Cannot call a command (${__.name}) ${violation}`);
72
79
  }
73
80
 
74
81
  if (state.is_in_render) {
75
- throw new Error(
76
- `Cannot call a command (\`${__.name}(${maybe_fn ? '...' : ''})\`) during server-side rendering`
77
- );
82
+ throw new Error(`Cannot call a command (${__.name}) during server-side rendering`);
78
83
  }
79
84
 
80
85
  const promise = Promise.resolve(
@@ -7,7 +7,8 @@ import {
7
7
  set_nested_value,
8
8
  deep_set,
9
9
  normalize_issue,
10
- flatten_issues
10
+ flatten_issues,
11
+ parse_form_key
11
12
  } from '../../../form-utils.js';
12
13
  import { get_cache, get_implicit_lookup, run_remote_function } from './shared.js';
13
14
  import { ValidationError } from '@sveltejs/kit/internal';
@@ -102,7 +103,7 @@ export function form(validate_or_fn, maybe_fn) {
102
103
  }
103
104
 
104
105
  if (validated?.issues !== undefined) {
105
- handle_issues(output, validated.issues, form_data);
106
+ handle_issues(output, validated.issues, form_data, __.id);
106
107
  } else {
107
108
  if (validated !== undefined) {
108
109
  data = validated.value;
@@ -120,7 +121,7 @@ export function form(validate_or_fn, maybe_fn) {
120
121
  );
121
122
  } catch (e) {
122
123
  if (e instanceof ValidationError) {
123
- handle_issues(output, e.issues, form_data);
124
+ handle_issues(output, e.issues, form_data, __.id);
124
125
  } else {
125
126
  throw e;
126
127
  }
@@ -135,7 +136,7 @@ export function form(validate_or_fn, maybe_fn) {
135
136
 
136
137
  // register under the client-side action id so the output is serialized
137
138
  // into the page, allowing the hydrated client to restore `result`/`issues`/`input`
138
- get_implicit_lookup(__, state)[__.action_id ?? __.id] = () => cache[''];
139
+ get_implicit_lookup(__, state)[__.key ? `${__.id}/${__.key}` : __.id] = () => cache[''];
139
140
  }
140
141
 
141
142
  return output;
@@ -145,7 +146,15 @@ export function form(validate_or_fn, maybe_fn) {
145
146
  Object.defineProperty(instance, '__', { value: __ });
146
147
 
147
148
  Object.defineProperty(instance, 'action', {
148
- get: () => `?/remote=${__.id}`,
149
+ get: () => {
150
+ const search = new URLSearchParams(get_request_store().event.url.search);
151
+ search.delete('/remote');
152
+
153
+ const query = search.toString();
154
+ const action_id = __.key ? `${__.id}/${encodeURIComponent(__.key)}` : __.id;
155
+
156
+ return `?${query ? `${query}&` : ''}/remote=${action_id}`;
157
+ },
149
158
  enumerable: true
150
159
  });
151
160
 
@@ -153,10 +162,10 @@ export function form(validate_or_fn, maybe_fn) {
153
162
  get() {
154
163
  // the form instance is created once per module and shared across requests,
155
164
  // so the current request's state has to be resolved at access time
156
- return create_field_proxy(
157
- {},
158
- () => get_cache(__, get_request_store().state)?.['']?.input ?? {},
159
- (path, value) => {
165
+ return create_field_proxy({
166
+ form_id: __.id,
167
+ get: () => get_cache(__, get_request_store().state)?.['']?.input ?? {},
168
+ set: (path, value) => {
160
169
  const cache = get_cache(__, get_request_store().state);
161
170
  const entry = cache[''];
162
171
 
@@ -174,11 +183,11 @@ export function form(validate_or_fn, maybe_fn) {
174
183
  deep_set(input, path.map(String), value);
175
184
  (cache[''] ??= {}).input = input;
176
185
  },
177
- () => flatten_issues(get_cache(__, get_request_store().state)?.['']?.issues ?? []),
178
- () => ({}),
179
- () => ({}),
180
- []
181
- );
186
+ get_issues: () =>
187
+ flatten_issues(get_cache(__, get_request_store().state)?.['']?.issues ?? []),
188
+ get_touched: () => ({}),
189
+ get_dirty: () => ({})
190
+ });
182
191
  }
183
192
  });
184
193
 
@@ -228,12 +237,15 @@ export function form(validate_or_fn, maybe_fn) {
228
237
  value: (key) => {
229
238
  const { state } = get_request_store();
230
239
  const cache_key = __.id + '|' + JSON.stringify(key);
240
+ /** @type {RemoteForm<Input, Output> & { __: RemoteFormInternals }} */
231
241
  let instance = (state.remote.forms ??= new Map()).get(cache_key);
232
242
 
233
243
  if (!instance) {
234
- instance = create_instance(key);
235
- instance.__.id = `${__.id}/${encodeURIComponent(JSON.stringify(key))}`;
236
- instance.__.action_id = `${__.id}/${JSON.stringify(key)}`;
244
+ instance = /** @type {RemoteForm<Input, Output> & { __: RemoteFormInternals }} */ (
245
+ create_instance(key)
246
+ );
247
+ instance.__.id = __.id;
248
+ instance.__.key = JSON.stringify(key);
237
249
  instance.__.name = __.name;
238
250
 
239
251
  state.remote.forms.set(cache_key, instance);
@@ -254,8 +266,9 @@ export function form(validate_or_fn, maybe_fn) {
254
266
  * @param {{ issues?: InternalRemoteFormIssue[], input?: Record<string, any>, result: any }} output
255
267
  * @param {readonly StandardSchemaV1.Issue[]} issues
256
268
  * @param {FormData | null} form_data - null if the form is progressively enhanced
269
+ * @param {string} form_id - hash/name of the form
257
270
  */
258
- function handle_issues(output, issues, form_data) {
271
+ function handle_issues(output, issues, form_data, form_id) {
259
272
  output.issues = issues.map((issue) => normalize_issue(issue, true));
260
273
 
261
274
  // if it was a progressively-enhanced submission, we don't need
@@ -263,19 +276,17 @@ function handle_issues(output, issues, form_data) {
263
276
  if (form_data) {
264
277
  output.input = {};
265
278
 
266
- for (let key of form_data.keys()) {
279
+ for (const field_name of form_data.keys()) {
267
280
  // redact sensitive fields
268
- if (/^[.\]]?_/.test(key)) continue;
269
-
270
- const is_array = key.endsWith('[]');
271
- const values = form_data.getAll(key).filter((value) => typeof value === 'string');
281
+ if (/^[.\]]?_/.test(field_name)) continue;
272
282
 
273
- if (is_array) key = key.slice(0, -2);
283
+ const values = form_data.getAll(field_name).filter((value) => typeof value === 'string');
284
+ const field = parse_form_key(form_id, field_name);
274
285
 
275
286
  set_nested_value(
276
287
  /** @type {Record<string, any>} */ (output.input),
277
- key,
278
- is_array ? values : values[0]
288
+ field,
289
+ field.is_array ? values : values[0]
279
290
  );
280
291
  }
281
292
  }
@@ -1,7 +1,8 @@
1
1
  /** @import { RemoteResource, RemotePrerenderFunction } from '@sveltejs/kit' */
2
- /** @import { RemotePrerenderInputsGenerator, RemotePrerenderInternals, MaybePromise } from 'types' */
2
+ /** @import { RemoteFunctionResponse, RemotePrerenderInputsGenerator, RemotePrerenderInternals, MaybePromise } from 'types' */
3
3
  /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
4
- import { json, error } from '@sveltejs/kit';
4
+ import { json } from '@sveltejs/kit';
5
+ import { HttpError } from '@sveltejs/kit/internal';
5
6
  import { get_request_store } from '@sveltejs/kit/internal/server';
6
7
  import { stringify, stringify_remote_arg } from '../../../shared.js';
7
8
  import { noop } from '../../../../utils/functions.js';
@@ -99,24 +100,28 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
99
100
  const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ''}`;
100
101
 
101
102
  if (!state.prerendering && !__SVELTEKIT_DEV__ && !event.isRemoteRequest) {
103
+ /** @type {RemoteFunctionResponse | undefined} */
104
+ let prerendered;
105
+
102
106
  try {
103
107
  // TODO adapters can provide prerendered data more efficiently than
104
108
  // fetching from the public internet
105
- const response = await fetch(new URL(url, event.url.origin).href);
109
+ // `request.url` rather than `event.url`, which throws inside queries
110
+ const response = await fetch(new URL(url, event.request.url).href);
106
111
 
107
- if (!response.ok) {
108
- throw new Error('Prerendered response not found');
112
+ if (response.ok) {
113
+ prerendered = /** @type {RemoteFunctionResponse} */ (await response.json());
109
114
  }
115
+ } catch {
116
+ // not available prerendered, fallback to normal function
117
+ }
110
118
 
111
- const prerendered = /** @type {RemoteFunctionResponse} */ await response.json();
112
-
119
+ if (prerendered) {
113
120
  if (prerendered.type === 'error') {
114
- error(prerendered.status, prerendered.error);
121
+ throw new HttpError(prerendered.error);
115
122
  }
116
123
 
117
124
  return parse_remote_response(prerendered.data, state.transport)._;
118
- } catch {
119
- // not available prerendered, fallback to normal function
120
125
  }
121
126
  }
122
127
 
@@ -126,7 +131,13 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
126
131
  return /** @type {Promise<any>} */ (state.prerendering.remote_responses.get(url));
127
132
  }
128
133
 
129
- const promise = run_remote_function(event, state, false, () => validate(arg), fn);
134
+ const promise = run_remote_function(
135
+ event,
136
+ { ...state, is_in_remote_prerender: true },
137
+ false,
138
+ () => validate(arg),
139
+ fn
140
+ );
130
141
 
131
142
  if (state.prerendering) {
132
143
  state.prerendering.remote_responses.set(url, promise);
@@ -404,17 +404,18 @@ export function refresh(event, state, internals, payload, fn) {
404
404
  return;
405
405
  }
406
406
 
407
- if (!event.isRemoteRequest) {
408
- // or this is a no-JS form submission
407
+ if (!event.isRemoteRequest && state.is_in_remote_form_or_command) {
408
+ // ...or this is a no-JS (native) form submission, where the page re-renders
409
+ // anyway so there's no live client cache to apply a single-flight update to.
409
410
  return;
410
411
  }
411
412
 
412
413
  const key = create_remote_key(internals.id, payload);
413
414
 
414
415
  // `fn` is stored rather than invoked eagerly. The query is run at the end of
415
- // the command/form (in `collect_remote_data`), so that it observes any state
416
+ // the request (in `collect_remote_data`), so that it observes any state
416
417
  // mutations that happen after `refresh()` is called. If the developer re-awaits
417
- // the query before the command finishes, the cache entry created by that await
418
+ // the query before the request finishes, the cache entry created by that await
418
419
  // is reused instead of re-running the query.
419
420
  (state.remote.explicit ??= new Map()).set(key, {
420
421
  internals,
@@ -152,10 +152,10 @@ export function requested(query, limit) {
152
152
  for (const payload of skipped) {
153
153
  record_failure(
154
154
  payload,
155
- new HttpError(
156
- 400,
157
- `Requested refresh was rejected because it exceeded requested(${__.name}, ${limit}) limit`
158
- )
155
+ new HttpError({
156
+ status: 400,
157
+ message: `Requested refresh was rejected because it exceeded requested(${__.name}, ${limit}) limit`
158
+ })
159
159
  );
160
160
  }
161
161
 
@@ -37,7 +37,7 @@ export function create_validator(validate_or_fn, maybe_fn) {
37
37
  if (result.issues) {
38
38
  const body = await state.handleValidationError({
39
39
  issues: result.issues,
40
- event
40
+ event: state.original_event ?? event
41
41
  });
42
42
  error(body.status ?? 400, body);
43
43
  }
@@ -101,40 +101,58 @@ export function parse_remote_response(data, transport) {
101
101
  * @returns {RequestStore}
102
102
  */
103
103
  function derive_remote_function_event(event, state, allow_cookies) {
104
- return {
105
- event: {
106
- ...event,
107
- setHeaders: () => {
108
- throw new Error('setHeaders is not allowed in remote functions');
104
+ /** @type {RequestEvent} */
105
+ const derived = {
106
+ ...event,
107
+ setHeaders: () => {
108
+ throw new Error('setHeaders is not allowed in remote functions');
109
+ },
110
+ cookies: {
111
+ ...event.cookies,
112
+ set: (name, value, opts) => {
113
+ if (!allow_cookies) {
114
+ throw new Error('Cannot set cookies in `query` or `prerender` functions');
115
+ }
116
+
117
+ if (opts.path && !opts.path.startsWith('/')) {
118
+ throw new Error('Cookies set in remote functions must have an absolute path');
119
+ }
120
+
121
+ return event.cookies.set(name, value, opts);
109
122
  },
110
- cookies: {
111
- ...event.cookies,
112
- set: (name, value, opts) => {
113
- if (!allow_cookies) {
114
- throw new Error('Cannot set cookies in `query` or `prerender` functions');
115
- }
116
-
117
- if (opts.path && !opts.path.startsWith('/')) {
118
- throw new Error('Cookies set in remote functions must have an absolute path');
119
- }
120
-
121
- return event.cookies.set(name, value, opts);
122
- },
123
- delete: (name, opts) => {
124
- if (!allow_cookies) {
125
- throw new Error('Cannot delete cookies in `query` or `prerender` functions');
126
- }
127
-
128
- if (opts.path && !opts.path.startsWith('/')) {
129
- throw new Error('Cookies deleted in remote functions must have an absolute path');
130
- }
131
-
132
- return event.cookies.delete(name, opts);
123
+ delete: (name, opts) => {
124
+ if (!allow_cookies) {
125
+ throw new Error('Cannot delete cookies in `query` or `prerender` functions');
126
+ }
127
+
128
+ if (opts.path && !opts.path.startsWith('/')) {
129
+ throw new Error('Cookies deleted in remote functions must have an absolute path');
133
130
  }
131
+
132
+ return event.cookies.delete(name, opts);
134
133
  }
135
- },
134
+ }
135
+ };
136
+
137
+ if (state.is_in_remote_query) {
138
+ for (const property of ['url', 'params', 'route']) {
139
+ // non-enumerable so spreading for a nested derivation doesn't invoke the getter
140
+ Object.defineProperty(derived, property, {
141
+ enumerable: false,
142
+ get() {
143
+ throw new Error(
144
+ `Cannot access event.${property} in a query. Pass the value as an argument to the query instead`
145
+ );
146
+ }
147
+ });
148
+ }
149
+ }
150
+
151
+ return {
152
+ event: derived,
136
153
  state: {
137
154
  ...state,
155
+ original_event: state.original_event ?? event,
138
156
  is_in_remote_function: true
139
157
  }
140
158
  };