@sveltejs/kit 3.0.0-next.0 → 3.0.0-next.10
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 +33 -21
- package/src/core/adapt/builder.js +29 -43
- package/src/core/adapt/index.js +1 -4
- package/src/core/config/index.js +132 -71
- package/src/core/config/options.js +294 -244
- package/src/core/config/types.d.ts +1 -1
- package/src/core/env.js +121 -5
- package/src/core/generate_manifest/index.js +12 -15
- package/src/core/postbuild/analyse.js +7 -19
- package/src/core/postbuild/crawl.js +22 -6
- package/src/core/postbuild/prerender.js +42 -23
- package/src/core/sync/create_manifest_data/index.js +53 -50
- package/src/core/sync/sync.js +0 -2
- package/src/core/sync/write_client_manifest.js +8 -15
- package/src/core/sync/write_env.js +2 -1
- package/src/core/sync/write_non_ambient.js +12 -9
- package/src/core/sync/write_server.js +13 -14
- package/src/core/sync/write_tsconfig.js +16 -8
- package/src/core/sync/write_tsconfig_test/package.json +7 -0
- package/src/core/sync/write_types/index.js +28 -24
- package/src/core/utils.js +2 -2
- package/src/exports/env/index.js +12 -0
- package/src/exports/hooks/index.js +3 -9
- package/src/exports/hooks/sequence.js +3 -2
- package/src/exports/index.js +35 -13
- package/src/exports/internal/client.js +5 -0
- package/src/exports/internal/env.js +2 -2
- package/src/exports/internal/index.js +1 -90
- package/src/exports/internal/{event.js → server/event.js} +1 -2
- package/src/exports/internal/server/index.js +33 -0
- package/src/exports/internal/shared.js +89 -0
- package/src/exports/node/index.js +62 -15
- package/src/exports/params.js +63 -0
- package/src/exports/public.d.ts +323 -170
- package/src/exports/url.js +86 -0
- package/src/exports/vite/build/build_server.js +53 -59
- package/src/exports/vite/build/remote.js +18 -11
- package/src/exports/vite/build/utils.js +0 -8
- package/src/exports/vite/dev/index.js +42 -44
- package/src/exports/vite/index.js +810 -593
- package/src/exports/vite/preview/index.js +29 -22
- package/src/exports/vite/static_analysis/index.js +2 -4
- package/src/exports/vite/static_analysis/types.d.ts +14 -0
- package/src/exports/vite/utils.js +21 -36
- package/src/runtime/app/env/internal.js +4 -4
- package/src/runtime/app/env/types.d.ts +1 -1
- package/src/runtime/app/environment/index.js +6 -0
- package/src/runtime/app/forms.js +24 -7
- package/src/runtime/app/paths/client.js +4 -10
- package/src/runtime/app/paths/internal/client.js +4 -2
- package/src/runtime/app/paths/internal/server.js +2 -23
- package/src/runtime/app/paths/public.d.ts +0 -28
- package/src/runtime/app/paths/server.js +9 -5
- package/src/runtime/app/server/remote/command.js +0 -3
- package/src/runtime/app/server/remote/form.js +27 -15
- package/src/runtime/app/server/remote/prerender.js +29 -36
- package/src/runtime/app/server/remote/query.js +101 -99
- package/src/runtime/app/server/remote/requested.js +22 -14
- package/src/runtime/app/server/remote/shared.js +30 -26
- package/src/runtime/app/state/client.js +1 -2
- package/src/runtime/app/stores.js +13 -76
- package/src/runtime/client/bundle.js +1 -1
- package/src/runtime/client/client-entry.js +3 -0
- package/src/runtime/client/client.js +411 -318
- package/src/runtime/client/entry.js +24 -3
- package/src/runtime/client/fetcher.js +3 -2
- package/src/runtime/client/ndjson.js +6 -33
- package/src/runtime/client/payload.js +17 -0
- package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
- package/src/runtime/client/remote-functions/command.svelte.js +7 -32
- package/src/runtime/client/remote-functions/form.svelte.js +183 -112
- package/src/runtime/client/remote-functions/prerender.svelte.js +29 -8
- package/src/runtime/client/remote-functions/query/index.js +7 -14
- package/src/runtime/client/remote-functions/query/instance.svelte.js +63 -18
- package/src/runtime/client/remote-functions/query/proxy.js +3 -3
- package/src/runtime/client/remote-functions/query-batch.svelte.js +60 -68
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +60 -18
- package/src/runtime/client/remote-functions/query-live/iterator.js +36 -55
- package/src/runtime/client/remote-functions/shared.svelte.js +88 -62
- package/src/runtime/client/sse.js +32 -0
- package/src/runtime/client/state.svelte.js +65 -49
- package/src/runtime/client/stream.js +38 -0
- package/src/runtime/client/types.d.ts +11 -7
- package/src/runtime/client/utils.js +0 -96
- package/src/runtime/components/root.svelte +66 -0
- package/src/runtime/env/dynamic/private.js +7 -0
- package/src/runtime/env/dynamic/public.js +7 -0
- package/src/runtime/env/static/private.js +6 -0
- package/src/runtime/env/static/public.js +6 -0
- package/src/runtime/form-utils.js +100 -22
- package/src/runtime/server/cookie.js +69 -52
- package/src/runtime/server/csrf.js +65 -0
- package/src/runtime/server/data/index.js +14 -18
- package/src/runtime/server/env_module.js +0 -5
- package/src/runtime/server/index.js +2 -2
- package/src/runtime/server/page/actions.js +41 -26
- package/src/runtime/server/page/index.js +9 -15
- package/src/runtime/server/page/load_data.js +1 -1
- package/src/runtime/server/page/render.js +112 -187
- package/src/runtime/server/page/respond_with_error.js +7 -8
- package/src/runtime/server/page/server_routing.js +27 -18
- package/src/runtime/server/remote.js +355 -216
- package/src/runtime/server/respond.js +97 -61
- package/src/runtime/server/utils.js +32 -9
- package/src/runtime/shared.js +83 -13
- package/src/runtime/telemetry/otel.js +1 -1
- package/src/runtime/types.d.ts +8 -0
- package/src/types/ambient.d.ts +10 -5
- package/src/types/global-private.d.ts +15 -22
- package/src/types/internal.d.ts +89 -74
- package/src/types/private.d.ts +33 -1
- package/src/utils/error.js +24 -4
- package/src/utils/import.js +6 -1
- package/src/utils/imports.js +83 -0
- package/src/utils/mime.js +9 -0
- package/src/utils/params.js +66 -0
- package/src/utils/routing.js +90 -44
- package/src/utils/shared-iterator.js +5 -0
- package/src/utils/streaming.js +14 -4
- package/src/utils/url.js +20 -2
- package/src/version.js +1 -1
- package/types/index.d.ts +467 -537
- package/types/index.d.ts.map +22 -39
- package/src/core/sync/write_root.js +0 -148
- package/src/exports/internal/server.js +0 -22
- package/src/types/synthetic/$lib.md +0 -5
- /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
|
|
2
2
|
/** @import { RemoteFormInput, RemoteForm, RemoteQueryUpdate } from '@sveltejs/kit' */
|
|
3
|
-
/** @import { InternalRemoteFormIssue
|
|
3
|
+
/** @import { InternalRemoteFormIssue } from 'types' */
|
|
4
4
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
5
|
-
import * as devalue from 'devalue';
|
|
6
5
|
import { DEV } from 'esm-env';
|
|
7
6
|
import { HttpError } from '@sveltejs/kit/internal';
|
|
8
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
query_responses,
|
|
9
|
+
_goto,
|
|
10
|
+
set_nearest_error_page,
|
|
11
|
+
handle_error,
|
|
12
|
+
refreshAll
|
|
13
|
+
} from '../client.js';
|
|
9
14
|
import { tick } from 'svelte';
|
|
10
|
-
import {
|
|
15
|
+
import { categorize_updates, remote_request } from './shared.svelte.js';
|
|
11
16
|
import { createAttachmentKey } from 'svelte/attachments';
|
|
12
17
|
import {
|
|
13
18
|
convert_formdata,
|
|
@@ -18,6 +23,8 @@ import {
|
|
|
18
23
|
build_path_string,
|
|
19
24
|
normalize_issue,
|
|
20
25
|
serialize_binary_form,
|
|
26
|
+
deep_get,
|
|
27
|
+
DELETE_KEY,
|
|
21
28
|
BINARY_FORM_CONTENT_TYPE
|
|
22
29
|
} from '../../form-utils.js';
|
|
23
30
|
|
|
@@ -53,24 +60,34 @@ export function form(id) {
|
|
|
53
60
|
/** @type {Map<any, { count: number, instance: RemoteForm<T, U> }>} */
|
|
54
61
|
const instances = new Map();
|
|
55
62
|
|
|
63
|
+
/** @type {StandardSchemaV1 | null} */
|
|
64
|
+
let shared_preflight_schema = null;
|
|
65
|
+
|
|
56
66
|
/** @param {string | number | boolean} [key] */
|
|
57
67
|
function create_instance(key) {
|
|
58
68
|
const action_id_without_key = id;
|
|
59
69
|
const action_id = id + (key != undefined ? `/${JSON.stringify(key)}` : '');
|
|
60
70
|
const action = '?/remote=' + encodeURIComponent(action_id);
|
|
61
71
|
|
|
72
|
+
// the output of a non-enhanced submission that resulted in this page —
|
|
73
|
+
// consume it so the form's state survives hydration (form outputs are
|
|
74
|
+
// always value nodes; the server never serializes them as errors)
|
|
75
|
+
/** @type {{ input?: Record<string, any>, issues?: InternalRemoteFormIssue[], result?: any } | undefined} */
|
|
76
|
+
const initial = query_responses[action_id]?.v;
|
|
77
|
+
delete query_responses[action_id];
|
|
78
|
+
|
|
62
79
|
/**
|
|
63
80
|
* @type {Record<string, string | string[] | File | File[]>}
|
|
64
81
|
*/
|
|
65
|
-
let input = $state({});
|
|
82
|
+
let input = $state(initial?.input ?? {});
|
|
66
83
|
|
|
67
84
|
/** @type {InternalRemoteFormIssue[]} */
|
|
68
|
-
let raw_issues = $state.raw([]);
|
|
85
|
+
let raw_issues = $state.raw(initial?.issues ?? []);
|
|
69
86
|
|
|
70
87
|
const issues = $derived(flatten_issues(raw_issues));
|
|
71
88
|
|
|
72
89
|
/** @type {any} */
|
|
73
|
-
let result = $state.raw(
|
|
90
|
+
let result = $state.raw(initial?.result);
|
|
74
91
|
|
|
75
92
|
/** @type {number} */
|
|
76
93
|
let pending_count = $state(0);
|
|
@@ -83,7 +100,9 @@ export function form(id) {
|
|
|
83
100
|
*/
|
|
84
101
|
let enhance_callback = async (instance) => {
|
|
85
102
|
if (await instance.submit()) {
|
|
86
|
-
|
|
103
|
+
await tick();
|
|
104
|
+
// We call reset from the prototype to avoid DOM clobbering
|
|
105
|
+
HTMLFormElement.prototype.reset.call(instance.element);
|
|
87
106
|
}
|
|
88
107
|
};
|
|
89
108
|
|
|
@@ -91,13 +110,22 @@ export function form(id) {
|
|
|
91
110
|
let element = null;
|
|
92
111
|
|
|
93
112
|
/** @type {Record<string, boolean>} */
|
|
94
|
-
let touched = {};
|
|
113
|
+
let touched = $state({});
|
|
114
|
+
|
|
115
|
+
/** @type {Record<string, boolean>} */
|
|
116
|
+
let dirty = $state({});
|
|
95
117
|
|
|
96
|
-
|
|
118
|
+
/** @type {Record<string, boolean>} */
|
|
119
|
+
let can_validate = {};
|
|
120
|
+
|
|
121
|
+
let submitted = $state(false);
|
|
97
122
|
|
|
98
123
|
/** @type {InternalRemoteFormIssue[] | null} */
|
|
99
124
|
let unread_issues = null;
|
|
100
125
|
|
|
126
|
+
/** @type {string | null} */
|
|
127
|
+
let previous_submitter_name = null;
|
|
128
|
+
|
|
101
129
|
/**
|
|
102
130
|
* In dev, warn if there are validation issues going unread
|
|
103
131
|
*/
|
|
@@ -181,77 +209,54 @@ export function form(id) {
|
|
|
181
209
|
remote_refreshes: Array.from(refreshes ?? [])
|
|
182
210
|
});
|
|
183
211
|
|
|
184
|
-
const response = await
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
'
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (!response.ok) {
|
|
196
|
-
// We only end up here in case of a network error or if the server has an internal error
|
|
197
|
-
// (which shouldn't happen because we handle errors on the server and always send a 200 response)
|
|
198
|
-
throw new Error('Failed to execute remote function');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const form_result = /** @type { RemoteFunctionResponse} */ (await response.json());
|
|
202
|
-
|
|
203
|
-
// reset issues in case it's a redirect or error (but issues passed in that case)
|
|
204
|
-
raw_issues = [];
|
|
205
|
-
result = undefined;
|
|
206
|
-
|
|
207
|
-
if (form_result.type === 'result') {
|
|
208
|
-
({ issues: raw_issues = [], result } = devalue.parse(form_result.result, app.decoders));
|
|
209
|
-
const succeeded = raw_issues.length === 0;
|
|
210
|
-
|
|
211
|
-
if (succeeded) {
|
|
212
|
-
if (refreshes === null && !form_result.refreshes && !form_result.reconnects) {
|
|
213
|
-
void invalidateAll();
|
|
214
|
-
} else {
|
|
215
|
-
if (form_result.refreshes) {
|
|
216
|
-
apply_refreshes(form_result.refreshes);
|
|
217
|
-
}
|
|
218
|
-
if (form_result.reconnects) {
|
|
219
|
-
apply_reconnections(form_result.reconnects);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
} else {
|
|
223
|
-
if (DEV) {
|
|
224
|
-
warn_on_missing_issue_reads();
|
|
225
|
-
}
|
|
212
|
+
const response = await remote_request(
|
|
213
|
+
`${base}/${app_dir}/remote/${action_id_without_key}`,
|
|
214
|
+
{
|
|
215
|
+
method: 'POST',
|
|
216
|
+
headers: {
|
|
217
|
+
'Content-Type': BINARY_FORM_CONTENT_TYPE,
|
|
218
|
+
// Forms cannot be called during rendering, so it's save to use location here
|
|
219
|
+
'x-sveltekit-pathname': location.pathname,
|
|
220
|
+
'x-sveltekit-search': location.search
|
|
221
|
+
},
|
|
222
|
+
body: blob
|
|
226
223
|
}
|
|
224
|
+
);
|
|
227
225
|
|
|
228
|
-
|
|
229
|
-
} else if (form_result.type === 'redirect') {
|
|
230
|
-
const stringified_refreshes = form_result.refreshes ?? '';
|
|
231
|
-
const stringified_reconnects = form_result.reconnects ?? '';
|
|
232
|
-
if (stringified_refreshes) {
|
|
233
|
-
apply_refreshes(stringified_refreshes);
|
|
234
|
-
}
|
|
226
|
+
({ issues: raw_issues = [], result } = response._ ?? {});
|
|
235
227
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
228
|
+
// if the developer took control of updates via `.updates(...)` (even with
|
|
229
|
+
// no arguments), or the server performed explicit refreshes, don't invalidateAll
|
|
230
|
+
const should_refresh = refreshes === null && !response.r;
|
|
239
231
|
|
|
232
|
+
if (response.redirect) {
|
|
240
233
|
// Use internal version to allow redirects to external URLs
|
|
241
234
|
void _goto(
|
|
242
|
-
|
|
235
|
+
response.redirect,
|
|
243
236
|
{
|
|
244
|
-
|
|
245
|
-
refreshes === null && !stringified_refreshes && !stringified_reconnects
|
|
237
|
+
refreshAll: should_refresh
|
|
246
238
|
},
|
|
247
239
|
0
|
|
248
240
|
);
|
|
249
241
|
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const succeeded = raw_issues.length === 0;
|
|
245
|
+
|
|
246
|
+
if (succeeded) {
|
|
247
|
+
if (should_refresh) {
|
|
248
|
+
void refreshAll();
|
|
249
|
+
}
|
|
250
250
|
} else {
|
|
251
|
-
|
|
251
|
+
if (DEV) {
|
|
252
|
+
warn_on_missing_issue_reads();
|
|
253
|
+
}
|
|
252
254
|
}
|
|
255
|
+
|
|
256
|
+
return succeeded;
|
|
253
257
|
} catch (e) {
|
|
254
258
|
result = undefined;
|
|
259
|
+
raw_issues = [];
|
|
255
260
|
throw e;
|
|
256
261
|
} finally {
|
|
257
262
|
overrides?.forEach((fn) => fn());
|
|
@@ -319,7 +324,8 @@ export function form(id) {
|
|
|
319
324
|
*/
|
|
320
325
|
async function preflight(form_data) {
|
|
321
326
|
const data = convert(form_data);
|
|
322
|
-
const
|
|
327
|
+
const schema = preflight_schema ?? shared_preflight_schema;
|
|
328
|
+
const validated = await schema?.['~standard'].validate(data);
|
|
323
329
|
|
|
324
330
|
if (validated?.issues) {
|
|
325
331
|
raw_issues = merge_with_server_issues(
|
|
@@ -363,6 +369,8 @@ export function form(id) {
|
|
|
363
369
|
element = form;
|
|
364
370
|
|
|
365
371
|
touched = {};
|
|
372
|
+
dirty = {};
|
|
373
|
+
can_validate = {};
|
|
366
374
|
|
|
367
375
|
/** @param {SubmitEvent} event */
|
|
368
376
|
const handle_submit = async (event) => {
|
|
@@ -396,6 +404,29 @@ export function form(id) {
|
|
|
396
404
|
|
|
397
405
|
const form_data = new FormData(form, event.submitter);
|
|
398
406
|
|
|
407
|
+
if (
|
|
408
|
+
previous_submitter_name !== null &&
|
|
409
|
+
!Array.from(form_data.keys()).map(strip_prefix).includes(previous_submitter_name)
|
|
410
|
+
) {
|
|
411
|
+
// Strip any `n:`/`b:` type prefix before clearing, otherwise
|
|
412
|
+
// `set_nested_value` would coerce `undefined` to `NaN`/`false`
|
|
413
|
+
// instead of clearing the previously-submitted value.
|
|
414
|
+
set_nested_value(input, previous_submitter_name, undefined);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (event.submitter) {
|
|
418
|
+
const name = event.submitter.getAttribute('name');
|
|
419
|
+
const value = /** @type {any} */ (event.submitter).value;
|
|
420
|
+
|
|
421
|
+
if (name !== null && value !== undefined) {
|
|
422
|
+
set_nested_value(input, name, value);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
previous_submitter_name = strip_prefix(name);
|
|
426
|
+
} else {
|
|
427
|
+
previous_submitter_name = null;
|
|
428
|
+
}
|
|
429
|
+
|
|
399
430
|
if (DEV) {
|
|
400
431
|
validate_form_data(form_data, clone(form).enctype);
|
|
401
432
|
}
|
|
@@ -413,9 +444,14 @@ export function form(id) {
|
|
|
413
444
|
await enhance_callback(create_enhance_callback_instance(form, form_data));
|
|
414
445
|
} catch (e) {
|
|
415
446
|
const error =
|
|
416
|
-
e instanceof HttpError
|
|
417
|
-
|
|
418
|
-
|
|
447
|
+
e instanceof HttpError
|
|
448
|
+
? e.body
|
|
449
|
+
: await handle_error(e, {
|
|
450
|
+
params: {},
|
|
451
|
+
route: { id: null },
|
|
452
|
+
url: new URL(location.href)
|
|
453
|
+
});
|
|
454
|
+
void set_nearest_error_page(error);
|
|
419
455
|
} finally {
|
|
420
456
|
pending_count--;
|
|
421
457
|
}
|
|
@@ -435,8 +471,6 @@ export function form(id) {
|
|
|
435
471
|
|
|
436
472
|
const is_file = element.type === 'file';
|
|
437
473
|
|
|
438
|
-
touched[name] = true;
|
|
439
|
-
|
|
440
474
|
if (is_array) {
|
|
441
475
|
let value;
|
|
442
476
|
|
|
@@ -481,14 +515,7 @@ export function form(id) {
|
|
|
481
515
|
if (file) {
|
|
482
516
|
set_nested_value(input, name, file);
|
|
483
517
|
} else {
|
|
484
|
-
|
|
485
|
-
const path_parts = name.split(/\.|\[|\]/).filter(Boolean);
|
|
486
|
-
let current = /** @type {any} */ (input);
|
|
487
|
-
for (let i = 0; i < path_parts.length - 1; i++) {
|
|
488
|
-
if (current[path_parts[i]] == null) return;
|
|
489
|
-
current = current[path_parts[i]];
|
|
490
|
-
}
|
|
491
|
-
delete current[path_parts[path_parts.length - 1]];
|
|
518
|
+
set_nested_value(input, name, DELETE_KEY);
|
|
492
519
|
}
|
|
493
520
|
} else {
|
|
494
521
|
set_nested_value(
|
|
@@ -498,9 +525,9 @@ export function form(id) {
|
|
|
498
525
|
);
|
|
499
526
|
}
|
|
500
527
|
|
|
501
|
-
name = name
|
|
528
|
+
name = strip_prefix(name);
|
|
502
529
|
|
|
503
|
-
|
|
530
|
+
dirty[name] = true;
|
|
504
531
|
};
|
|
505
532
|
|
|
506
533
|
const handle_reset = async () => {
|
|
@@ -509,18 +536,38 @@ export function form(id) {
|
|
|
509
536
|
await tick();
|
|
510
537
|
|
|
511
538
|
input = convert_formdata(new FormData(form));
|
|
539
|
+
raw_issues = [];
|
|
540
|
+
touched = {};
|
|
541
|
+
dirty = {};
|
|
542
|
+
can_validate = {};
|
|
543
|
+
submitted = false;
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
/** @param {Event} e */
|
|
547
|
+
const handle_focusout = (e) => {
|
|
548
|
+
let name = /** @type {HTMLInputElement} */ (e.target).name;
|
|
549
|
+
if (!name) return;
|
|
550
|
+
|
|
551
|
+
name = strip_prefix(name).replace(/\[\]$/, '');
|
|
552
|
+
|
|
553
|
+
touched[name] = true;
|
|
554
|
+
|
|
555
|
+
if (Object.hasOwn(dirty, name)) {
|
|
556
|
+
can_validate[name] = true;
|
|
557
|
+
}
|
|
512
558
|
};
|
|
513
559
|
|
|
514
560
|
form.addEventListener('submit', handle_submit);
|
|
515
561
|
form.addEventListener('input', handle_input);
|
|
562
|
+
form.addEventListener('focusout', handle_focusout);
|
|
516
563
|
form.addEventListener('reset', handle_reset);
|
|
517
564
|
|
|
518
565
|
return () => {
|
|
519
566
|
form.removeEventListener('submit', handle_submit);
|
|
520
567
|
form.removeEventListener('input', handle_input);
|
|
568
|
+
form.removeEventListener('focusout', handle_focusout);
|
|
521
569
|
form.removeEventListener('reset', handle_reset);
|
|
522
570
|
element = null;
|
|
523
|
-
preflight_schema = undefined;
|
|
524
571
|
};
|
|
525
572
|
};
|
|
526
573
|
|
|
@@ -551,9 +598,10 @@ export function form(id) {
|
|
|
551
598
|
|
|
552
599
|
const submission = submit(form_data, true);
|
|
553
600
|
|
|
554
|
-
|
|
601
|
+
const decrement = () => {
|
|
555
602
|
pending_count--;
|
|
556
|
-
}
|
|
603
|
+
};
|
|
604
|
+
void submission.then(decrement, decrement);
|
|
557
605
|
|
|
558
606
|
return submission;
|
|
559
607
|
}
|
|
@@ -566,11 +614,16 @@ export function form(id) {
|
|
|
566
614
|
(path, value) => {
|
|
567
615
|
if (path.length === 0) {
|
|
568
616
|
input = value;
|
|
569
|
-
} else {
|
|
617
|
+
} else if (value !== deep_get(input, path)) {
|
|
570
618
|
deep_set(input, path.map(String), value);
|
|
571
619
|
|
|
572
620
|
const key = build_path_string(path);
|
|
573
|
-
|
|
621
|
+
|
|
622
|
+
if (element) {
|
|
623
|
+
touched[key] = true;
|
|
624
|
+
dirty[key] = true;
|
|
625
|
+
can_validate[key] = true;
|
|
626
|
+
}
|
|
574
627
|
}
|
|
575
628
|
},
|
|
576
629
|
(path, all) => {
|
|
@@ -584,7 +637,10 @@ export function form(id) {
|
|
|
584
637
|
}
|
|
585
638
|
|
|
586
639
|
return issues;
|
|
587
|
-
}
|
|
640
|
+
},
|
|
641
|
+
() => touched,
|
|
642
|
+
() => dirty,
|
|
643
|
+
[]
|
|
588
644
|
)
|
|
589
645
|
},
|
|
590
646
|
result: {
|
|
@@ -593,16 +649,24 @@ export function form(id) {
|
|
|
593
649
|
pending: {
|
|
594
650
|
get: () => pending_count
|
|
595
651
|
},
|
|
652
|
+
submitted: {
|
|
653
|
+
get: () => submitted
|
|
654
|
+
},
|
|
596
655
|
preflight: {
|
|
597
656
|
/** @type {RemoteForm<T, U>['preflight']} */
|
|
598
657
|
value: (schema) => {
|
|
599
658
|
preflight_schema = schema;
|
|
659
|
+
|
|
660
|
+
if (key === undefined) {
|
|
661
|
+
shared_preflight_schema = schema;
|
|
662
|
+
}
|
|
663
|
+
|
|
600
664
|
return instance;
|
|
601
665
|
}
|
|
602
666
|
},
|
|
603
667
|
validate: {
|
|
604
668
|
/** @type {RemoteForm<any, any>['validate']} */
|
|
605
|
-
value: async ({
|
|
669
|
+
value: async ({ all = false, preflightOnly = false } = {}) => {
|
|
606
670
|
if (!element) return;
|
|
607
671
|
|
|
608
672
|
const id = ++validate_id;
|
|
@@ -620,8 +684,8 @@ export function form(id) {
|
|
|
620
684
|
let array = [];
|
|
621
685
|
|
|
622
686
|
const data = convert(form_data);
|
|
623
|
-
|
|
624
|
-
const validated = await
|
|
687
|
+
const schema = preflight_schema ?? shared_preflight_schema;
|
|
688
|
+
const validated = await schema?.['~standard'].validate(data);
|
|
625
689
|
|
|
626
690
|
if (validate_id !== id) {
|
|
627
691
|
return;
|
|
@@ -630,34 +694,31 @@ export function form(id) {
|
|
|
630
694
|
if (validated?.issues) {
|
|
631
695
|
array = validated.issues.map((issue) => normalize_issue(issue, false));
|
|
632
696
|
} else if (!preflightOnly) {
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
'
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
697
|
+
const result = await remote_request(
|
|
698
|
+
`${base}/${app_dir}/remote/${action_id_without_key}`,
|
|
699
|
+
{
|
|
700
|
+
method: 'POST',
|
|
701
|
+
headers: {
|
|
702
|
+
'Content-Type': BINARY_FORM_CONTENT_TYPE,
|
|
703
|
+
// Validation should not be and will not be called during rendering, so it's save to use location here
|
|
704
|
+
'x-sveltekit-pathname': location.pathname,
|
|
705
|
+
'x-sveltekit-search': location.search
|
|
706
|
+
},
|
|
707
|
+
body: serialize_binary_form(data, {
|
|
708
|
+
validate_only: true
|
|
709
|
+
}).blob
|
|
710
|
+
}
|
|
711
|
+
);
|
|
647
712
|
|
|
648
713
|
if (validate_id !== id) {
|
|
649
714
|
return;
|
|
650
715
|
}
|
|
651
716
|
|
|
652
|
-
|
|
653
|
-
array = /** @type {InternalRemoteFormIssue[]} */ (
|
|
654
|
-
devalue.parse(result.result, app.decoders)
|
|
655
|
-
);
|
|
656
|
-
}
|
|
717
|
+
array = /** @type {InternalRemoteFormIssue[]} */ (result._);
|
|
657
718
|
}
|
|
658
719
|
|
|
659
|
-
if (!
|
|
660
|
-
array = array.filter((issue) =>
|
|
720
|
+
if (!all && !submitted) {
|
|
721
|
+
array = array.filter((issue) => can_validate[issue.name]);
|
|
661
722
|
}
|
|
662
723
|
|
|
663
724
|
const is_server_validation = !validated?.issues && !preflightOnly;
|
|
@@ -748,3 +809,13 @@ function validate_form_data(form_data, enctype) {
|
|
|
748
809
|
}
|
|
749
810
|
}
|
|
750
811
|
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Remove the `n:` or `b:` prefix from a field name
|
|
815
|
+
* @template {string | null} T
|
|
816
|
+
* @param {T} name
|
|
817
|
+
* @returns {T}
|
|
818
|
+
*/
|
|
819
|
+
function strip_prefix(name) {
|
|
820
|
+
return /** @type {T} */ (name && name.replace(/^[nb]:/, ''));
|
|
821
|
+
}
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
3
3
|
import { version } from '$app/env';
|
|
4
4
|
import * as devalue from 'devalue';
|
|
5
|
-
import { app, prerender_responses } from '../client.js';
|
|
6
|
-
import { get_remote_request_headers, remote_request } from './shared.svelte.js';
|
|
5
|
+
import { app, _goto, handle_error, prerender_responses } from '../client.js';
|
|
6
|
+
import { get_remote_request_headers, remote_request, unwrap_node } from './shared.svelte.js';
|
|
7
7
|
import { create_remote_key, stringify_remote_arg } from '../../shared.js';
|
|
8
|
+
import { noop } from '../../../utils/functions.js';
|
|
9
|
+
import { HttpError } from '@sveltejs/kit/internal';
|
|
8
10
|
|
|
9
11
|
// Initialize Cache API for prerender functions
|
|
10
12
|
const CACHE_NAME = __SVELTEKIT_DEV__ ? `sveltekit:${Date.now()}` : `sveltekit:${version}`;
|
|
@@ -67,7 +69,7 @@ export function prerender(id) {
|
|
|
67
69
|
const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ''}`;
|
|
68
70
|
|
|
69
71
|
if (Object.hasOwn(prerender_responses, cache_key)) {
|
|
70
|
-
const data = prerender_responses[cache_key];
|
|
72
|
+
const data = unwrap_node(prerender_responses[cache_key]);
|
|
71
73
|
|
|
72
74
|
if (prerender_cache) {
|
|
73
75
|
void put(url, devalue.stringify(data, app.encoders));
|
|
@@ -77,6 +79,7 @@ export function prerender(id) {
|
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
// Do this here, after await Svelte' reactivity context is gone.
|
|
82
|
+
// TODO we really don't want to be sending these specific headers here?
|
|
80
83
|
const headers = get_remote_request_headers();
|
|
81
84
|
|
|
82
85
|
// Check the Cache API first
|
|
@@ -93,14 +96,22 @@ export function prerender(id) {
|
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
98
|
|
|
96
|
-
const
|
|
99
|
+
const result = await remote_request(url, { headers });
|
|
100
|
+
|
|
101
|
+
if (result.redirect) {
|
|
102
|
+
// Use internal version to allow redirects to external URLs
|
|
103
|
+
void _goto(result.redirect, {}, 0);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const data = result._;
|
|
97
108
|
|
|
98
109
|
// For successful prerender requests, save to cache
|
|
99
110
|
if (prerender_cache) {
|
|
100
|
-
void put(url,
|
|
111
|
+
void put(url, devalue.stringify(data, app.encoders));
|
|
101
112
|
}
|
|
102
113
|
|
|
103
|
-
return
|
|
114
|
+
return data;
|
|
104
115
|
});
|
|
105
116
|
|
|
106
117
|
prerender_resources.set(cache_key, new WeakRef(resource));
|
|
@@ -138,6 +149,7 @@ class Prerender {
|
|
|
138
149
|
/** @type {T | undefined} */
|
|
139
150
|
#current = $state.raw();
|
|
140
151
|
|
|
152
|
+
/** @type {App.Error | undefined} */
|
|
141
153
|
#error = $state.raw(undefined);
|
|
142
154
|
|
|
143
155
|
/**
|
|
@@ -152,12 +164,21 @@ class Prerender {
|
|
|
152
164
|
this.#error = undefined;
|
|
153
165
|
return value;
|
|
154
166
|
},
|
|
155
|
-
(
|
|
167
|
+
async (e) => {
|
|
168
|
+
const error = await handle_error(e, {
|
|
169
|
+
params: {},
|
|
170
|
+
route: { id: null },
|
|
171
|
+
url: new URL(location.href)
|
|
172
|
+
});
|
|
156
173
|
this.#loading = false;
|
|
157
174
|
this.#error = error;
|
|
158
|
-
throw error;
|
|
175
|
+
throw new HttpError(error.status, error); // so that transformError doesn't transform it again
|
|
159
176
|
}
|
|
160
177
|
);
|
|
178
|
+
|
|
179
|
+
// rejections are surfaced via `.error` for reactive consumers — make sure the
|
|
180
|
+
// stored promise (consumed without `await`) never becomes an unhandled rejection
|
|
181
|
+
this.#promise.catch(noop);
|
|
161
182
|
}
|
|
162
183
|
|
|
163
184
|
/**
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/** @import { RemoteQueryFunction } from '@sveltejs/kit' */
|
|
2
2
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
3
|
-
import {
|
|
3
|
+
import { _goto, query_map } from '../../client.js';
|
|
4
4
|
import { get_remote_request_headers, QUERY_FUNCTION_ID, remote_request } from '../shared.svelte.js';
|
|
5
|
-
import * as devalue from 'devalue';
|
|
6
5
|
import { DEV } from 'esm-env';
|
|
7
|
-
import { hydratable } from 'svelte';
|
|
8
6
|
import { QueryProxy } from './proxy.js';
|
|
9
7
|
|
|
10
8
|
/**
|
|
@@ -25,20 +23,15 @@ export function query(id) {
|
|
|
25
23
|
|
|
26
24
|
/** @type {RemoteQueryFunction<any, any>} */
|
|
27
25
|
const wrapper = (arg) => {
|
|
28
|
-
return new QueryProxy(id, arg, async (
|
|
29
|
-
if (Object.hasOwn(query_responses, key)) {
|
|
30
|
-
const value = query_responses[key];
|
|
31
|
-
delete query_responses[key];
|
|
32
|
-
return value;
|
|
33
|
-
}
|
|
34
|
-
|
|
26
|
+
return new QueryProxy(id, arg, async (payload) => {
|
|
35
27
|
const url = `${base}/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
|
|
36
28
|
|
|
37
|
-
const
|
|
38
|
-
remote_request(url, get_remote_request_headers())
|
|
39
|
-
);
|
|
29
|
+
const result = await remote_request(url, { headers: get_remote_request_headers() });
|
|
40
30
|
|
|
41
|
-
|
|
31
|
+
if (result.redirect) {
|
|
32
|
+
// Use internal version to allow redirects to external URLs
|
|
33
|
+
await _goto(result.redirect, {}, 0);
|
|
34
|
+
}
|
|
42
35
|
});
|
|
43
36
|
};
|
|
44
37
|
|