@sveltejs/kit 3.0.0-next.24 → 3.0.0-next.27

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 (71) hide show
  1. package/package.json +16 -10
  2. package/src/constants.js +6 -0
  3. package/src/core/adapt/builder.js +128 -33
  4. package/src/core/adapt/index.js +3 -0
  5. package/src/core/config/index.js +4 -7
  6. package/src/core/env.js +39 -14
  7. package/src/core/generate_manifest/index.js +42 -43
  8. package/src/core/postbuild/analyse.js +4 -4
  9. package/src/core/postbuild/fallback.js +1 -1
  10. package/src/core/postbuild/prerender.js +4 -4
  11. package/src/core/sync/create_manifest_data/index.js +0 -1
  12. package/src/core/sync/write_app_manifest.js +3 -2
  13. package/src/core/sync/write_server.js +3 -17
  14. package/src/core/utils.js +10 -0
  15. package/src/exports/adapter.js +22 -0
  16. package/src/exports/public.d.ts +101 -49
  17. package/src/exports/vite/build/index.js +1173 -0
  18. package/src/exports/vite/build/remote.js +4 -4
  19. package/src/exports/vite/build/service-worker.js +98 -0
  20. package/src/exports/vite/dev/generate_manifest.js +308 -0
  21. package/src/exports/vite/dev/index.js +40 -282
  22. package/src/exports/vite/index.js +156 -1717
  23. package/src/exports/vite/plugins/env-vars.js +53 -34
  24. package/src/exports/vite/plugins/guard.js +217 -0
  25. package/src/exports/vite/plugins/remote.js +237 -0
  26. package/src/exports/vite/preview/index.js +8 -8
  27. package/src/exports/vite/static_analysis/index.js +15 -15
  28. package/src/exports/vite/utils.js +98 -1
  29. package/src/runner.js +4 -3
  30. package/src/runtime/app/paths/server.js +2 -2
  31. package/src/runtime/app/server/index.js +3 -3
  32. package/src/runtime/app/server/public.d.ts +114 -57
  33. package/src/runtime/app/server/remote/query.js +2 -2
  34. package/src/runtime/app/server/remote/requested.js +31 -15
  35. package/src/runtime/app/state/client.svelte.js +3 -1
  36. package/src/runtime/client/client.js +43 -198
  37. package/src/runtime/client/fetcher.js +6 -6
  38. package/src/runtime/client/focus.js +120 -0
  39. package/src/runtime/client/remote-functions/command.svelte.js +20 -9
  40. package/src/runtime/client/remote-functions/form.svelte.js +12 -7
  41. package/src/runtime/client/remote-functions/query/instance.svelte.js +19 -5
  42. package/src/runtime/client/remote-functions/shared.svelte.js +22 -1
  43. package/src/runtime/client/scroll.js +39 -0
  44. package/src/runtime/client/utils.js +28 -0
  45. package/src/runtime/form-utils.js +254 -264
  46. package/src/runtime/server/data/index.js +14 -36
  47. package/src/runtime/server/errors.js +7 -10
  48. package/src/runtime/server/fetch.js +21 -25
  49. package/src/runtime/server/index.js +35 -31
  50. package/src/runtime/server/internal.js +34 -5
  51. package/src/runtime/server/page/actions.js +6 -8
  52. package/src/runtime/server/page/data_serializer.js +6 -10
  53. package/src/runtime/server/page/index.js +18 -28
  54. package/src/runtime/server/page/load_data.js +2 -2
  55. package/src/runtime/server/page/render.js +22 -40
  56. package/src/runtime/server/page/respond_with_error.js +10 -13
  57. package/src/runtime/server/page/server_routing.js +21 -27
  58. package/src/runtime/server/remote-functions.js +136 -136
  59. package/src/runtime/server/respond.js +66 -64
  60. package/src/runtime/server/state.js +2 -0
  61. package/src/runtime/server/utils.js +4 -11
  62. package/src/runtime/shared.js +9 -0
  63. package/src/runtime/utils.js +41 -0
  64. package/src/types/ambient-private.d.ts +1 -2
  65. package/src/types/global-private.d.ts +8 -0
  66. package/src/types/internal.d.ts +56 -17
  67. package/src/utils/filesystem.js +44 -28
  68. package/src/utils/streaming.js +5 -15
  69. package/src/version.js +1 -1
  70. package/types/index.d.ts +235 -93
  71. package/types/index.d.ts.map +9 -2
