@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.
- package/package.json +16 -10
- package/src/constants.js +6 -0
- package/src/core/adapt/builder.js +128 -33
- package/src/core/adapt/index.js +3 -0
- package/src/core/config/index.js +4 -7
- package/src/core/env.js +39 -14
- package/src/core/generate_manifest/index.js +42 -43
- package/src/core/postbuild/analyse.js +4 -4
- package/src/core/postbuild/fallback.js +1 -1
- package/src/core/postbuild/prerender.js +4 -4
- package/src/core/sync/create_manifest_data/index.js +0 -1
- package/src/core/sync/write_app_manifest.js +3 -2
- package/src/core/sync/write_server.js +3 -17
- package/src/core/utils.js +10 -0
- package/src/exports/adapter.js +22 -0
- package/src/exports/public.d.ts +101 -49
- package/src/exports/vite/build/index.js +1173 -0
- package/src/exports/vite/build/remote.js +4 -4
- package/src/exports/vite/build/service-worker.js +98 -0
- package/src/exports/vite/dev/generate_manifest.js +308 -0
- package/src/exports/vite/dev/index.js +40 -282
- package/src/exports/vite/index.js +156 -1717
- package/src/exports/vite/plugins/env-vars.js +53 -34
- package/src/exports/vite/plugins/guard.js +217 -0
- package/src/exports/vite/plugins/remote.js +237 -0
- package/src/exports/vite/preview/index.js +8 -8
- package/src/exports/vite/static_analysis/index.js +15 -15
- package/src/exports/vite/utils.js +98 -1
- package/src/runner.js +4 -3
- package/src/runtime/app/paths/server.js +2 -2
- package/src/runtime/app/server/index.js +3 -3
- package/src/runtime/app/server/public.d.ts +114 -57
- package/src/runtime/app/server/remote/query.js +2 -2
- package/src/runtime/app/server/remote/requested.js +31 -15
- package/src/runtime/app/state/client.svelte.js +3 -1
- package/src/runtime/client/client.js +43 -198
- package/src/runtime/client/fetcher.js +6 -6
- package/src/runtime/client/focus.js +120 -0
- package/src/runtime/client/remote-functions/command.svelte.js +20 -9
- package/src/runtime/client/remote-functions/form.svelte.js +12 -7
- package/src/runtime/client/remote-functions/query/instance.svelte.js +19 -5
- package/src/runtime/client/remote-functions/shared.svelte.js +22 -1
- package/src/runtime/client/scroll.js +39 -0
- package/src/runtime/client/utils.js +28 -0
- package/src/runtime/form-utils.js +254 -264
- package/src/runtime/server/data/index.js +14 -36
- package/src/runtime/server/errors.js +7 -10
- package/src/runtime/server/fetch.js +21 -25
- package/src/runtime/server/index.js +35 -31
- package/src/runtime/server/internal.js +34 -5
- package/src/runtime/server/page/actions.js +6 -8
- package/src/runtime/server/page/data_serializer.js +6 -10
- package/src/runtime/server/page/index.js +18 -28
- package/src/runtime/server/page/load_data.js +2 -2
- package/src/runtime/server/page/render.js +22 -40
- package/src/runtime/server/page/respond_with_error.js +10 -13
- package/src/runtime/server/page/server_routing.js +21 -27
- package/src/runtime/server/remote-functions.js +136 -136
- package/src/runtime/server/respond.js +66 -64
- package/src/runtime/server/state.js +2 -0
- package/src/runtime/server/utils.js +4 -11
- package/src/runtime/shared.js +9 -0
- package/src/runtime/utils.js +41 -0
- package/src/types/ambient-private.d.ts +1 -2
- package/src/types/global-private.d.ts +8 -0
- package/src/types/internal.d.ts +56 -17
- package/src/utils/filesystem.js +44 -28
- package/src/utils/streaming.js +5 -15
- package/src/version.js +1 -1
- package/types/index.d.ts +235 -93
- 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 {
|
|
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 =
|
|
43
|
-
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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 {
|
|
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
|
|
5
|
+
* @param {typeof vite} vite the vite module that created the server
|
|
5
6
|
* @param {ViteDevServer} server
|
|
6
7
|
*/
|
|
7
|
-
export function get_runner(
|
|
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 (!
|
|
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.
|
|
87
|
-
const result = find_route(resolved_path, manifest.
|
|
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.
|
|
63
|
-
const length = manifest.
|
|
64
|
-
const type = manifest.
|
|
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:
|
|
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 '
|
|
62
|
+
: T extends 'image'
|
|
55
63
|
? {
|
|
56
64
|
name: string;
|
|
57
|
-
type: '
|
|
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 '
|
|
68
|
+
: T extends 'file'
|
|
63
69
|
? {
|
|
64
70
|
name: string;
|
|
71
|
+
type: 'file';
|
|
65
72
|
'aria-invalid': boolean | 'false' | 'true' | undefined;
|
|
66
|
-
get
|
|
67
|
-
set
|
|
73
|
+
get files(): FileList | null;
|
|
74
|
+
set files(v: FileList | null);
|
|
68
75
|
}
|
|
69
|
-
: T extends 'select
|
|
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 '
|
|
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
|
|
82
|
-
set value(v: string
|
|
83
|
-
readonly defaultValue?: string | number;
|
|
88
|
+
get value(): string[];
|
|
89
|
+
set value(v: string[]);
|
|
84
90
|
}
|
|
85
|
-
:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 =
|
|
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
|
|
123
|
-
: [type: Type
|
|
124
|
-
: Type extends '
|
|
125
|
-
?
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
: Type extends '
|
|
131
|
-
? [type: Type]
|
|
132
|
-
:
|
|
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>>(
|
|
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<
|
|
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
|
-
:
|
|
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
|
-
:
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
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,
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
|