@sveltejs/kit 3.0.0-next.4 → 3.0.0-next.7
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 +23 -18
- package/src/core/adapt/builder.js +29 -43
- package/src/core/adapt/index.js +1 -4
- package/src/core/config/index.js +135 -71
- package/src/core/config/options.js +291 -244
- package/src/core/config/types.d.ts +1 -1
- package/src/core/env.js +120 -5
- package/src/core/generate_manifest/index.js +12 -15
- package/src/core/postbuild/analyse.js +8 -12
- package/src/core/postbuild/crawl.js +22 -6
- package/src/core/postbuild/prerender.js +40 -23
- package/src/core/sync/create_manifest_data/index.js +29 -49
- package/src/core/sync/write_client_manifest.js +14 -14
- package/src/core/sync/write_non_ambient.js +10 -7
- package/src/core/sync/write_root.js +9 -30
- package/src/core/sync/write_server.js +13 -11
- package/src/core/sync/write_tsconfig.js +2 -4
- package/src/core/sync/write_types/index.js +11 -10
- package/src/exports/index.js +34 -12
- package/src/exports/internal/client.js +5 -0
- package/src/exports/internal/env.js +1 -1
- 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 +58 -7
- package/src/exports/params.js +63 -0
- package/src/exports/public.d.ts +299 -145
- package/src/exports/url.js +84 -0
- package/src/exports/vite/build/build_server.js +6 -1
- package/src/exports/vite/dev/index.js +38 -37
- package/src/exports/vite/index.js +498 -353
- package/src/exports/vite/preview/index.js +16 -8
- package/src/exports/vite/utils.js +7 -11
- package/src/runtime/app/env/types.d.ts +1 -1
- package/src/runtime/app/forms.js +22 -5
- package/src/runtime/app/paths/client.js +4 -10
- package/src/runtime/app/paths/public.d.ts +0 -28
- package/src/runtime/app/paths/server.js +8 -4
- package/src/runtime/app/server/remote/form.js +10 -3
- package/src/runtime/app/server/remote/query.js +9 -18
- package/src/runtime/app/server/remote/requested.js +8 -4
- package/src/runtime/app/server/remote/shared.js +5 -7
- package/src/runtime/app/state/client.js +1 -2
- package/src/runtime/app/stores.js +13 -76
- package/src/runtime/client/client.js +205 -167
- package/src/runtime/client/fetcher.js +3 -2
- package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
- package/src/runtime/client/remote-functions/form.svelte.js +122 -31
- package/src/runtime/client/remote-functions/prerender.svelte.js +17 -4
- package/src/runtime/client/remote-functions/query/index.js +3 -2
- package/src/runtime/client/remote-functions/query/instance.svelte.js +27 -11
- package/src/runtime/client/remote-functions/query-batch.svelte.js +4 -3
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +42 -15
- package/src/runtime/client/remote-functions/shared.svelte.js +18 -9
- package/src/runtime/client/state.svelte.js +66 -49
- package/src/runtime/client/types.d.ts +10 -2
- package/src/runtime/client/utils.js +0 -96
- package/src/runtime/form-utils.js +99 -18
- package/src/runtime/server/cookie.js +22 -33
- package/src/runtime/server/csrf.js +65 -0
- package/src/runtime/server/data/index.js +7 -7
- 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 +2 -1
- package/src/runtime/server/page/load_data.js +1 -1
- package/src/runtime/server/page/render.js +36 -47
- package/src/runtime/server/page/respond_with_error.js +7 -8
- package/src/runtime/server/page/server_routing.js +13 -9
- package/src/runtime/server/remote.js +85 -39
- package/src/runtime/server/respond.js +92 -58
- package/src/runtime/server/utils.js +7 -7
- package/src/runtime/telemetry/otel.js +1 -1
- package/src/types/ambient.d.ts +10 -5
- package/src/types/global-private.d.ts +4 -4
- package/src/types/internal.d.ts +17 -20
- package/src/types/private.d.ts +33 -1
- package/src/types/synthetic/$lib.md +1 -1
- package/src/utils/error.js +12 -4
- package/src/utils/mime.js +9 -0
- package/src/utils/params.js +66 -0
- package/src/utils/routing.js +84 -38
- 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 +434 -269
- package/types/index.d.ts.map +19 -15
- package/src/exports/internal/server.js +0 -22
- /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/** @import { RemoteQueryUpdate } from '@sveltejs/kit' */
|
|
3
3
|
/** @import { CacheEntry } from './cache.svelte.js' */
|
|
4
4
|
import * as devalue from 'devalue';
|
|
5
|
-
import { app,
|
|
5
|
+
import { app, _goto, live_query_map, query_map, query_responses } from '../client.js';
|
|
6
6
|
import { HttpError, Redirect } from '@sveltejs/kit/internal';
|
|
7
7
|
import { untrack } from 'svelte';
|
|
8
8
|
import { create_remote_key, split_remote_key } from '../../shared.js';
|
|
@@ -86,7 +86,7 @@ export function pin_while_resolving(cache_map, cache, id, payload, then) {
|
|
|
86
86
|
*/
|
|
87
87
|
export function unwrap_node(node) {
|
|
88
88
|
if (node.e) {
|
|
89
|
-
throw new HttpError(node.e
|
|
89
|
+
throw new HttpError(node.e.status, node.e);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
return node.v;
|
|
@@ -114,13 +114,22 @@ export async function remote_request(url, init) {
|
|
|
114
114
|
const response = await fetch(url, init);
|
|
115
115
|
|
|
116
116
|
if (!response.ok) {
|
|
117
|
-
|
|
117
|
+
const result = await response.json().catch(() => ({
|
|
118
|
+
type: 'error',
|
|
119
|
+
status: response.status,
|
|
120
|
+
error: response.statusText
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
throw new HttpError(
|
|
124
|
+
result.error?.status ?? result.status ?? response.status ?? 500,
|
|
125
|
+
result.error
|
|
126
|
+
);
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
const result = /** @type {RemoteFunctionResponse} */ (await response.json());
|
|
121
130
|
|
|
122
131
|
if (result.type === 'error') {
|
|
123
|
-
throw new HttpError(result.status
|
|
132
|
+
throw new HttpError(result.error.status, result.error);
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
const data = /** @type {RemoteFunctionData} */ (
|
|
@@ -128,15 +137,14 @@ export async function remote_request(url, init) {
|
|
|
128
137
|
);
|
|
129
138
|
|
|
130
139
|
/**
|
|
131
|
-
*
|
|
132
140
|
* @param {string} key
|
|
133
|
-
* @param {CacheEntry
|
|
141
|
+
* @param {CacheEntry | undefined} entry
|
|
134
142
|
* @param {any} result
|
|
135
143
|
*/
|
|
136
144
|
function refresh(key, entry, result) {
|
|
137
145
|
if (entry?.resource) {
|
|
138
146
|
if (result.e) {
|
|
139
|
-
entry.resource.fail(new HttpError(result.e
|
|
147
|
+
entry.resource.fail(new HttpError(result.e.status, result.e));
|
|
140
148
|
} else {
|
|
141
149
|
entry.resource.set(result.v);
|
|
142
150
|
}
|
|
@@ -184,12 +192,13 @@ export async function remote_request(url, init) {
|
|
|
184
192
|
*/
|
|
185
193
|
export async function handle_side_channel_response(response) {
|
|
186
194
|
if (response.type === 'redirect') {
|
|
187
|
-
|
|
195
|
+
// Use internal version to allow redirects to external URLs
|
|
196
|
+
await _goto(response.location, {}, 0);
|
|
188
197
|
throw new Redirect(307, response.location);
|
|
189
198
|
}
|
|
190
199
|
|
|
191
200
|
if (response.type === 'error') {
|
|
192
|
-
throw new HttpError(response.status
|
|
201
|
+
throw new HttpError(response.error.status, response.error);
|
|
193
202
|
}
|
|
194
203
|
|
|
195
204
|
return response;
|
|
@@ -1,54 +1,71 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { version } from '$app/env';
|
|
2
|
+
import { assets } from '$app/paths/internal/client';
|
|
3
|
+
import { BROWSER, DEV } from 'esm-env';
|
|
3
4
|
|
|
4
5
|
/** @type {import('@sveltejs/kit').Page} */
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
6
|
+
export const page = new (class Page {
|
|
7
|
+
data = $state.raw({});
|
|
8
|
+
form = $state.raw(null);
|
|
9
|
+
error = $state.raw(null);
|
|
10
|
+
params = $state.raw({});
|
|
11
|
+
route = $state.raw({ id: null });
|
|
12
|
+
state = $state.raw({});
|
|
13
|
+
status = $state.raw(-1);
|
|
14
|
+
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
15
|
+
url = $state.raw(new URL('a:'));
|
|
16
|
+
})();
|
|
17
|
+
|
|
18
|
+
export const navigating = new (class Navigating {
|
|
19
|
+
/** @type {import('@sveltejs/kit').Navigation | null} */
|
|
20
|
+
current = $state.raw(null);
|
|
21
|
+
})();
|
|
22
|
+
|
|
23
|
+
export const updated = new (class Updated {
|
|
24
|
+
current = $state.raw(false);
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
26
|
+
check = async () => false;
|
|
27
|
+
})();
|
|
28
|
+
|
|
29
|
+
if (!DEV && BROWSER) {
|
|
30
|
+
const interval = __SVELTEKIT_APP_VERSION_POLL_INTERVAL__;
|
|
31
|
+
|
|
32
|
+
/** @type {number} */
|
|
33
|
+
let timeout;
|
|
34
|
+
|
|
35
|
+
/** @type {() => Promise<boolean>} */
|
|
36
|
+
async function check() {
|
|
37
|
+
window.clearTimeout(timeout);
|
|
38
|
+
|
|
39
|
+
if (interval) timeout = window.setTimeout(check, interval);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
|
|
43
|
+
headers: {
|
|
44
|
+
'cache-control': 'no-cache'
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const data = await res.json();
|
|
53
|
+
const new_update = data.version !== version;
|
|
54
|
+
|
|
55
|
+
if (new_update) {
|
|
56
|
+
updated.current = true;
|
|
57
|
+
window.clearTimeout(timeout);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return new_update;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (interval) timeout = window.setTimeout(check, interval);
|
|
67
|
+
|
|
68
|
+
updated.check = check;
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
/**
|
|
@@ -39,7 +39,7 @@ export interface SvelteKitApp {
|
|
|
39
39
|
dictionary: Record<string, [leaf: number, layouts: number[], errors?: number[]]>;
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* A map of `[matcherName: string]:
|
|
42
|
+
* A map of `[matcherName: string]: ParamMatcher`, which is used to match and parse route parameters.
|
|
43
43
|
*
|
|
44
44
|
* In case of router.resolution=server, this object is empty, as resolution happens on the server.
|
|
45
45
|
*/
|
|
@@ -58,6 +58,12 @@ export interface SvelteKitApp {
|
|
|
58
58
|
hash: boolean;
|
|
59
59
|
|
|
60
60
|
root: typeof SvelteComponent;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Lazily loads the contents of src/error.html, used as a last-resort
|
|
64
|
+
* error page when the root layout's load function throws during client-side rendering.
|
|
65
|
+
*/
|
|
66
|
+
get_error_template: () => Promise<(data: { status: number; message: string }) => string>;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
export type NavigationIntent = {
|
|
@@ -77,6 +83,7 @@ export type NavigationResult = NavigationRedirect | NavigationFinished;
|
|
|
77
83
|
|
|
78
84
|
export type NavigationRedirect = {
|
|
79
85
|
type: 'redirect';
|
|
86
|
+
status: number;
|
|
80
87
|
location: string;
|
|
81
88
|
};
|
|
82
89
|
|
|
@@ -119,7 +126,8 @@ export interface NavigationState {
|
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
export interface HydrateOptions {
|
|
122
|
-
|
|
129
|
+
/** Provided in the case of a form action that returns `fail`, but otherwise derived from `error` */
|
|
130
|
+
status?: number;
|
|
123
131
|
error: App.Error | null;
|
|
124
132
|
node_ids: number[];
|
|
125
133
|
params: Record<string, string>;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { BROWSER, DEV } from 'esm-env';
|
|
2
|
-
import { writable } from 'svelte/store';
|
|
3
|
-
import { assets } from '$app/paths';
|
|
4
|
-
import { version } from '$app/env';
|
|
5
|
-
import { noop } from '../../utils/functions.js';
|
|
6
2
|
import { PRELOAD_PRIORITIES } from './constants.js';
|
|
7
3
|
|
|
8
|
-
/* global __SVELTEKIT_APP_VERSION_FILE__, __SVELTEKIT_APP_VERSION_POLL_INTERVAL__ */
|
|
9
|
-
|
|
10
4
|
export const origin = BROWSER ? location.origin : '';
|
|
11
5
|
|
|
12
6
|
/** @param {string | URL} url */
|
|
@@ -211,96 +205,6 @@ export function get_router_options(element) {
|
|
|
211
205
|
};
|
|
212
206
|
}
|
|
213
207
|
|
|
214
|
-
/** @param {any} value */
|
|
215
|
-
export function notifiable_store(value) {
|
|
216
|
-
const store = writable(value);
|
|
217
|
-
let ready = true;
|
|
218
|
-
|
|
219
|
-
function notify() {
|
|
220
|
-
ready = true;
|
|
221
|
-
store.update((val) => val);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/** @param {any} new_value */
|
|
225
|
-
function set(new_value) {
|
|
226
|
-
ready = false;
|
|
227
|
-
store.set(new_value);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** @param {(value: any) => void} run */
|
|
231
|
-
function subscribe(run) {
|
|
232
|
-
/** @type {any} */
|
|
233
|
-
let old_value;
|
|
234
|
-
return store.subscribe((new_value) => {
|
|
235
|
-
if (old_value === undefined || (ready && new_value !== old_value)) {
|
|
236
|
-
run((old_value = new_value));
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return { notify, set, subscribe };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export const updated_listener = {
|
|
245
|
-
v: noop
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
export function create_updated_store() {
|
|
249
|
-
const { set, subscribe } = writable(false);
|
|
250
|
-
|
|
251
|
-
if (__SVELTEKIT_DEV__ || !BROWSER) {
|
|
252
|
-
return {
|
|
253
|
-
subscribe,
|
|
254
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
255
|
-
check: async () => false
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const interval = __SVELTEKIT_APP_VERSION_POLL_INTERVAL__;
|
|
260
|
-
|
|
261
|
-
/** @type {NodeJS.Timeout} */
|
|
262
|
-
let timeout;
|
|
263
|
-
|
|
264
|
-
/** @type {() => Promise<boolean>} */
|
|
265
|
-
async function check() {
|
|
266
|
-
clearTimeout(timeout);
|
|
267
|
-
|
|
268
|
-
if (interval) timeout = setTimeout(check, interval);
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
|
|
272
|
-
headers: {
|
|
273
|
-
'cache-control': 'no-cache'
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
if (!res.ok) {
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const data = await res.json();
|
|
282
|
-
const updated = data.version !== version;
|
|
283
|
-
|
|
284
|
-
if (updated) {
|
|
285
|
-
set(true);
|
|
286
|
-
updated_listener.v();
|
|
287
|
-
clearTimeout(timeout);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return updated;
|
|
291
|
-
} catch {
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (interval) timeout = setTimeout(check, interval);
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
subscribe,
|
|
300
|
-
check
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
208
|
/**
|
|
305
209
|
* Is external if
|
|
306
210
|
* - origin different
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { DEV } from 'esm-env';
|
|
5
5
|
import * as devalue from 'devalue';
|
|
6
6
|
import { text_encoder } from './utils.js';
|
|
7
|
+
import { noop } from '../utils/functions.js';
|
|
7
8
|
import { SvelteKitError } from '@sveltejs/kit/internal';
|
|
8
9
|
|
|
9
10
|
const decoder = new TextDecoder();
|
|
@@ -26,6 +27,9 @@ export function set_nested_value(object, path_string, value) {
|
|
|
26
27
|
deep_set(object, split_path(path_string), value);
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
/** Pass this to set_nested_value to delete the last part of the given path */
|
|
31
|
+
export const DELETE_KEY = {};
|
|
32
|
+
|
|
29
33
|
/**
|
|
30
34
|
* Convert `FormData` into a POJO
|
|
31
35
|
* @param {FormData} data
|
|
@@ -41,14 +45,11 @@ export function convert_formdata(data) {
|
|
|
41
45
|
|
|
42
46
|
if (is_array) key = key.slice(0, -2);
|
|
43
47
|
|
|
44
|
-
if (values.length > 1 && !is_array) {
|
|
45
|
-
throw new Error(`Form cannot contain duplicated keys — "${key}" has ${values.length} values`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
48
|
// an empty `<input type="file">` will submit a non-existent file, bizarrely
|
|
49
49
|
values = values.filter(
|
|
50
50
|
(entry) => typeof entry === 'string' || entry.name !== '' || entry.size > 0
|
|
51
51
|
);
|
|
52
|
+
if (values.length === 0 && !is_array) continue;
|
|
52
53
|
|
|
53
54
|
if (key.startsWith('n:')) {
|
|
54
55
|
key = key.slice(2);
|
|
@@ -58,6 +59,10 @@ export function convert_formdata(data) {
|
|
|
58
59
|
values = values.map((v) => v === 'on');
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
if (values.length > 1 && !is_array) {
|
|
63
|
+
throw new Error(`Form cannot contain duplicated keys — "${key}" has ${values.length} values`);
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
set_nested_value(result, key, is_array ? values : values[0]);
|
|
62
67
|
}
|
|
63
68
|
|
|
@@ -319,7 +324,7 @@ export async function deserialize_binary_form(request) {
|
|
|
319
324
|
const chunk = await get_chunk(chunks.length);
|
|
320
325
|
has_more = !!chunk;
|
|
321
326
|
}
|
|
322
|
-
})();
|
|
327
|
+
})().catch(noop); // prevent unhandled rejection potentially crashing the process
|
|
323
328
|
|
|
324
329
|
return { data, meta, form_data: null };
|
|
325
330
|
}
|
|
@@ -503,6 +508,10 @@ export function deep_set(object, keys, value) {
|
|
|
503
508
|
}
|
|
504
509
|
|
|
505
510
|
if (!exists) {
|
|
511
|
+
if (value === DELETE_KEY) {
|
|
512
|
+
// don't create the nested structure if we want to delete the key anyway
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
506
515
|
current[key] = is_array ? [] : {};
|
|
507
516
|
}
|
|
508
517
|
|
|
@@ -511,7 +520,12 @@ export function deep_set(object, keys, value) {
|
|
|
511
520
|
|
|
512
521
|
const final_key = keys[keys.length - 1];
|
|
513
522
|
check_prototype_pollution(final_key);
|
|
514
|
-
|
|
523
|
+
|
|
524
|
+
if (value === DELETE_KEY) {
|
|
525
|
+
delete current[final_key];
|
|
526
|
+
} else {
|
|
527
|
+
current[final_key] = value;
|
|
528
|
+
}
|
|
515
529
|
}
|
|
516
530
|
|
|
517
531
|
/**
|
|
@@ -608,18 +622,53 @@ function get_type_prefix(field_type, is_array, input_value) {
|
|
|
608
622
|
return '';
|
|
609
623
|
}
|
|
610
624
|
|
|
625
|
+
/**
|
|
626
|
+
* A deep-clone implementation specifically for form data, where
|
|
627
|
+
* we don't need to worry about cycles and whatnot
|
|
628
|
+
* @param {any} value
|
|
629
|
+
* @returns {any}
|
|
630
|
+
*/
|
|
631
|
+
function deep_clone(value) {
|
|
632
|
+
if (value !== null && typeof value === 'object') {
|
|
633
|
+
if (value instanceof Date) {
|
|
634
|
+
return new Date(value.getTime());
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (value instanceof File) {
|
|
638
|
+
return value;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (Array.isArray(value)) {
|
|
642
|
+
return value.map(deep_clone);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/** @type {Record<string, any>} */
|
|
646
|
+
const clone = {};
|
|
647
|
+
for (const key of Object.keys(value)) {
|
|
648
|
+
clone[key] = deep_clone(value[key]);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return clone;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return value;
|
|
655
|
+
}
|
|
656
|
+
|
|
611
657
|
/**
|
|
612
658
|
* Creates a proxy-based field accessor for form data
|
|
613
659
|
* @param {any} target - Function or empty POJO
|
|
614
|
-
* @param {() => Record<string, any>}
|
|
615
|
-
* @param {(path: (string | number)[], value: any) => void}
|
|
660
|
+
* @param {() => Record<string, any>} get - Function to get current input data
|
|
661
|
+
* @param {(path: (string | number)[], value: any) => void} set - Function to set input data
|
|
616
662
|
* @param {(path?: (string | number)[], all?: boolean) => Record<string, InternalRemoteFormIssue[]>} get_issues - Function to get current issues
|
|
663
|
+
* @param {() => Record<string, boolean>} get_touched - Function to get touched fields
|
|
664
|
+
* @param {() => Record<string, boolean>} get_dirty - Function to get dirty fields
|
|
617
665
|
* @param {(string | number)[]} path - Current access path
|
|
618
666
|
* @returns {any} Proxy object with name(), value(), and issues() methods
|
|
619
667
|
*/
|
|
620
|
-
export function create_field_proxy(target,
|
|
668
|
+
export function create_field_proxy(target, get, set, get_issues, get_touched, get_dirty, path) {
|
|
621
669
|
const get_value = () => {
|
|
622
|
-
|
|
670
|
+
const value = deep_get(get(), path);
|
|
671
|
+
return deep_clone(value);
|
|
623
672
|
};
|
|
624
673
|
|
|
625
674
|
return new Proxy(target, {
|
|
@@ -628,24 +677,26 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
|
|
|
628
677
|
|
|
629
678
|
// Handle array access like jobs[0]
|
|
630
679
|
if (/^\d+$/.test(prop)) {
|
|
631
|
-
return create_field_proxy({},
|
|
680
|
+
return create_field_proxy({}, get, set, get_issues, get_touched, get_dirty, [
|
|
632
681
|
...path,
|
|
633
682
|
parseInt(prop, 10)
|
|
634
683
|
]);
|
|
635
684
|
}
|
|
636
685
|
|
|
637
686
|
const key = build_path_string(path);
|
|
687
|
+
const next = [...path, prop];
|
|
638
688
|
|
|
639
689
|
if (prop === 'set') {
|
|
640
690
|
const set_func = function (/** @type {any} */ newValue) {
|
|
641
|
-
|
|
691
|
+
set(path, newValue);
|
|
642
692
|
return newValue;
|
|
643
693
|
};
|
|
644
|
-
|
|
694
|
+
|
|
695
|
+
return create_field_proxy(set_func, get, set, get_issues, get_touched, get_dirty, next);
|
|
645
696
|
}
|
|
646
697
|
|
|
647
698
|
if (prop === 'value') {
|
|
648
|
-
return create_field_proxy(get_value,
|
|
699
|
+
return create_field_proxy(get_value, get, set, get_issues, get_touched, get_dirty, next);
|
|
649
700
|
}
|
|
650
701
|
|
|
651
702
|
if (prop === 'issues' || prop === 'allIssues') {
|
|
@@ -659,15 +710,45 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
|
|
|
659
710
|
}));
|
|
660
711
|
}
|
|
661
712
|
|
|
662
|
-
|
|
713
|
+
const issues = all_issues
|
|
663
714
|
?.filter((issue) => issue.name === key)
|
|
664
715
|
?.map((issue) => ({
|
|
665
716
|
path: issue.path,
|
|
666
717
|
message: issue.message
|
|
667
718
|
}));
|
|
719
|
+
|
|
720
|
+
return issues?.length ? issues : undefined;
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
return create_field_proxy(issues_func, get, set, get_issues, get_touched, get_dirty, next);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (prop === 'touched' || prop === 'dirty') {
|
|
727
|
+
const fn = () => {
|
|
728
|
+
const object = prop === 'dirty' ? get_dirty() : get_touched();
|
|
729
|
+
|
|
730
|
+
if (key === '') {
|
|
731
|
+
return Object.keys(object).length > 0;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (Object.hasOwn(object, key)) {
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
for (const candidate in object) {
|
|
739
|
+
if (!Object.hasOwn(object, candidate)) continue;
|
|
740
|
+
if (!candidate.startsWith(key)) continue;
|
|
741
|
+
|
|
742
|
+
const next = candidate[key.length];
|
|
743
|
+
if (next === '.' || next === '[') {
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return false;
|
|
668
749
|
};
|
|
669
750
|
|
|
670
|
-
return create_field_proxy(
|
|
751
|
+
return create_field_proxy(fn, get, set, get_issues, get_touched, get_dirty, next);
|
|
671
752
|
}
|
|
672
753
|
|
|
673
754
|
if (prop === 'as') {
|
|
@@ -833,11 +914,11 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
|
|
|
833
914
|
});
|
|
834
915
|
};
|
|
835
916
|
|
|
836
|
-
return create_field_proxy(as_func,
|
|
917
|
+
return create_field_proxy(as_func, get, set, get_issues, get_touched, get_dirty, next);
|
|
837
918
|
}
|
|
838
919
|
|
|
839
920
|
// Handle property access (nested fields)
|
|
840
|
-
return create_field_proxy({},
|
|
921
|
+
return create_field_proxy({}, get, set, get_issues, get_touched, get_dirty, next);
|
|
841
922
|
}
|
|
842
923
|
});
|
|
843
924
|
}
|