@@ -1,7 +1,7 @@
1
1
  /** @import { PageOptions } from './types.js' */
2
+ /** @import { ESTree } from 'vite' */
2
3
  import path from 'node:path';
3
- import { tsPlugin } from '@sveltejs/acorn-typescript';
4
- import { Parser } from 'acorn';
4
+ import { parseSync } from 'vite';
5
5
  import { read } from '../../../utils/filesystem.js';
6
6
 
7
7
  export const valid_page_options_array = /** @type {const} */ ([
@@ -21,8 +21,6 @@ const skip_parsing_regex = new RegExp(
21
21
  `${Array.from(valid_page_options).join('|')}|(?:export[\\s\\n]+\\*[\\s\\n]+from)`
22
22
  );
23
23
 
24
- const parser = Parser.extend(tsPlugin());
25
-
26
24
  /**
27
25
  * Collects page options from a +page.js/+layout.js file, ignoring reassignments
28
26
  * and using the declared value (except for load functions, for which the value is `true`).
@@ -39,15 +37,13 @@ export function statically_analyse_page_options(filename, input) {
39
37
  }
40
38
 
41
39
  try {
42
- const source = parser.parse(input, {
43
- sourceType: 'module',
44
- ecmaVersion: 'latest'
45
- });
40
+ const source = parseSync(filename, input, { sourceType: 'module' });
41
+ if (source.errors.length) throw new Error(source.errors[0].message);
46
42
 
47
- /** @type {Map<string, import('acorn').Literal['value']>} */
43
+ /** @type {Map<string, Extract<ESTree.Expression, { type: 'Literal' }>['value']>} */
48
44
  const page_options = new Map();
49
45
 
50
- for (const statement of source.body) {
46
+ for (const statement of source.program.body) {
51
47
  // ignore export all declarations with aliases that are not page options
52
48
  if (
53
49
  statement.type === 'ExportAllDeclaration' &&
@@ -82,7 +78,7 @@ export function statically_analyse_page_options(filename, input) {
82
78
  export_specifiers.set(get_name(specifier.local), exported_name);
83
79
  }
84
80
 
85
- for (const statement of source.body) {
81
+ for (const statement of source.program.body) {
86
82
  switch (statement.type) {
87
83
  case 'ImportDeclaration': {
88
84
  for (const import_specifier of statement.specifiers) {
@@ -105,7 +101,10 @@ export function statically_analyse_page_options(filename, input) {
105
101
 
106
102
  // class and function declarations
107
103
  if (declaration.type !== 'VariableDeclaration') {
108
- if (export_specifiers.has(declaration.id.name)) {
104
+ if (
105
+ declaration.id?.type === 'Identifier' &&
106
+ export_specifiers.has(declaration.id.name)
107
+ ) {
109
108
  return null;
110
109
  }
111
110
  break;
@@ -156,9 +155,10 @@ export function statically_analyse_page_options(filename, input) {
156
155
 
157
156
  // class and function declarations
158
157
  if (statement.declaration.type !== 'VariableDeclaration') {
159
- if (valid_page_options.has(statement.declaration.id.name)) {
158
+ const { id } = statement.declaration;
159
+ if (id?.type === 'Identifier' && valid_page_options.has(id.name)) {
160
160
  // Special case: We only want to know that 'load' is exported (in a way that doesn't cause truthy checks in other places to trigger)
161
- if (statement.declaration.id.name === 'load') {
161
+ if (id.name === 'load') {
162
162
  page_options.set('load', null);
163
163
  } else {
164
164
  return null;
@@ -202,7 +202,7 @@ export function statically_analyse_page_options(filename, input) {
202
202
  }
203
203
 
204
204
  /**
205
- * @param {import('acorn').Identifier | import('acorn').Literal} node
205
+ * @param {ESTree.ModuleExportName} node
206
206
  * @returns {string}
207
207
  */
208
208
  function get_name(node) {
@@ -1,5 +1,9 @@
1
+ /** @import { UserConfig } from 'vite' */
2
+ /** @import { EnforcedConfig } from './types.js' */
1
3
  import fs from 'node:fs';
2
4
  import path from 'node:path';
5
+ import process from 'node:process';
6
+ import { styleText } from 'node:util';
3
7
  import { posixify } from '../../utils/os.js';
4
8
  import { negotiate } from '../../utils/http.js';
5
9
  import { escape_html } from '../../utils/escape.js';
@@ -130,7 +134,7 @@ export function normalize_id(id, aliases, cwd) {
130
134
  }
131
135
  }
132
136
 
133
- if (id.startsWith(cwd)) {
137
+ if (id.startsWith(cwd + '/')) {
134
138
  id = path.relative(cwd, id);
135
139
  }
136
140
 
@@ -227,3 +231,96 @@ export function error_for_missing_config(feature_name, path, value) {
227
231
  `
228
232
  );
229
233
  }
234
+
235
+ /** @type {EnforcedConfig} */
236
+ export const enforced_config = {
237
+ appType: true,
238
+ base: true,
239
+ build: {
240
+ cssCodeSplit: true,
241
+ emptyOutDir: true,
242
+ lib: {
243
+ entry: true,
244
+ name: true,
245
+ formats: true
246
+ },
247
+ manifest: true,
248
+ outDir: true,
249
+ rolldownOptions: {
250
+ input: true,
251
+ output: {
252
+ format: true,
253
+ entryFileNames: true,
254
+ chunkFileNames: true,
255
+ assetFileNames: true
256
+ },
257
+ preserveEntrySignatures: true
258
+ },
259
+ ssr: true
260
+ },
261
+ publicDir: true,
262
+ resolve: {
263
+ alias: {
264
+ $app: true,
265
+ $env: true,
266
+ '<sveltekit:generated>': true
267
+ }
268
+ }
269
+ };
270
+
271
+ /**
272
+ * @param {UserConfig} config
273
+ * @param {UserConfig} resolved_config
274
+ */
275
+ export function warn_overridden_config(config, resolved_config) {
276
+ const overridden = find_overridden_config(config, resolved_config, enforced_config, '', []);
277
+
278
+ if (overridden.length > 0) {
279
+ console.error(
280
+ styleText(
281
+ ['bold', 'red'],
282
+ 'The following Vite config options will be overridden by SvelteKit:'
283
+ ) + overridden.map((key) => `\n - ${key}`).join('')
284
+ );
285
+ }
286
+ }
287
+
288
+ /**
289
+ * @param {Record<string, any>} config
290
+ * @param {Record<string, any>} resolved_config
291
+ * @param {EnforcedConfig} enforced_config
292
+ * @param {string} path
293
+ * @param {string[]} out used locally to compute the return value
294
+ */
295
+ export function find_overridden_config(config, resolved_config, enforced_config, path, out) {
296
+ if (config == null || resolved_config == null) {
297
+ return out;
298
+ }
299
+
300
+ for (const key in enforced_config) {
301
+ if (typeof config === 'object' && key in config && key in resolved_config) {
302
+ const enforced = enforced_config[key];
303
+ const resolved = resolved_config[key];
304
+
305
+ if (enforced === true) {
306
+ if (comparable(config[key]) !== comparable(resolved)) {
307
+ out.push(path + key);
308
+ }
309
+ } else {
310
+ find_overridden_config(config[key], resolved, enforced, path + key + '.', out);
311
+ }
312
+ }
313
+ }
314
+ return out;
315
+ }
316
+
317
+ /**
318
+ * Normalizes a config value for comparison, since Windows paths may use backslashes
319
+ * and differ in casing (e.g. the drive letter) depending on where they came from.
320
+ * @param {any} value
321
+ */
322
+ export function comparable(value) {
323
+ if (typeof value !== 'string') return value;
324
+ const normalized = posixify(value);
325
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
326
+ }
package/src/runner.js CHANGED
@@ -1,13 +1,14 @@
1
+ /** @import * as vite from 'vite' */
1
2
  /** @import { ViteDevServer } from 'vite' */
2
3
 
3
4
  /**
4
- * @param {typeof import('vite')} vite the peer resolved vite module
5
+ * @param {typeof vite} vite the vite module that created the server
5
6
  * @param {ViteDevServer} server
6
7
  */
7
- export function get_runner(vite, server) {
8
+ export function get_runner({ isRunnableDevEnvironment }, server) {
8
9
  // `isRunnableDevEnvironment` does an `instanceof` check and will fail if
9
10
  // we're using different instances of Vite
10
- if (!vite.isRunnableDevEnvironment(server.environments.ssr)) {
11
+ if (!isRunnableDevEnvironment(server.environments.ssr)) {
11
12
  throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment');
12
13
  }
13
14
 
@@ -83,8 +83,8 @@ export async function match(url) {
83
83
  resolved_path = resolved_path.slice(base.length) || '/';
84
84
  }
85
85
 
86
- const matchers = await manifest._.matchers();
87
- const result = find_route(resolved_path, manifest._.routes, matchers);
86
+ const matchers = await manifest.matchers();
87
+ const result = find_route(resolved_path, manifest.routes, matchers);
88
88
 
89
89
  if (result) {
90
90
  return {
@@ -59,9 +59,9 @@ export function read(asset) {
59
59
  : asset.slice(assets.length + 1)
60
60
  );
61
61
 
62
- if (file in manifest._.server_assets) {
63
- const length = manifest._.server_assets[file];
64
- const type = manifest.mimeTypes[file.slice(file.lastIndexOf('.'))];
62
+ if (file in manifest.server_assets) {
63
+ const length = manifest.server_assets[file];
64
+ const type = manifest.mime_types[file.slice(file.lastIndexOf('.'))];
65
65
 
66
66
  return new Response(read_implementation(file), {
67
67
  headers: {
@@ -3,6 +3,14 @@ import { DeepPartial, IsAny, MaybePromise } from 'types';
3
3
 
4
4
  export * from './index.js';
5
5
 
6
+ type ImageInputValue = { x: number; y: number };
7
+
8
+ type IsImageInputValue<T> = T extends ImageInputValue
9
+ ? Exclude<keyof T, keyof ImageInputValue> extends never
10
+ ? true
11
+ : false
12
+ : false;
13
+
6
14
  // If T is unknown or has an index signature, the types below will recurse indefinitely and create giant unions that TS can't handle
7
15
  type WillRecurseIndefinitely<T> = unknown extends T ? true : string extends keyof T ? true : false;
8
16
 
@@ -29,7 +37,7 @@ type InputTypeMap = {
29
37
  submit: string | number | boolean;
30
38
  button: string;
31
39
  reset: string;
32
- image: string;
40
+ image: ImageInputValue;
33
41
  select: string;
34
42
  'select multiple': string[];
35
43
  'file multiple': File[];
@@ -41,7 +49,7 @@ export type RemoteFormFieldType<T> = {
41
49
  }[keyof InputTypeMap];
42
50
 
43
51
  // Input element properties based on type
44
- type InputElementProps<T extends keyof InputTypeMap> = T extends 'checkbox' | 'radio'
52
+ type InputElementProps<T extends keyof InputTypeMap, Value> = T extends 'checkbox' | 'radio'
45
53
  ? {
46
54
  name: string;
47
55
  type: T;
@@ -51,45 +59,51 @@ type InputElementProps<T extends keyof InputTypeMap> = T extends 'checkbox' | 'r
51
59
  set checked(value: boolean);
52
60
  readonly defaultChecked?: boolean;
53
61
  }
54
- : T extends 'file'
62
+ : T extends 'image'
55
63
  ? {
56
64
  name: string;
57
- type: 'file';
65
+ type: 'image';
58
66
  'aria-invalid': boolean | 'false' | 'true' | undefined;
59
- get files(): FileList | null;
60
- set files(v: FileList | null);
61
67
  }
62
- : T extends 'select'
68
+ : T extends 'file'
63
69
  ? {
64
70
  name: string;
71
+ type: 'file';
65
72
  'aria-invalid': boolean | 'false' | 'true' | undefined;
66
- get value(): string;
67
- set value(v: string);
73
+ get files(): FileList | null;
74
+ set files(v: FileList | null);
68
75
  }
69
- : T extends 'select multiple'
76
+ : T extends 'select'
70
77
  ? {
71
78
  name: string;
72
- multiple: true;
73
79
  'aria-invalid': boolean | 'false' | 'true' | undefined;
74
- get value(): string[];
75
- set value(v: string[]);
80
+ get value(): string;
81
+ set value(v: string);
76
82
  }
77
- : T extends 'text'
83
+ : T extends 'select multiple'
78
84
  ? {
79
85
  name: string;
86
+ multiple: true;
80
87
  'aria-invalid': boolean | 'false' | 'true' | undefined;
81
- get value(): string | number;
82
- set value(v: string | number);
83
- readonly defaultValue?: string | number;
88
+ get value(): string[];
89
+ set value(v: string[]);
84
90
  }
85
- : {
86
- name: string;
87
- type: T;
88
- 'aria-invalid': boolean | 'false' | 'true' | undefined;
89
- get value(): string | number;
90
- set value(v: string | number);
91
- readonly defaultValue?: string | number;
92
- };
91
+ : T extends 'text'
92
+ ? {
93
+ name: string;
94
+ 'aria-invalid': boolean | 'false' | 'true' | undefined;
95
+ get value(): Value extends string ? string : string | number;
96
+ set value(v: Value extends string ? string : string | number);
97
+ readonly defaultValue?: Value extends string ? string : string | number;
98
+ }
99
+ : {
100
+ name: string;
101
+ type: T;
102
+ 'aria-invalid': boolean | 'false' | 'true' | undefined;
103
+ get value(): string | number;
104
+ set value(v: string | number);
105
+ readonly defaultValue?: string | number;
106
+ };
93
107
 
94
108
  type RemoteFormFieldMethods<T> = {
95
109
  /** The values that will be submitted */
@@ -113,23 +127,34 @@ type ValueOfUnionKey<T, K extends PropertyKey> = T extends unknown
113
127
  : never
114
128
  : never;
115
129
 
116
- export type RemoteFormFieldValue = string | string[] | number | boolean | File | File[];
130
+ export type RemoteFormFieldValue =
131
+ | string
132
+ | string[]
133
+ | number
134
+ | boolean
135
+ | File
136
+ | File[]
137
+ | ImageInputValue;
117
138
 
118
139
  type AsArgs<Type extends keyof InputTypeMap, Value> = Type extends 'checkbox'
119
140
  ? Value extends string[]
120
- ? [type: Type, value: Value[number] | (string & {})]
141
+ ? [type: Type, value: Value[number] | (string & {}), checked?: boolean]
121
142
  : Value extends boolean
122
- ? [type: Type] | [type: Type, value: boolean]
123
- : [type: Type] | [type: Type, value: Value | (string & {})]
124
- : Type extends 'submit' | 'hidden'
125
- ? Value extends string
126
- ? [type: Type, value: Value | (string & {})]
127
- : [type: Type, value: Value]
128
- : Type extends 'radio'
129
- ? [type: Type, value: Value | (string & {})]
130
- : Type extends 'file' | 'file multiple'
131
- ? [type: Type]
132
- : [type: Type] | [type: Type, value: Value | undefined];
143
+ ? [type: Type, value?: boolean]
144
+ : [type: Type, value?: Value]
145
+ : Type extends 'image'
146
+ ? [type: Type]
147
+ : Type extends 'submit' | 'hidden'
148
+ ? Value extends string
149
+ ? [type: Type, value: Value | (string & {})]
150
+ : [type: Type, value: Value]
151
+ : Type extends 'radio'
152
+ ? [type: Type, value: Value | (string & {}), checked?: boolean]
153
+ : Type extends 'file' | 'file multiple'
154
+ ? [type: Type]
155
+ : [type: Type, value?: Value];
156
+
157
+ type WidenLiteralString<T> = T extends string ? (string extends T ? T : string) : T;
133
158
 
134
159
  /**
135
160
  * Form field accessor type that provides name(), value(), and issues() methods
@@ -145,7 +170,9 @@ export type RemoteFormField<Value extends RemoteFormFieldValue> = RemoteFormFiel
145
170
  * <input {...myForm.fields.myBoolean.as('checkbox')} />
146
171
  * ```
147
172
  */
148
- as<T extends RemoteFormFieldType<Value>>(...args: AsArgs<T, Value>): InputElementProps<T>;
173
+ as<T extends RemoteFormFieldType<Value>>(
174
+ ...args: AsArgs<T, Value>
175
+ ): InputElementProps<T, WidenLiteralString<Value>>;
149
176
  };
150
177
 
151
178
  type RemoteFormFieldContainer<Value> = RemoteFormFieldMethods<Value> & {
@@ -166,12 +193,15 @@ type UnknownField<Value> = RemoteFormFieldMethods<Value> & {
166
193
  * <input {...myForm.fields.myBoolean.as('checkbox')} />
167
194
  * ```
168
195
  */
169
- as<T extends RemoteFormFieldType<Value>>(...args: AsArgs<T, Value>): InputElementProps<T>;
196
+ as<T extends RemoteFormFieldType<Value>>(...args: AsArgs<T, Value>): InputElementProps<T, Value>;
170
197
  } & {
171
198
  [key: string | number]: UnknownField<any>;
172
199
  };
173
200
 
174
- type RemoteFormFieldsRoot<Input extends RemoteFormInput | void> =
201
+ type RemoteFormFieldsRoot<
202
+ Input extends RemoteFormInput | void,
203
+ Original extends [RemoteFormInput | void] = [Input]
204
+ > =
175
205
  IsAny<Input> extends true
176
206
  ? RecursiveFormFields
177
207
  : Input extends void
@@ -181,7 +211,11 @@ type RemoteFormFieldsRoot<Input extends RemoteFormInput | void> =
181
211
  /** Validation issues belonging to this or any of the fields that belong to it, if any */
182
212
  allIssues(): RemoteFormIssue[] | undefined;
183
213
  }
184
- : RemoteFormFields<Input>;
214
+ : WillRecurseIndefinitely<Input> extends true
215
+ ? RecursiveFormFields
216
+ : RemoteFormFieldContainer<Original[0]> & {
217
+ [K in KeysOfUnion<Original[0]>]-?: RemoteFormFields<ValueOfUnionKey<Original[0], K>>;
218
+ };
185
219
 
186
220
  /**
187
221
  * Recursive type to build form fields structure with proxy access
@@ -191,20 +225,25 @@ export type RemoteFormFields<T> =
191
225
  ? RecursiveFormFields
192
226
  : NonNullable<T> extends string | number | boolean | File
193
227
  ? RemoteFormField<NonNullable<T>>
194
- : // [NonNullable<T>] is used to prevent distributing over union while still allowing
195
- // nullable wrappers (e.g. `string[] | undefined` from a schema with `.default([])`)
196
- // to be treated as arrays; only the last condition should distribute over unions
197
- [NonNullable<T>] extends [string[] | File[]]
198
- ? RemoteFormField<NonNullable<T>> & {
199
- [K in number]: RemoteFormField<NonNullable<T>[number]>;
200
- }
201
- : [NonNullable<T>] extends [Array<infer U>]
202
- ? RemoteFormFieldContainer<NonNullable<T>> & {
203
- [K in number]: RemoteFormFields<U>;
204
- }
205
- : RemoteFormFieldContainer<T> & {
228
+ : IsImageInputValue<NonNullable<T>> extends true
229
+ ? RemoteFormField<NonNullable<T> & ImageInputValue> &
230
+ Pick<RemoteFormFieldContainer<T>, 'allIssues'> & {
206
231
  [K in KeysOfUnion<T>]-?: RemoteFormFields<ValueOfUnionKey<T, K>>;
207
- };
232
+ }
233
+ : // [NonNullable<T>] is used to prevent distributing over union while still allowing
234
+ // nullable wrappers (e.g. `string[] | undefined` from a schema with `.default([])`)
235
+ // to be treated as arrays; only the last condition should distribute over unions
236
+ [NonNullable<T>] extends [string[] | File[]]
237
+ ? RemoteFormField<NonNullable<T>> & {
238
+ [K in number]: RemoteFormField<NonNullable<T>[number]>;
239
+ }
240
+ : [NonNullable<T>] extends [Array<infer U>]
241
+ ? RemoteFormFieldContainer<NonNullable<T>> & {
242
+ [K in number]: RemoteFormFields<U>;
243
+ }
244
+ : RemoteFormFieldContainer<T> & {
245
+ [K in KeysOfUnion<T>]-?: RemoteFormFields<ValueOfUnionKey<T, K>>;
246
+ };
208
247
 
209
248
  // By breaking this out into its own type, we avoid the TS recursion depth limit
210
249
  type RecursiveFormFields = RemoteFormFieldContainer<any> & {
@@ -273,7 +312,17 @@ export type RemoteFormEnhanceCallback<
273
312
  /**
274
313
  * The type of a remote `form` function. See [Remote functions](https://svelte.dev/docs/kit/remote-functions#form) for full documentation.
275
314
  */
276
- export type RemoteForm<Input extends RemoteFormInput | void, Output> = {
315
+ export type RemoteForm<Input extends RemoteFormInput | void, Output> = RemoteForm_<
316
+ Input,
317
+ Output,
318
+ [Input]
319
+ >;
320
+
321
+ type RemoteForm_<
322
+ Input extends RemoteFormInput | void,
323
+ Output,
324
+ Original extends [RemoteFormInput | void]
325
+ > = {
277
326
  /** Attachment that sets up an event handler that intercepts the form submission on the client to prevent a full page reload */
278
327
  [attachment: symbol]: (node: HTMLFormElement) => void;
279
328
  method: 'POST';
@@ -327,7 +376,7 @@ export type RemoteForm<Input extends RemoteFormInput | void, Output> = {
327
376
  /** True if the form has been submitted at least once, and hasn't been reset since */
328
377
  get submitted(): boolean;
329
378
  /** Access form fields using object notation */
330
- fields: RemoteFormFieldsRoot<Input>;
379
+ fields: RemoteFormFieldsRoot<Input, Original>;
331
380
  };
332
381
 
333
382
  /**
@@ -459,6 +508,8 @@ export type RemoteLiveQueryFunction<Input, Output, _Validated = Input> = (
459
508
  export type RequestedEntry<Validated, Output> = {
460
509
  arg: Validated;
461
510
  query: RemoteQuery<Output>;
511
+ /** Explicitly ignore this requested update. */
512
+ ignore: () => void;
462
513
  };
463
514
 
464
515
  /**
@@ -470,6 +521,8 @@ export type RequestedEntry<Validated, Output> = {
470
521
  export type RemoteLiveQueryRequestedEntry<Validated, Output> = {
471
522
  arg: Validated;
472
523
  query: RemoteLiveQuery<Output>;
524
+ /** Explicitly ignore this requested update. */
525
+ ignore: () => void;
473
526
  };
474
527
 
475
528
  export type RemoteQueryRequestedResult<Validated, Output> = Iterable<
@@ -488,6 +541,8 @@ export type RemoteQueryRequestedResult<Validated, Output> = Iterable<
488
541
  * ```
489
542
  */
490
543
  refreshAll: () => Promise<void>;
544
+ /** Explicitly ignore all updates selected by this `requested` invocation. */
545
+ ignoreAll: () => Promise<void>;
491
546
  };
492
547
 
493
548
  export type RemoteLiveQueryRequestedResult<Validated, Output> = Iterable<
@@ -506,6 +561,8 @@ export type RemoteLiveQueryRequestedResult<Validated, Output> = Iterable<
506
561
  * ```
507
562
  */
508
563
  reconnectAll: () => Promise<void>;
564
+ /** Explicitly ignore all updates selected by this `requested` invocation. */
565
+ ignoreAll: () => Promise<void>;
509
566
  };
510
567
 
511
568
  export type RequestedResult<Validated, Output> =
@@ -330,7 +330,7 @@ function batch(validate_or_fn, maybe_fn) {
330
330
  id: '',
331
331
  name: '',
332
332
  validate,
333
- run: async (args, options) => {
333
+ run: async (args) => {
334
334
  const { event, state } = get_request_store();
335
335
 
336
336
  return run_remote_function(
@@ -347,7 +347,7 @@ function batch(validate_or_fn, maybe_fn) {
347
347
  const data = get_result(arg, i);
348
348
  return { type: 'result', data };
349
349
  } catch (error) {
350
- const transformed = await handle_error_and_jsonify(event, state, options, error);
350
+ const transformed = await handle_error_and_jsonify(event, state, error);
351
351
 
352
352
  return {
353
353
  type: 'error',
@@ -1,8 +1,7 @@
1
1
  /** @import { RemoteLiveQuery, RemoteLiveQueryFunction, RemoteQuery, RemoteQueryFunction, RequestedResult, RemoteQueryRequestedResult, RemoteLiveQueryRequestedResult } from '$app/server' */
2
2
  /** @import { MaybePromise, RemoteAnyQueryInternals } from 'types' */
3
- import { HttpError } from '@sveltejs/kit/internal';
4
3
  import { get_request_store } from '@sveltejs/kit/internal/server';
5
- import { parse_remote_arg } from '../../../shared.js';
4
+ import { create_remote_key, parse_remote_arg } from '../../../shared.js';
6
5
  import { noop } from '../../../../utils/functions.js';
7
6
  import { get_cache } from './shared.js';
8
7
  import { refresh } from './query.js';
@@ -121,7 +120,19 @@ export function requested(query, limit) {
121
120
  const __ = internals;
122
121
 
123
122
  const requested = state.remote.requested;
124
- const payloads = requested?.get(__.id) ?? [];
123
+ const payloads = requested?.get(__.id) ?? new Set();
124
+ const ignored = (state.remote.ignored ??= new Set());
125
+
126
+ /** @param {string} payload */
127
+ const consume = (payload) => {
128
+ payloads.delete(payload);
129
+ if (payloads.size === 0) requested?.delete(__.id);
130
+ };
131
+
132
+ /** @param {string} payload */
133
+ const create_ignore = (payload) => () => {
134
+ ignored.add(create_remote_key(__.id, payload));
135
+ };
125
136
 
126
137
  // note: don't initialize these maps here -- they will be initialized by the
127
138
  // command/form wrapper when we enter them, and if we initialize them here
@@ -132,7 +143,7 @@ export function requested(query, limit) {
132
143
  'requested(...) can only be called in the context of a command/form remote function'
133
144
  );
134
145
  }
135
- const [selected, skipped] = split_limit(payloads, limit);
146
+ const [selected, skipped] = split_limit([...payloads], limit);
136
147
 
137
148
  /**
138
149
  * Registers the failure exactly like `.set()` registers a value: the error record
@@ -149,19 +160,12 @@ export function requested(query, limit) {
149
160
  refresh(event, state, __, payload, () => promise);
150
161
  };
151
162
 
152
- for (const payload of skipped) {
153
- record_failure(
154
- payload,
155
- new HttpError({
156
- status: 400,
157
- message: `Requested refresh was rejected because it exceeded requested(${__.name}, ${limit}) limit`
158
- })
159
- );
160
- }
163
+ for (const payload of skipped) consume(payload);
161
164
 
162
165
  const result = {
163
166
  *[Symbol.iterator]() {
164
167
  for (const payload of selected) {
168
+ consume(payload);
165
169
  try {
166
170
  const parsed = parse_remote_arg(payload);
167
171
  const validated = __.validate(parsed);
@@ -173,7 +177,11 @@ export function requested(query, limit) {
173
177
  );
174
178
  }
175
179
 
176
- yield { arg: validated, query: __.bind(payload, validated) };
180
+ yield {
181
+ arg: validated,
182
+ query: __.bind(payload, validated),
183
+ ignore: create_ignore(payload)
184
+ };
177
185
  } catch (error) {
178
186
  record_failure(payload, error);
179
187
  continue;
@@ -182,10 +190,15 @@ export function requested(query, limit) {
182
190
  },
183
191
  async *[Symbol.asyncIterator]() {
184
192
  yield* race_all(selected, async (payload) => {
193
+ consume(payload);
185
194
  try {
186
195
  const parsed = parse_remote_arg(payload);
187
196
  const validated = await __.validate(parsed);
188
- return { arg: validated, query: __.bind(payload, validated) };
197
+ return {
198
+ arg: validated,
199
+ query: __.bind(payload, validated),
200
+ ignore: create_ignore(payload)
201
+ };
189
202
  } catch (error) {
190
203
  record_failure(payload, error);
191
204
  throw new Error(`Skipping ${__.name}(${payload})`, { cause: error });
@@ -209,6 +222,9 @@ export function requested(query, limit) {
209
222
  for await (const { query } of result) {
210
223
  void (/** @type {RemoteLiveQuery<Output>} */ (query).reconnect());
211
224
  }
225
+ },
226
+ async ignoreAll() {
227
+ for await (const { ignore } of result) ignore();
212
228
  }
213
229
  };
214
230
 
@@ -1,6 +1,7 @@
1
1
  /** @import { Navigation } from '$app/navigation' */
2
2
  /** @import { Page } from '$app/state' */
3
3
  import { DEV } from 'esm-env';
4
+ import { untrack } from 'svelte';
4
5
  import { assets } from '#app/paths';
5
6
  import { version } from '$app/env';
6
7
 
@@ -195,7 +196,8 @@ if (!DEV && interval) {
195
196
  */
196
197
  export function notify_version(new_version) {
197
198
  if (__SVELTEKIT_APP_VERSION_CHECKS_ENABLED__ && new_version) {
198
- _updated ||= new_version !== version;
199
+ // a tracked write inside a restored reaction context throws `state_unsafe_mutation`
200
+ untrack(() => (_updated ||= new_version !== version));
199
201
  }
200
202
  }
201
203