@sveltejs/kit 2.43.7 → 2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "2.43.7",
3
+ "version": "2.44.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -41,7 +41,7 @@
41
41
  "@types/set-cookie-parser": "^2.4.7",
42
42
  "dts-buddy": "^0.6.2",
43
43
  "rollup": "^4.14.2",
44
- "svelte": "^5.39.3",
44
+ "svelte": "^5.39.8",
45
45
  "svelte-preprocess": "^6.0.0",
46
46
  "typescript": "^5.3.3",
47
47
  "vite": "^6.3.5",
@@ -320,7 +320,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
320
320
  // avoid triggering `filterSerializeResponseHeaders` guard
321
321
  const headers = Object.fromEntries(response.headers);
322
322
 
323
- if (config.prerender.crawl && headers['content-type'] === 'text/html') {
323
+ // if it's a 200 HTML response, crawl it. Skip error responses, as we don't save those
324
+ if (response.ok && config.prerender.crawl && headers['content-type'] === 'text/html') {
324
325
  const { ids, hrefs } = crawl(body.toString(), decoded);
325
326
 
326
327
  actual_hashlinks.set(decoded, ids);
@@ -1813,78 +1813,127 @@ export interface Snapshot<T = any> {
1813
1813
  // If T is unknown or has an index signature, the types below will recurse indefinitely and create giant unions that TS can't handle
1814
1814
  type WillRecurseIndefinitely<T> = unknown extends T ? true : string extends keyof T ? true : false;
1815
1815
 
1816
- // Helper type to convert union to intersection
1817
- type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void
1818
- ? I
1819
- : never;
1820
-
1821
- type FlattenInput<T, Prefix extends string> = T extends string | number | boolean | null | undefined
1822
- ? { [P in Prefix]: string }
1823
- : WillRecurseIndefinitely<T> extends true
1824
- ? { [key: string]: string }
1825
- : T extends Array<infer U>
1826
- ? U extends string | File
1827
- ? { [P in Prefix]: string[] }
1828
- : FlattenInput<U, `${Prefix}[${number}]`>
1829
- : T extends File
1830
- ? { [P in Prefix]: string }
1831
- : {
1832
- // Required<T> is crucial here to avoid an undefined type to sneak into the union, which would turn the intersection into never
1833
- [K in keyof Required<T>]: FlattenInput<
1834
- T[K],
1835
- Prefix extends '' ? K & string : `${Prefix}.${K & string}`
1836
- >;
1837
- }[keyof T];
1838
-
1839
- type FlattenIssues<T, Prefix extends string> = T extends
1840
- | string
1841
- | number
1842
- | boolean
1843
- | null
1844
- | undefined
1845
- ? { [P in Prefix]: RemoteFormIssue[] }
1846
- : WillRecurseIndefinitely<T> extends true
1847
- ? { [key: string]: RemoteFormIssue[] }
1848
- : T extends Array<infer U>
1849
- ? { [P in Prefix | `${Prefix}[${number}]`]: RemoteFormIssue[] } & FlattenIssues<
1850
- U,
1851
- `${Prefix}[${number}]`
1852
- >
1853
- : T extends File
1854
- ? { [P in Prefix]: RemoteFormIssue[] }
1855
- : {
1856
- // Required<T> is crucial here to avoid an undefined type to sneak into the union, which would turn the intersection into never
1857
- [K in keyof Required<T>]: FlattenIssues<
1858
- T[K],
1859
- Prefix extends '' ? K & string : `${Prefix}.${K & string}`
1860
- >;
1861
- }[keyof T];
1862
-
1863
- type FlattenKeys<T, Prefix extends string> = T extends string | number | boolean | null | undefined
1864
- ? { [P in Prefix]: string }
1865
- : WillRecurseIndefinitely<T> extends true
1866
- ? { [key: string]: string }
1867
- : T extends Array<infer U>
1868
- ? U extends string | File
1869
- ? { [P in `${Prefix}[]`]: string[] }
1870
- : FlattenKeys<U, `${Prefix}[${number}]`>
1871
- : T extends File
1872
- ? { [P in Prefix]: string }
1873
- : {
1874
- // Required<T> is crucial here to avoid an undefined type to sneak into the union, which would turn the intersection into never
1875
- [K in keyof Required<T>]: FlattenKeys<
1876
- T[K],
1877
- Prefix extends '' ? K & string : `${Prefix}.${K & string}`
1878
- >;
1879
- }[keyof T];
1816
+ // Input type mappings for form fields
1817
+ type InputTypeMap = {
1818
+ text: string;
1819
+ email: string;
1820
+ password: string;
1821
+ url: string;
1822
+ tel: string;
1823
+ search: string;
1824
+ number: number;
1825
+ range: number;
1826
+ date: string;
1827
+ 'datetime-local': string;
1828
+ time: string;
1829
+ month: string;
1830
+ week: string;
1831
+ color: string;
1832
+ checkbox: boolean | string[];
1833
+ radio: string;
1834
+ file: File;
1835
+ hidden: string;
1836
+ submit: string;
1837
+ button: string;
1838
+ reset: string;
1839
+ image: string;
1840
+ select: string;
1841
+ 'select multiple': string[];
1842
+ 'file multiple': File[];
1843
+ };
1844
+
1845
+ // Valid input types for a given value type
1846
+ export type RemoteFormFieldType<T> = {
1847
+ [K in keyof InputTypeMap]: T extends InputTypeMap[K] ? K : never;
1848
+ }[keyof InputTypeMap];
1849
+
1850
+ // Input element properties based on type
1851
+ type InputElementProps<T extends keyof InputTypeMap> = T extends 'checkbox' | 'radio'
1852
+ ? {
1853
+ type: T;
1854
+ 'aria-invalid': boolean | 'false' | 'true' | undefined;
1855
+ get checked(): boolean;
1856
+ set checked(value: boolean);
1857
+ }
1858
+ : T extends 'file'
1859
+ ? {
1860
+ type: 'file';
1861
+ 'aria-invalid': boolean | 'false' | 'true' | undefined;
1862
+ get files(): FileList | null;
1863
+ set files(v: FileList | null);
1864
+ }
1865
+ : {
1866
+ type: T;
1867
+ 'aria-invalid': boolean | 'false' | 'true' | undefined;
1868
+ get value(): string | number;
1869
+ set value(v: string | number);
1870
+ };
1871
+
1872
+ type RemoteFormFieldMethods<T> = {
1873
+ /** The values that will be submitted */
1874
+ value(): T;
1875
+ /** Set the values that will be submitted */
1876
+ set(input: T): T;
1877
+ /** Validation issues, if any */
1878
+ issues(): RemoteFormIssue[] | undefined;
1879
+ };
1880
+
1881
+ export type RemoteFormFieldValue = string | string[] | number | boolean | File | File[];
1882
+
1883
+ type AsArgs<Type extends keyof InputTypeMap, Value> = Type extends 'checkbox'
1884
+ ? Value extends string[]
1885
+ ? [type: 'checkbox', value: Value[number] | (string & {})]
1886
+ : [type: Type]
1887
+ : Type extends 'radio'
1888
+ ? [type: 'radio', value: Value | (string & {})]
1889
+ : [type: Type];
1890
+
1891
+ /**
1892
+ * Form field accessor type that provides name(), value(), and issues() methods
1893
+ */
1894
+ export type RemoteFormField<Value extends RemoteFormFieldValue> = RemoteFormFieldMethods<Value> & {
1895
+ /**
1896
+ * Returns an object that can be spread onto an input element with the correct type attribute,
1897
+ * aria-invalid attribute if the field is invalid, and appropriate value/checked property getters/setters.
1898
+ * @example
1899
+ * ```svelte
1900
+ * <input {...myForm.fields.myString.as('text')} />
1901
+ * <input {...myForm.fields.myNumber.as('number')} />
1902
+ * <input {...myForm.fields.myBoolean.as('checkbox')} />
1903
+ * ```
1904
+ */
1905
+ as<T extends RemoteFormFieldType<Value>>(...args: AsArgs<T, Value>): InputElementProps<T>;
1906
+ };
1907
+
1908
+ type RemoteFormFieldContainer<Value> = RemoteFormFieldMethods<Value> & {
1909
+ /** Validation issues belonging to this or any of the fields that belong to it, if any */
1910
+ allIssues(): RemoteFormIssue[] | undefined;
1911
+ };
1912
+
1913
+ /**
1914
+ * Recursive type to build form fields structure with proxy access
1915
+ */
1916
+ type RemoteFormFields<T> =
1917
+ WillRecurseIndefinitely<T> extends true
1918
+ ? RecursiveFormFields
1919
+ : NonNullable<T> extends string | number | boolean | File
1920
+ ? RemoteFormField<NonNullable<T>>
1921
+ : T extends string[] | File[]
1922
+ ? RemoteFormField<T> & { [K in number]: RemoteFormField<T[number]> }
1923
+ : T extends Array<infer U>
1924
+ ? RemoteFormFieldContainer<T> & { [K in number]: RemoteFormFields<U> }
1925
+ : RemoteFormFieldContainer<T> & { [K in keyof T]-?: RemoteFormFields<T[K]> };
1926
+
1927
+ // By breaking this out into its own type, we avoid the TS recursion depth limit
1928
+ type RecursiveFormFields = RemoteFormField<any> & { [key: string]: RecursiveFormFields };
1929
+
1930
+ type MaybeArray<T> = T | T[];
1880
1931
 
1881
1932
  export interface RemoteFormInput {
1882
- [key: string]: FormDataEntryValue | FormDataEntryValue[] | RemoteFormInput | RemoteFormInput[];
1933
+ [key: string]: MaybeArray<string | number | boolean | File | RemoteFormInput>;
1883
1934
  }
1884
1935
 
1885
1936
  export interface RemoteFormIssue {
1886
- name: string;
1887
- path: Array<string | number>;
1888
1937
  message: string;
1889
1938
  }
1890
1939
 
@@ -1926,14 +1975,6 @@ export type RemoteForm<Input extends RemoteFormInput | void, Output> = {
1926
1975
  * ```
1927
1976
  */
1928
1977
  for(key: string | number | boolean): Omit<RemoteForm<Input, Output>, 'for'>;
1929
- /**
1930
- * This method exists to allow you to typecheck `name` attributes. It returns its argument
1931
- * @example
1932
- * ```svelte
1933
- * <input name={login.field('username')} />
1934
- * ```
1935
- **/
1936
- field<Name extends keyof UnionToIntersection<FlattenKeys<Input, ''>>>(string: Name): Name;
1937
1978
  /** Preflight checks */
1938
1979
  preflight(schema: StandardSchemaV1<Input, any>): RemoteForm<Input, Output>;
1939
1980
  /** Validate the form contents programmatically */
@@ -1946,10 +1987,8 @@ export type RemoteForm<Input extends RemoteFormInput | void, Output> = {
1946
1987
  get result(): Output | undefined;
1947
1988
  /** The number of pending submissions */
1948
1989
  get pending(): number;
1949
- /** The submitted values */
1950
- input: null | UnionToIntersection<FlattenInput<Input, ''>>;
1951
- /** Validation issues */
1952
- issues: null | UnionToIntersection<FlattenIssues<Input, ''>>;
1990
+ /** Access form fields using object notation */
1991
+ fields: Input extends void ? never : RemoteFormFields<Input>;
1953
1992
  /** Spread this onto a `<button>` or `<input type="submit">` */
1954
1993
  buttonProps: {
1955
1994
  type: 'submit';
@@ -234,7 +234,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) {
234
234
  // in dev we inline all styles to avoid FOUC. this gets populated lazily so that
235
235
  // components/stylesheets loaded via import() during `load` are included
236
236
  result.inline_styles = async () => {
237
- /** @type {Set<import('vite').ModuleNode>} */
237
+ /** @type {Set<import('vite').ModuleNode | import('vite').EnvironmentModuleNode>} */
238
238
  const deps = new Set();
239
239
 
240
240
  for (const module_node of module_nodes) {
@@ -610,8 +610,8 @@ function remove_static_middlewares(server) {
610
610
 
611
611
  /**
612
612
  * @param {import('vite').ViteDevServer} vite
613
- * @param {import('vite').ModuleNode} node
614
- * @param {Set<import('vite').ModuleNode>} deps
613
+ * @param {import('vite').ModuleNode | import('vite').EnvironmentModuleNode} node
614
+ * @param {Set<import('vite').ModuleNode | import('vite').EnvironmentModuleNode>} deps
615
615
  */
616
616
  async function find_deps(vite, node, deps) {
617
617
  // since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous.
@@ -619,7 +619,7 @@ async function find_deps(vite, node, deps) {
619
619
  /** @type {Promise<void>[]} */
620
620
  const branches = [];
621
621
 
622
- /** @param {import('vite').ModuleNode} node */
622
+ /** @param {import('vite').ModuleNode | import('vite').EnvironmentModuleNode} node */
623
623
  async function add(node) {
624
624
  if (!deps.has(node)) {
625
625
  deps.add(node);
@@ -629,20 +629,23 @@ async function find_deps(vite, node, deps) {
629
629
 
630
630
  /** @param {string} url */
631
631
  async function add_by_url(url) {
632
- const node = await vite.moduleGraph.getModuleByUrl(url);
632
+ const node = await get_server_module_by_url(vite, url);
633
633
 
634
634
  if (node) {
635
635
  await add(node);
636
636
  }
637
637
  }
638
638
 
639
- if (node.ssrTransformResult) {
640
- if (node.ssrTransformResult.deps) {
641
- node.ssrTransformResult.deps.forEach((url) => branches.push(add_by_url(url)));
639
+ const transform_result =
640
+ /** @type {import('vite').ModuleNode} */ (node).ssrTransformResult || node.transformResult;
641
+
642
+ if (transform_result) {
643
+ if (transform_result.deps) {
644
+ transform_result.deps.forEach((url) => branches.push(add_by_url(url)));
642
645
  }
643
646
 
644
- if (node.ssrTransformResult.dynamicDeps) {
645
- node.ssrTransformResult.dynamicDeps.forEach((url) => branches.push(add_by_url(url)));
647
+ if (transform_result.dynamicDeps) {
648
+ transform_result.dynamicDeps.forEach((url) => branches.push(add_by_url(url)));
646
649
  }
647
650
  } else {
648
651
  node.importedModules.forEach((node) => branches.push(add(node)));
@@ -651,6 +654,16 @@ async function find_deps(vite, node, deps) {
651
654
  await Promise.all(branches);
652
655
  }
653
656
 
657
+ /**
658
+ * @param {import('vite').ViteDevServer} vite
659
+ * @param {string} url
660
+ */
661
+ function get_server_module_by_url(vite, url) {
662
+ return vite.environments
663
+ ? vite.environments.ssr.moduleGraph.getModuleByUrl(url)
664
+ : vite.moduleGraph.getModuleByUrl(url, true);
665
+ }
666
+
654
667
  /**
655
668
  * Determine if a file is being requested with the correct case,
656
669
  * to ensure consistent behaviour between dev and prod and across
@@ -298,6 +298,23 @@ async function kit({ svelte_config }) {
298
298
  `${kit.files.routes}/**/+*.{svelte,js,ts}`,
299
299
  `!${kit.files.routes}/**/+*server.*`
300
300
  ],
301
+ esbuildOptions: {
302
+ plugins: [
303
+ {
304
+ name: 'vite-plugin-sveltekit-setup:optimize',
305
+ setup(build) {
306
+ if (!kit.experimental.remoteFunctions) return;
307
+
308
+ const filter = new RegExp(
309
+ `.remote(${kit.moduleExtensions.join('|')})$`.replaceAll('.', '\\.')
310
+ );
311
+
312
+ // treat .remote.js files as empty for the purposes of prebundling
313
+ build.onLoad({ filter }, () => ({ contents: '' }));
314
+ }
315
+ }
316
+ ]
317
+ },
301
318
  exclude: [
302
319
  // Without this SvelteKit will be prebundled on the client, which means we end up with two versions of Redirect etc.
303
320
  // Also see https://github.com/sveltejs/kit/issues/5952#issuecomment-1218844057
@@ -697,7 +714,8 @@ async function kit({ svelte_config }) {
697
714
  },
698
715
 
699
716
  async transform(code, id, opts) {
700
- if (!svelte_config.kit.moduleExtensions.some((ext) => id.endsWith(`.remote${ext}`))) {
717
+ const normalized = normalize_id(id, normalized_lib, normalized_cwd);
718
+ if (!svelte_config.kit.moduleExtensions.some((ext) => normalized.endsWith(`.remote${ext}`))) {
701
719
  return;
702
720
  }
703
721
 
@@ -770,8 +788,14 @@ async function kit({ svelte_config }) {
770
788
  return `export const ${name} = ${namespace}.${type}('${remote.hash}/${name}');`;
771
789
  });
772
790
 
791
+ let result = `import * as ${namespace} from '__sveltekit/remote';\n\n${exports.join('\n')}\n`;
792
+
793
+ if (dev_server) {
794
+ result += `\nimport.meta.hot?.accept();\n`;
795
+ }
796
+
773
797
  return {
774
- code: `import * as ${namespace} from '__sveltekit/remote';\n\n${exports.join('\n')}\n`
798
+ code: result
775
799
  };
776
800
  },
777
801
 
@@ -1,10 +1,17 @@
1
1
  /** @import { RemoteFormInput, RemoteForm } from '@sveltejs/kit' */
2
- /** @import { MaybePromise, RemoteInfo } from 'types' */
2
+ /** @import { InternalRemoteFormIssue, MaybePromise, RemoteInfo } from 'types' */
3
3
  /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
4
4
  import { get_request_store } from '@sveltejs/kit/internal/server';
5
5
  import { DEV } from 'esm-env';
6
+ import {
7
+ convert_formdata,
8
+ flatten_issues,
9
+ create_field_proxy,
10
+ set_nested_value,
11
+ throw_on_old_property_access,
12
+ deep_set
13
+ } from '../../../form-utils.svelte.js';
6
14
  import { get_cache, run_remote_function } from './shared.js';
7
- import { convert_formdata, flatten_issues } from '../../../utils.js';
8
15
 
9
16
  /**
10
17
  * Creates a form object that can be spread onto a `<form>` element.
@@ -128,7 +135,7 @@ export function form(validate_or_fn, maybe_fn) {
128
135
  }
129
136
  }
130
137
 
131
- /** @type {{ input?: Record<string, string | string[]>, issues?: Record<string, StandardSchemaV1.Issue[]>, result: Output }} */
138
+ /** @type {{ input?: Record<string, any>, issues?: Record<string, InternalRemoteFormIssue[]>, result: Output }} */
132
139
  const output = {};
133
140
 
134
141
  const { event, state } = get_request_store();
@@ -140,18 +147,27 @@ export function form(validate_or_fn, maybe_fn) {
140
147
 
141
148
  if (validated?.issues !== undefined) {
142
149
  output.issues = flatten_issues(validated.issues);
143
- output.input = {};
144
150
 
145
- for (let key of form_data.keys()) {
146
- // redact sensitive fields
147
- if (/^[.\]]?_/.test(key)) continue;
151
+ // if it was a progressively-enhanced submission, we don't need
152
+ // to return the input — it's already there
153
+ if (!event.isRemoteRequest) {
154
+ output.input = {};
148
155
 
149
- const is_array = key.endsWith('[]');
150
- const values = form_data.getAll(key).filter((value) => typeof value === 'string');
156
+ for (let key of form_data.keys()) {
157
+ // redact sensitive fields
158
+ if (/^[.\]]?_/.test(key)) continue;
151
159
 
152
- if (is_array) key = key.slice(0, -2);
160
+ const is_array = key.endsWith('[]');
161
+ const values = form_data.getAll(key).filter((value) => typeof value === 'string');
153
162
 
154
- output.input[key] = is_array ? values : values[0];
163
+ if (is_array) key = key.slice(0, -2);
164
+
165
+ output.input = set_nested_value(
166
+ /** @type {Record<string, any>} */ (output.input),
167
+ key,
168
+ is_array ? values : values[0]
169
+ );
170
+ }
155
171
  }
156
172
  } else {
157
173
  if (validated !== undefined) {
@@ -185,16 +201,30 @@ export function form(validate_or_fn, maybe_fn) {
185
201
  enumerable: true
186
202
  });
187
203
 
188
- for (const property of ['input', 'issues']) {
189
- Object.defineProperty(instance, property, {
190
- get() {
191
- try {
192
- return get_cache(__)?.['']?.[property] ?? {};
193
- } catch {
194
- return undefined;
195
- }
196
- }
197
- });
204
+ Object.defineProperty(instance, 'fields', {
205
+ get() {
206
+ const data = get_cache(__)?.[''];
207
+ return create_field_proxy(
208
+ {},
209
+ () => data?.input ?? {},
210
+ (path, value) => {
211
+ if (data) {
212
+ // don't override a submission
213
+ return;
214
+ }
215
+
216
+ const input = path.length === 0 ? value : deep_set({}, path.map(String), value);
217
+
218
+ get_cache(__)[''] ??= { input };
219
+ },
220
+ () => data?.issues ?? {}
221
+ );
222
+ }
223
+ });
224
+
225
+ // TODO 3.0 remove
226
+ if (DEV) {
227
+ throw_on_old_property_access(instance);
198
228
  }
199
229
 
200
230
  Object.defineProperty(instance, 'result', {
@@ -217,10 +247,6 @@ export function form(validate_or_fn, maybe_fn) {
217
247
  get: () => 0
218
248
  });
219
249
 
220
- Object.defineProperty(instance, 'field', {
221
- value: (/** @type {string} */ name) => name
222
- });
223
-
224
250
  Object.defineProperty(instance, 'preflight', {
225
251
  // preflight is a noop on the server
226
252
  value: () => instance
@@ -133,9 +133,7 @@ export async function run_remote_function(event, state, allow_cookies, arg, vali
133
133
 
134
134
  return event.cookies.delete(name, opts);
135
135
  }
136
- },
137
- route: { id: null },
138
- url: new URL(event.url.origin)
136
+ }
139
137
  };
140
138
 
141
139
  // In two parts, each with_event, so that runtimes without async local storage can still get the event at the start of the function
@@ -40,7 +40,9 @@ export function command(id) {
40
40
  refreshes: updates.map((u) => u._key)
41
41
  }),
42
42
  headers: {
43
- 'Content-Type': 'application/json'
43
+ 'Content-Type': 'application/json',
44
+ 'x-sveltekit-pathname': location.pathname,
45
+ 'x-sveltekit-search': location.search
44
46
  }
45
47
  });
46
48