@sveltejs/kit 2.26.0 → 2.27.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/README.md +1 -1
- package/package.json +3 -2
- package/src/core/adapt/builder.js +6 -1
- package/src/core/config/options.js +4 -0
- package/src/core/generate_manifest/index.js +3 -0
- package/src/core/postbuild/analyse.js +25 -1
- package/src/core/postbuild/fallback.js +2 -1
- package/src/core/postbuild/prerender.js +41 -10
- package/src/core/sync/create_manifest_data/index.js +35 -1
- package/src/core/sync/write_server.js +4 -2
- package/src/core/sync/write_types/index.js +12 -5
- package/src/exports/index.js +1 -1
- package/src/exports/internal/index.js +3 -1
- package/src/exports/internal/remote-functions.js +21 -0
- package/src/exports/public.d.ts +162 -2
- package/src/exports/vite/build/build_remote.js +129 -0
- package/src/exports/vite/dev/index.js +7 -0
- package/src/exports/vite/index.js +123 -8
- package/src/exports/vite/module_ids.js +3 -2
- package/src/exports/vite/preview/index.js +3 -1
- package/src/runtime/app/navigation.js +1 -0
- package/src/runtime/app/server/index.js +2 -0
- package/src/runtime/app/server/remote/command.js +91 -0
- package/src/runtime/app/server/remote/form.js +124 -0
- package/src/runtime/app/server/remote/index.js +4 -0
- package/src/runtime/app/server/remote/prerender.js +163 -0
- package/src/runtime/app/server/remote/query.js +115 -0
- package/src/runtime/app/server/remote/shared.js +153 -0
- package/src/runtime/client/client.js +107 -39
- package/src/runtime/client/fetcher.js +1 -1
- package/src/runtime/client/remote-functions/command.js +71 -0
- package/src/runtime/client/remote-functions/form.svelte.js +312 -0
- package/src/runtime/client/remote-functions/index.js +4 -0
- package/src/runtime/client/remote-functions/prerender.svelte.js +166 -0
- package/src/runtime/client/remote-functions/query.svelte.js +219 -0
- package/src/runtime/client/remote-functions/shared.svelte.js +143 -0
- package/src/runtime/client/types.d.ts +2 -0
- package/src/runtime/server/data/index.js +6 -4
- package/src/runtime/server/event-state.js +41 -0
- package/src/runtime/server/index.js +12 -3
- package/src/runtime/server/page/actions.js +1 -1
- package/src/runtime/server/page/index.js +10 -3
- package/src/runtime/server/page/load_data.js +18 -12
- package/src/runtime/server/page/render.js +31 -5
- package/src/runtime/server/page/serialize_data.js +1 -1
- package/src/runtime/server/remote.js +237 -0
- package/src/runtime/server/respond.js +57 -36
- package/src/runtime/shared.js +61 -0
- package/src/types/global-private.d.ts +2 -0
- package/src/types/internal.d.ts +51 -4
- package/src/types/synthetic/$env+static+private.md +1 -1
- package/src/utils/routing.js +1 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +266 -3
- package/types/index.d.ts.map +14 -1
- /package/src/{runtime → utils}/hash.js +0 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/** @import { RemoteResource, RemotePrerenderFunction } from '@sveltejs/kit' */
|
|
2
|
+
/** @import { RemotePrerenderInputsGenerator, RemoteInfo, MaybePromise } from 'types' */
|
|
3
|
+
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
|
|
4
|
+
import { error, json } from '@sveltejs/kit';
|
|
5
|
+
import { DEV } from 'esm-env';
|
|
6
|
+
import { getRequestEvent } from '../event.js';
|
|
7
|
+
import { create_remote_cache_key, stringify, stringify_remote_arg } from '../../../shared.js';
|
|
8
|
+
import { app_dir, base } from '__sveltekit/paths';
|
|
9
|
+
import {
|
|
10
|
+
check_experimental,
|
|
11
|
+
create_validator,
|
|
12
|
+
get_response,
|
|
13
|
+
parse_remote_response,
|
|
14
|
+
run_remote_function
|
|
15
|
+
} from './shared.js';
|
|
16
|
+
import { get_event_state } from '../../../server/event-state.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
20
|
+
*
|
|
21
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#prerender) for full documentation.
|
|
22
|
+
*
|
|
23
|
+
* @template Output
|
|
24
|
+
* @overload
|
|
25
|
+
* @param {() => MaybePromise<Output>} fn
|
|
26
|
+
* @param {{ inputs?: RemotePrerenderInputsGenerator<void>, dynamic?: boolean }} [options]
|
|
27
|
+
* @returns {RemotePrerenderFunction<void, Output>}
|
|
28
|
+
* @since 2.27
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
32
|
+
*
|
|
33
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#prerender) for full documentation.
|
|
34
|
+
*
|
|
35
|
+
* @template Input
|
|
36
|
+
* @template Output
|
|
37
|
+
* @overload
|
|
38
|
+
* @param {'unchecked'} validate
|
|
39
|
+
* @param {(arg: Input) => MaybePromise<Output>} fn
|
|
40
|
+
* @param {{ inputs?: RemotePrerenderInputsGenerator<Input>, dynamic?: boolean }} [options]
|
|
41
|
+
* @returns {RemotePrerenderFunction<Input, Output>}
|
|
42
|
+
* @since 2.27
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
46
|
+
*
|
|
47
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#prerender) for full documentation.
|
|
48
|
+
*
|
|
49
|
+
* @template {StandardSchemaV1} Schema
|
|
50
|
+
* @template Output
|
|
51
|
+
* @overload
|
|
52
|
+
* @param {Schema} schema
|
|
53
|
+
* @param {(arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>} fn
|
|
54
|
+
* @param {{ inputs?: RemotePrerenderInputsGenerator<StandardSchemaV1.InferOutput<Schema>>, dynamic?: boolean }} [options]
|
|
55
|
+
* @returns {RemotePrerenderFunction<StandardSchemaV1.InferOutput<Schema>, Output>}
|
|
56
|
+
* @since 2.27
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* @template Input
|
|
60
|
+
* @template Output
|
|
61
|
+
* @param {any} validate_or_fn
|
|
62
|
+
* @param {any} [fn_or_options]
|
|
63
|
+
* @param {{ inputs?: RemotePrerenderInputsGenerator<Input>, dynamic?: boolean }} [maybe_options]
|
|
64
|
+
* @returns {RemotePrerenderFunction<Input, Output>}
|
|
65
|
+
* @since 2.27
|
|
66
|
+
*/
|
|
67
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
68
|
+
export function prerender(validate_or_fn, fn_or_options, maybe_options) {
|
|
69
|
+
check_experimental('prerender');
|
|
70
|
+
|
|
71
|
+
const maybe_fn = typeof fn_or_options === 'function' ? fn_or_options : undefined;
|
|
72
|
+
|
|
73
|
+
/** @type {typeof maybe_options} */
|
|
74
|
+
const options = maybe_options ?? (maybe_fn ? undefined : fn_or_options);
|
|
75
|
+
|
|
76
|
+
/** @type {(arg?: Input) => MaybePromise<Output>} */
|
|
77
|
+
const fn = maybe_fn ?? validate_or_fn;
|
|
78
|
+
|
|
79
|
+
/** @type {(arg?: any) => MaybePromise<Input>} */
|
|
80
|
+
const validate = create_validator(validate_or_fn, maybe_fn);
|
|
81
|
+
|
|
82
|
+
/** @type {RemoteInfo} */
|
|
83
|
+
const __ = {
|
|
84
|
+
type: 'prerender',
|
|
85
|
+
id: '',
|
|
86
|
+
name: '',
|
|
87
|
+
has_arg: !!maybe_fn,
|
|
88
|
+
inputs: options?.inputs,
|
|
89
|
+
dynamic: options?.dynamic
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** @type {RemotePrerenderFunction<Input, Output> & { __: RemoteInfo }} */
|
|
93
|
+
const wrapper = (arg) => {
|
|
94
|
+
/** @type {Promise<Output> & Partial<RemoteResource<Output>>} */
|
|
95
|
+
const promise = (async () => {
|
|
96
|
+
const event = getRequestEvent();
|
|
97
|
+
const state = get_event_state(event);
|
|
98
|
+
const payload = stringify_remote_arg(arg, state.transport);
|
|
99
|
+
const id = __.id;
|
|
100
|
+
const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ''}`;
|
|
101
|
+
|
|
102
|
+
if (!state.prerendering && !DEV && !event.isRemoteRequest) {
|
|
103
|
+
try {
|
|
104
|
+
return await get_response(id, arg, event, async () => {
|
|
105
|
+
// TODO adapters can provide prerendered data more efficiently than
|
|
106
|
+
// fetching from the public internet
|
|
107
|
+
const response = await fetch(new URL(url, event.url.origin).href);
|
|
108
|
+
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new Error('Prerendered response not found');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const prerendered = await response.json();
|
|
114
|
+
|
|
115
|
+
if (prerendered.type === 'error') {
|
|
116
|
+
error(prerendered.status, prerendered.error);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// TODO can we redirect here?
|
|
120
|
+
|
|
121
|
+
(state.remote_data ??= {})[create_remote_cache_key(id, payload)] = prerendered.result;
|
|
122
|
+
return parse_remote_response(prerendered.result, state.transport);
|
|
123
|
+
});
|
|
124
|
+
} catch {
|
|
125
|
+
// not available prerendered, fallback to normal function
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (state.prerendering?.remote_responses.has(url)) {
|
|
130
|
+
return /** @type {Promise<any>} */ (state.prerendering.remote_responses.get(url));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const promise = get_response(id, arg, event, () =>
|
|
134
|
+
run_remote_function(event, false, arg, validate, fn)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (state.prerendering) {
|
|
138
|
+
state.prerendering.remote_responses.set(url, promise);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const result = await promise;
|
|
142
|
+
|
|
143
|
+
if (state.prerendering) {
|
|
144
|
+
const body = { type: 'result', result: stringify(result, state.transport) };
|
|
145
|
+
state.prerendering.dependencies.set(url, {
|
|
146
|
+
body: JSON.stringify(body),
|
|
147
|
+
response: json(body)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// TODO this is missing error/loading/current/status
|
|
152
|
+
return result;
|
|
153
|
+
})();
|
|
154
|
+
|
|
155
|
+
promise.catch(() => {});
|
|
156
|
+
|
|
157
|
+
return /** @type {RemoteResource<Output>} */ (promise);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
Object.defineProperty(wrapper, '__', { value: __ });
|
|
161
|
+
|
|
162
|
+
return wrapper;
|
|
163
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/** @import { RemoteQuery, RemoteQueryFunction } from '@sveltejs/kit' */
|
|
2
|
+
/** @import { RemoteInfo, MaybePromise } from 'types' */
|
|
3
|
+
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
|
|
4
|
+
import { getRequestEvent } from '../event.js';
|
|
5
|
+
import { create_remote_cache_key, stringify_remote_arg } from '../../../shared.js';
|
|
6
|
+
import { prerendering } from '__sveltekit/environment';
|
|
7
|
+
import {
|
|
8
|
+
check_experimental,
|
|
9
|
+
create_validator,
|
|
10
|
+
get_response,
|
|
11
|
+
run_remote_function
|
|
12
|
+
} from './shared.js';
|
|
13
|
+
import { get_event_state } from '../../../server/event-state.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
17
|
+
*
|
|
18
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
|
|
19
|
+
*
|
|
20
|
+
* @template Output
|
|
21
|
+
* @overload
|
|
22
|
+
* @param {() => MaybePromise<Output>} fn
|
|
23
|
+
* @returns {RemoteQueryFunction<void, Output>}
|
|
24
|
+
* @since 2.27
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
28
|
+
*
|
|
29
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
|
|
30
|
+
*
|
|
31
|
+
* @template Input
|
|
32
|
+
* @template Output
|
|
33
|
+
* @overload
|
|
34
|
+
* @param {'unchecked'} validate
|
|
35
|
+
* @param {(arg: Input) => MaybePromise<Output>} fn
|
|
36
|
+
* @returns {RemoteQueryFunction<Input, Output>}
|
|
37
|
+
* @since 2.27
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
41
|
+
*
|
|
42
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
|
|
43
|
+
*
|
|
44
|
+
* @template {StandardSchemaV1} Schema
|
|
45
|
+
* @template Output
|
|
46
|
+
* @overload
|
|
47
|
+
* @param {Schema} schema
|
|
48
|
+
* @param {(arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>} fn
|
|
49
|
+
* @returns {RemoteQueryFunction<StandardSchemaV1.InferOutput<Schema>, Output>}
|
|
50
|
+
* @since 2.27
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* @template Input
|
|
54
|
+
* @template Output
|
|
55
|
+
* @param {any} validate_or_fn
|
|
56
|
+
* @param {(args?: Input) => MaybePromise<Output>} [maybe_fn]
|
|
57
|
+
* @returns {RemoteQueryFunction<Input, Output>}
|
|
58
|
+
* @since 2.27
|
|
59
|
+
*/
|
|
60
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
61
|
+
export function query(validate_or_fn, maybe_fn) {
|
|
62
|
+
check_experimental('query');
|
|
63
|
+
|
|
64
|
+
/** @type {(arg?: Input) => Output} */
|
|
65
|
+
const fn = maybe_fn ?? validate_or_fn;
|
|
66
|
+
|
|
67
|
+
/** @type {(arg?: any) => MaybePromise<Input>} */
|
|
68
|
+
const validate = create_validator(validate_or_fn, maybe_fn);
|
|
69
|
+
|
|
70
|
+
/** @type {RemoteInfo} */
|
|
71
|
+
const __ = { type: 'query', id: '', name: '' };
|
|
72
|
+
|
|
73
|
+
/** @type {RemoteQueryFunction<Input, Output> & { __: RemoteInfo }} */
|
|
74
|
+
const wrapper = (arg) => {
|
|
75
|
+
if (prerendering) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Cannot call query '${__.name}' while prerendering, as prerendered pages need static data. Use 'prerender' from $app/server instead`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const event = getRequestEvent();
|
|
82
|
+
|
|
83
|
+
/** @type {Promise<any> & Partial<RemoteQuery<any>>} */
|
|
84
|
+
const promise = get_response(__.id, arg, event, () =>
|
|
85
|
+
run_remote_function(event, false, arg, validate, fn)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
promise.catch(() => {});
|
|
89
|
+
|
|
90
|
+
promise.refresh = async () => {
|
|
91
|
+
const event = getRequestEvent();
|
|
92
|
+
const state = get_event_state(event);
|
|
93
|
+
const refreshes = state?.refreshes;
|
|
94
|
+
|
|
95
|
+
if (!refreshes) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Cannot call refresh on query '${__.name}' because it is not executed in the context of a command/form remote function`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const cache_key = create_remote_cache_key(__.id, stringify_remote_arg(arg, state.transport));
|
|
102
|
+
refreshes[cache_key] = await /** @type {Promise<any>} */ (promise);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
promise.withOverride = () => {
|
|
106
|
+
throw new Error(`Cannot call '${__.name}.withOverride()' on the server`);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return /** @type {RemoteQuery<Output>} */ (promise);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
Object.defineProperty(wrapper, '__', { value: __ });
|
|
113
|
+
|
|
114
|
+
return wrapper;
|
|
115
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/** @import { RequestEvent } from '@sveltejs/kit' */
|
|
2
|
+
/** @import { ServerHooks, MaybePromise } from 'types' */
|
|
3
|
+
import { parse } from 'devalue';
|
|
4
|
+
import { error } from '@sveltejs/kit';
|
|
5
|
+
import { getRequestEvent, with_event } from '../event.js';
|
|
6
|
+
import { create_remote_cache_key, stringify_remote_arg } from '../../../shared.js';
|
|
7
|
+
import { EVENT_STATE, get_event_state } from '../../../server/event-state.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {any} validate_or_fn
|
|
11
|
+
* @param {(arg?: any) => any} [maybe_fn]
|
|
12
|
+
* @returns {(arg?: any) => MaybePromise<any>}
|
|
13
|
+
*/
|
|
14
|
+
export function create_validator(validate_or_fn, maybe_fn) {
|
|
15
|
+
// prevent functions without validators being called with arguments
|
|
16
|
+
if (!maybe_fn) {
|
|
17
|
+
return (arg) => {
|
|
18
|
+
if (arg !== undefined) {
|
|
19
|
+
error(400, 'Bad Request');
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// if 'unchecked', pass input through without validating
|
|
25
|
+
if (validate_or_fn === 'unchecked') {
|
|
26
|
+
return (arg) => arg;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// use https://standardschema.dev validator if provided
|
|
30
|
+
if ('~standard' in validate_or_fn) {
|
|
31
|
+
return async (arg) => {
|
|
32
|
+
// Get event before async validation to ensure it's available in server environments without AsyncLocalStorage, too
|
|
33
|
+
const event = getRequestEvent();
|
|
34
|
+
const state = get_event_state(event);
|
|
35
|
+
const validate = validate_or_fn['~standard'].validate;
|
|
36
|
+
|
|
37
|
+
const result = await validate(arg);
|
|
38
|
+
|
|
39
|
+
// if the `issues` field exists, the validation failed
|
|
40
|
+
if (result.issues) {
|
|
41
|
+
error(
|
|
42
|
+
400,
|
|
43
|
+
await state.handleValidationError({
|
|
44
|
+
...result,
|
|
45
|
+
event
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return result.value;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
throw new Error(
|
|
55
|
+
'Invalid validator passed to remote function. Expected "unchecked" or a Standard Schema (https://standardschema.dev)'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* In case of a single remote function call, just returns the result.
|
|
61
|
+
*
|
|
62
|
+
* In case of a full page reload, returns the response for a remote function call,
|
|
63
|
+
* either from the cache or by invoking the function.
|
|
64
|
+
* Also saves an uneval'ed version of the result for later HTML inlining for hydration.
|
|
65
|
+
*
|
|
66
|
+
* @template {MaybePromise<any>} T
|
|
67
|
+
* @param {string} id
|
|
68
|
+
* @param {any} arg
|
|
69
|
+
* @param {RequestEvent} event
|
|
70
|
+
* @param {() => Promise<T>} get_result
|
|
71
|
+
* @returns {Promise<T>}
|
|
72
|
+
*/
|
|
73
|
+
export function get_response(id, arg, event, get_result) {
|
|
74
|
+
const state = get_event_state(event);
|
|
75
|
+
const cache_key = create_remote_cache_key(id, stringify_remote_arg(arg, state.transport));
|
|
76
|
+
|
|
77
|
+
return ((state.remote_data ??= {})[cache_key] ??= get_result());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @param {string} feature */
|
|
81
|
+
export function check_experimental(feature) {
|
|
82
|
+
if (!__SVELTEKIT_EXPERIMENTAL__REMOTE_FUNCTIONS__) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Cannot use \`${feature}\` from \`$app/server\` without the experimental flag set to true. Please set kit.experimental.remoteFunctions to \`true\` in your config.`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {any} data
|
|
91
|
+
* @param {ServerHooks['transport']} transport
|
|
92
|
+
*/
|
|
93
|
+
export function parse_remote_response(data, transport) {
|
|
94
|
+
/** @type {Record<string, any>} */
|
|
95
|
+
const revivers = {};
|
|
96
|
+
for (const key in transport) {
|
|
97
|
+
revivers[key] = transport[key].decode;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return parse(data, revivers);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Like `with_event` but removes things from `event` you cannot see/call in remote functions, such as `setHeaders`.
|
|
105
|
+
* @template T
|
|
106
|
+
* @param {RequestEvent} event
|
|
107
|
+
* @param {boolean} allow_cookies
|
|
108
|
+
* @param {any} arg
|
|
109
|
+
* @param {(arg: any) => any} validate
|
|
110
|
+
* @param {(arg?: any) => T} fn
|
|
111
|
+
*/
|
|
112
|
+
export async function run_remote_function(event, allow_cookies, arg, validate, fn) {
|
|
113
|
+
/** @type {RequestEvent} */
|
|
114
|
+
const cleansed = {
|
|
115
|
+
...event,
|
|
116
|
+
// @ts-expect-error this isn't part of the public `RequestEvent` type
|
|
117
|
+
[EVENT_STATE]: event[EVENT_STATE],
|
|
118
|
+
setHeaders: () => {
|
|
119
|
+
throw new Error('setHeaders is not allowed in remote functions');
|
|
120
|
+
},
|
|
121
|
+
cookies: {
|
|
122
|
+
...event.cookies,
|
|
123
|
+
set: (name, value, opts) => {
|
|
124
|
+
if (!allow_cookies) {
|
|
125
|
+
throw new Error('Cannot set cookies in `query` or `prerender` functions');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (opts.path && !opts.path.startsWith('/')) {
|
|
129
|
+
throw new Error('Cookies set in remote functions must have an absolute path');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return event.cookies.set(name, value, opts);
|
|
133
|
+
},
|
|
134
|
+
delete: (name, opts) => {
|
|
135
|
+
if (!allow_cookies) {
|
|
136
|
+
throw new Error('Cannot delete cookies in `query` or `prerender` functions');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (opts.path && !opts.path.startsWith('/')) {
|
|
140
|
+
throw new Error('Cookies deleted in remote functions must have an absolute path');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return event.cookies.delete(name, opts);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
route: { id: null },
|
|
147
|
+
url: new URL(event.url.origin)
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// In two parts, each with_event, so that runtimes without async local storage can still get the event at the start of the function
|
|
151
|
+
const validated = await with_event(cleansed, () => validate(arg));
|
|
152
|
+
return with_event(cleansed, () => fn(validated));
|
|
153
|
+
}
|
|
@@ -46,7 +46,6 @@ import { page, update, navigating } from './state.svelte.js';
|
|
|
46
46
|
import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
|
|
47
47
|
|
|
48
48
|
export { load_css };
|
|
49
|
-
|
|
50
49
|
const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
|
|
51
50
|
|
|
52
51
|
let errored = false;
|
|
@@ -174,9 +173,13 @@ let default_error_loader;
|
|
|
174
173
|
let container;
|
|
175
174
|
/** @type {HTMLElement} */
|
|
176
175
|
let target;
|
|
176
|
+
|
|
177
177
|
/** @type {import('./types.js').SvelteKitApp} */
|
|
178
178
|
export let app;
|
|
179
179
|
|
|
180
|
+
/** @type {Record<string, any>} */
|
|
181
|
+
export let remote_responses;
|
|
182
|
+
|
|
180
183
|
/** @type {Array<((url: URL) => boolean)>} */
|
|
181
184
|
const invalidated = [];
|
|
182
185
|
|
|
@@ -225,7 +228,7 @@ let current = {
|
|
|
225
228
|
|
|
226
229
|
/** this being true means we SSR'd */
|
|
227
230
|
let hydrated = false;
|
|
228
|
-
let started = false;
|
|
231
|
+
export let started = false;
|
|
229
232
|
let autoscroll = true;
|
|
230
233
|
let updating = false;
|
|
231
234
|
let is_navigating = false;
|
|
@@ -256,7 +259,13 @@ let token;
|
|
|
256
259
|
const preload_tokens = new Set();
|
|
257
260
|
|
|
258
261
|
/** @type {Promise<void> | null} */
|
|
259
|
-
let pending_invalidate;
|
|
262
|
+
export let pending_invalidate;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @type {Map<string, {count: number, resource: any}>}
|
|
266
|
+
* A map of id -> query info with all queries that currently exist in the app.
|
|
267
|
+
*/
|
|
268
|
+
export const query_map = new Map();
|
|
260
269
|
|
|
261
270
|
/**
|
|
262
271
|
* @param {import('./types.js').SvelteKitApp} _app
|
|
@@ -279,6 +288,7 @@ export async function start(_app, _target, hydrate) {
|
|
|
279
288
|
}
|
|
280
289
|
|
|
281
290
|
app = _app;
|
|
291
|
+
remote_responses = hydrate?.remote ?? {};
|
|
282
292
|
|
|
283
293
|
await _app.hooks.init?.();
|
|
284
294
|
|
|
@@ -339,7 +349,7 @@ export async function start(_app, _target, hydrate) {
|
|
|
339
349
|
_start_router();
|
|
340
350
|
}
|
|
341
351
|
|
|
342
|
-
async function _invalidate() {
|
|
352
|
+
async function _invalidate(include_load_functions = true, reset_page_state = true) {
|
|
343
353
|
// Accept all invalidations as they come, don't swallow any while another invalidation
|
|
344
354
|
// is running because subsequent invalidations may make earlier ones outdated,
|
|
345
355
|
// but batch multiple synchronous invalidations.
|
|
@@ -356,20 +366,36 @@ async function _invalidate() {
|
|
|
356
366
|
// at which point the invalidation should take over and "win".
|
|
357
367
|
load_cache = null;
|
|
358
368
|
|
|
359
|
-
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
369
|
+
// Rerun queries
|
|
370
|
+
if (force_invalidation) {
|
|
371
|
+
query_map.forEach(({ resource }) => {
|
|
372
|
+
resource.refresh?.();
|
|
373
|
+
});
|
|
364
374
|
}
|
|
365
375
|
|
|
366
|
-
if (
|
|
367
|
-
|
|
376
|
+
if (include_load_functions) {
|
|
377
|
+
const prev_state = page.state;
|
|
378
|
+
const navigation_result = intent && (await load_route(intent));
|
|
379
|
+
if (!navigation_result || nav_token !== token) return;
|
|
380
|
+
|
|
381
|
+
if (navigation_result.type === 'redirect') {
|
|
382
|
+
return _goto(new URL(navigation_result.location, current.url).href, {}, 1, nav_token);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// This is a bit hacky but allows us not having to pass that boolean around, making things harder to reason about
|
|
386
|
+
if (!reset_page_state) {
|
|
387
|
+
navigation_result.props.page.state = prev_state;
|
|
388
|
+
}
|
|
389
|
+
update(navigation_result.props.page);
|
|
390
|
+
current = navigation_result.state;
|
|
391
|
+
reset_invalidation();
|
|
392
|
+
root.$set(navigation_result.props);
|
|
393
|
+
} else {
|
|
394
|
+
reset_invalidation();
|
|
368
395
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
update(navigation_result.props.page);
|
|
396
|
+
|
|
397
|
+
// Don't use allSettled yet because it's too new
|
|
398
|
+
await Promise.all([...query_map.values()].map(({ resource }) => resource)).catch(noop);
|
|
373
399
|
}
|
|
374
400
|
|
|
375
401
|
function reset_invalidation() {
|
|
@@ -406,7 +432,9 @@ function persist_state() {
|
|
|
406
432
|
* @param {{}} [nav_token]
|
|
407
433
|
*/
|
|
408
434
|
async function _goto(url, options, redirect_count, nav_token) {
|
|
409
|
-
|
|
435
|
+
/** @type {string[]} */
|
|
436
|
+
let query_keys;
|
|
437
|
+
const result = await navigate({
|
|
410
438
|
type: 'goto',
|
|
411
439
|
url: resolve_url(url),
|
|
412
440
|
keepfocus: options.keepFocus,
|
|
@@ -418,6 +446,7 @@ async function _goto(url, options, redirect_count, nav_token) {
|
|
|
418
446
|
accept: () => {
|
|
419
447
|
if (options.invalidateAll) {
|
|
420
448
|
force_invalidation = true;
|
|
449
|
+
query_keys = [...query_map.keys()];
|
|
421
450
|
}
|
|
422
451
|
|
|
423
452
|
if (options.invalidate) {
|
|
@@ -425,6 +454,22 @@ async function _goto(url, options, redirect_count, nav_token) {
|
|
|
425
454
|
}
|
|
426
455
|
}
|
|
427
456
|
});
|
|
457
|
+
if (options.invalidateAll) {
|
|
458
|
+
// TODO the ticks shouldn't be necessary, something inside Svelte itself is buggy
|
|
459
|
+
// when a query in a layout that still exists after page change is refreshed earlier than this
|
|
460
|
+
void svelte
|
|
461
|
+
.tick()
|
|
462
|
+
.then(svelte.tick)
|
|
463
|
+
.then(() => {
|
|
464
|
+
query_map.forEach(({ resource }, key) => {
|
|
465
|
+
// Only refresh those that already existed on the old page
|
|
466
|
+
if (query_keys?.includes(key)) {
|
|
467
|
+
resource.refresh?.();
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
return result;
|
|
428
473
|
}
|
|
429
474
|
|
|
430
475
|
/** @param {import('./types.js').NavigationIntent} intent */
|
|
@@ -1993,6 +2038,21 @@ export function invalidateAll() {
|
|
|
1993
2038
|
return _invalidate();
|
|
1994
2039
|
}
|
|
1995
2040
|
|
|
2041
|
+
/**
|
|
2042
|
+
* Causes all currently active remote functions to refresh, and all `load` functions belonging to the currently active page to re-run (unless disabled via the option argument).
|
|
2043
|
+
* Returns a `Promise` that resolves when the page is subsequently updated.
|
|
2044
|
+
* @param {{ includeLoadFunctions?: boolean }} [options]
|
|
2045
|
+
* @returns {Promise<void>}
|
|
2046
|
+
*/
|
|
2047
|
+
export function refreshAll({ includeLoadFunctions = true } = {}) {
|
|
2048
|
+
if (!BROWSER) {
|
|
2049
|
+
throw new Error('Cannot call refreshAll() on the server');
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
force_invalidation = true;
|
|
2053
|
+
return _invalidate(includeLoadFunctions, false);
|
|
2054
|
+
}
|
|
2055
|
+
|
|
1996
2056
|
/**
|
|
1997
2057
|
* Programmatically preloads the given page, which means
|
|
1998
2058
|
* 1. ensuring that the code for the page is loaded, and
|
|
@@ -2174,29 +2234,7 @@ export async function applyAction(result) {
|
|
|
2174
2234
|
}
|
|
2175
2235
|
|
|
2176
2236
|
if (result.type === 'error') {
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
const { branch, route } = current;
|
|
2180
|
-
if (!route) return;
|
|
2181
|
-
|
|
2182
|
-
const error_load = await load_nearest_error_page(current.branch.length, branch, route.errors);
|
|
2183
|
-
if (error_load) {
|
|
2184
|
-
const navigation_result = get_navigation_result_from_branch({
|
|
2185
|
-
url,
|
|
2186
|
-
params: current.params,
|
|
2187
|
-
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
2188
|
-
status: result.status ?? 500,
|
|
2189
|
-
error: result.error,
|
|
2190
|
-
route
|
|
2191
|
-
});
|
|
2192
|
-
|
|
2193
|
-
current = navigation_result.state;
|
|
2194
|
-
|
|
2195
|
-
root.$set(navigation_result.props);
|
|
2196
|
-
update(navigation_result.props.page);
|
|
2197
|
-
|
|
2198
|
-
void tick().then(() => reset_focus(current.url));
|
|
2199
|
-
}
|
|
2237
|
+
await set_nearest_error_page(result.error, result.status);
|
|
2200
2238
|
} else if (result.type === 'redirect') {
|
|
2201
2239
|
await _goto(result.location, { invalidateAll: true }, 0);
|
|
2202
2240
|
} else {
|
|
@@ -2221,6 +2259,36 @@ export async function applyAction(result) {
|
|
|
2221
2259
|
}
|
|
2222
2260
|
}
|
|
2223
2261
|
|
|
2262
|
+
/**
|
|
2263
|
+
* @param {App.Error} error
|
|
2264
|
+
* @param {number} status
|
|
2265
|
+
*/
|
|
2266
|
+
export async function set_nearest_error_page(error, status = 500) {
|
|
2267
|
+
const url = new URL(location.href);
|
|
2268
|
+
|
|
2269
|
+
const { branch, route } = current;
|
|
2270
|
+
if (!route) return;
|
|
2271
|
+
|
|
2272
|
+
const error_load = await load_nearest_error_page(current.branch.length, branch, route.errors);
|
|
2273
|
+
if (error_load) {
|
|
2274
|
+
const navigation_result = get_navigation_result_from_branch({
|
|
2275
|
+
url,
|
|
2276
|
+
params: current.params,
|
|
2277
|
+
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
2278
|
+
status,
|
|
2279
|
+
error,
|
|
2280
|
+
route
|
|
2281
|
+
});
|
|
2282
|
+
|
|
2283
|
+
current = navigation_result.state;
|
|
2284
|
+
|
|
2285
|
+
root.$set(navigation_result.props);
|
|
2286
|
+
update(navigation_result.props.page);
|
|
2287
|
+
|
|
2288
|
+
void tick().then(() => reset_focus(current.url));
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2224
2292
|
function _start_router() {
|
|
2225
2293
|
history.scrollRestoration = 'manual';
|
|
2226
2294
|
|