@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,237 @@
|
|
|
1
|
+
/** @import { ActionResult, RemoteForm, RequestEvent, SSRManifest } from '@sveltejs/kit' */
|
|
2
|
+
/** @import { RemoteFunctionResponse, RemoteInfo, SSROptions } from 'types' */
|
|
3
|
+
|
|
4
|
+
import { json, error } from '@sveltejs/kit';
|
|
5
|
+
import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal';
|
|
6
|
+
import { app_dir, base } from '__sveltekit/paths';
|
|
7
|
+
import { with_event } from '../app/server/event.js';
|
|
8
|
+
import { is_form_content_type } from '../../utils/http.js';
|
|
9
|
+
import { parse_remote_arg, stringify } from '../shared.js';
|
|
10
|
+
import { handle_error_and_jsonify } from './utils.js';
|
|
11
|
+
import { normalize_error } from '../../utils/error.js';
|
|
12
|
+
import { check_incorrect_fail_use } from './page/actions.js';
|
|
13
|
+
import { DEV } from 'esm-env';
|
|
14
|
+
import { get_event_state } from './event-state.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {RequestEvent} event
|
|
18
|
+
* @param {SSROptions} options
|
|
19
|
+
* @param {SSRManifest} manifest
|
|
20
|
+
* @param {string} id
|
|
21
|
+
*/
|
|
22
|
+
export async function handle_remote_call(event, options, manifest, id) {
|
|
23
|
+
const [hash, name, prerender_args] = id.split('/');
|
|
24
|
+
const remotes = manifest._.remotes;
|
|
25
|
+
|
|
26
|
+
if (!remotes[hash]) error(404);
|
|
27
|
+
|
|
28
|
+
const module = await remotes[hash]();
|
|
29
|
+
const fn = module[name];
|
|
30
|
+
|
|
31
|
+
if (!fn) error(404);
|
|
32
|
+
|
|
33
|
+
/** @type {RemoteInfo} */
|
|
34
|
+
const info = fn.__;
|
|
35
|
+
const transport = options.hooks.transport;
|
|
36
|
+
|
|
37
|
+
/** @type {string[] | undefined} */
|
|
38
|
+
let form_client_refreshes;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
if (info.type === 'form') {
|
|
42
|
+
if (!is_form_content_type(event.request)) {
|
|
43
|
+
throw new SvelteKitError(
|
|
44
|
+
415,
|
|
45
|
+
'Unsupported Media Type',
|
|
46
|
+
`Form actions expect form-encoded data — received ${event.request.headers.get(
|
|
47
|
+
'content-type'
|
|
48
|
+
)}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const form_data = await event.request.formData();
|
|
53
|
+
form_client_refreshes = JSON.parse(
|
|
54
|
+
/** @type {string} */ (form_data.get('sveltekit:remote_refreshes')) ?? '[]'
|
|
55
|
+
);
|
|
56
|
+
form_data.delete('sveltekit:remote_refreshes');
|
|
57
|
+
|
|
58
|
+
const fn = info.fn;
|
|
59
|
+
const data = await with_event(event, () => fn(form_data));
|
|
60
|
+
|
|
61
|
+
return json(
|
|
62
|
+
/** @type {RemoteFunctionResponse} */ ({
|
|
63
|
+
type: 'result',
|
|
64
|
+
result: stringify(data, transport),
|
|
65
|
+
refreshes: stringify(
|
|
66
|
+
{
|
|
67
|
+
...get_event_state(event).refreshes,
|
|
68
|
+
...(await apply_client_refreshes(/** @type {string[]} */ (form_client_refreshes)))
|
|
69
|
+
},
|
|
70
|
+
transport
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (info.type === 'command') {
|
|
77
|
+
/** @type {{ payload: string, refreshes: string[] }} */
|
|
78
|
+
const { payload, refreshes } = await event.request.json();
|
|
79
|
+
const arg = parse_remote_arg(payload, transport);
|
|
80
|
+
const data = await with_event(event, () => fn(arg));
|
|
81
|
+
const refreshed = await apply_client_refreshes(refreshes);
|
|
82
|
+
|
|
83
|
+
return json(
|
|
84
|
+
/** @type {RemoteFunctionResponse} */ ({
|
|
85
|
+
type: 'result',
|
|
86
|
+
result: stringify(data, transport),
|
|
87
|
+
refreshes: stringify({ ...get_event_state(event).refreshes, ...refreshed }, transport)
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const payload =
|
|
93
|
+
info.type === 'prerender'
|
|
94
|
+
? prerender_args
|
|
95
|
+
: /** @type {string} */ (
|
|
96
|
+
// new URL(...) necessary because we're hiding the URL from the user in the event object
|
|
97
|
+
new URL(event.request.url).searchParams.get('payload')
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const data = await with_event(event, () => fn(parse_remote_arg(payload, transport)));
|
|
101
|
+
|
|
102
|
+
return json(
|
|
103
|
+
/** @type {RemoteFunctionResponse} */ ({
|
|
104
|
+
type: 'result',
|
|
105
|
+
result: stringify(data, transport)
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error instanceof Redirect) {
|
|
110
|
+
const refreshes = {
|
|
111
|
+
...(get_event_state(event).refreshes ?? {}), // could be set by form actions
|
|
112
|
+
...(await apply_client_refreshes(form_client_refreshes ?? []))
|
|
113
|
+
};
|
|
114
|
+
return json({
|
|
115
|
+
type: 'redirect',
|
|
116
|
+
location: error.location,
|
|
117
|
+
refreshes: Object.keys(refreshes).length > 0 ? stringify(refreshes, transport) : undefined
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return json(
|
|
122
|
+
/** @type {RemoteFunctionResponse} */ ({
|
|
123
|
+
type: 'error',
|
|
124
|
+
error: await handle_error_and_jsonify(event, options, error),
|
|
125
|
+
status: error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500
|
|
126
|
+
}),
|
|
127
|
+
{
|
|
128
|
+
headers: {
|
|
129
|
+
'cache-control': 'private, no-store'
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** @param {string[]} refreshes */
|
|
136
|
+
async function apply_client_refreshes(refreshes) {
|
|
137
|
+
return Object.fromEntries(
|
|
138
|
+
await Promise.all(
|
|
139
|
+
refreshes.map(async (key) => {
|
|
140
|
+
const [hash, name, payload] = key.split('/');
|
|
141
|
+
const loader = manifest._.remotes[hash];
|
|
142
|
+
|
|
143
|
+
// TODO what do we do in this case? erroring after the mutation has happened is not great
|
|
144
|
+
if (!loader) error(400, 'Bad Request');
|
|
145
|
+
|
|
146
|
+
const module = await loader();
|
|
147
|
+
const fn = module[name];
|
|
148
|
+
|
|
149
|
+
if (!fn) error(400, 'Bad Request');
|
|
150
|
+
|
|
151
|
+
return [key, await with_event(event, () => fn(parse_remote_arg(payload, transport)))];
|
|
152
|
+
})
|
|
153
|
+
)
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @param {RequestEvent} event
|
|
160
|
+
* @param {SSRManifest} manifest
|
|
161
|
+
* @param {string} id
|
|
162
|
+
* @returns {Promise<ActionResult>}
|
|
163
|
+
*/
|
|
164
|
+
export async function handle_remote_form_post(event, manifest, id) {
|
|
165
|
+
const [hash, name, action_id] = id.split('/');
|
|
166
|
+
const remotes = manifest._.remotes;
|
|
167
|
+
const module = await remotes[hash]?.();
|
|
168
|
+
|
|
169
|
+
let form = /** @type {RemoteForm<any>} */ (module?.[name]);
|
|
170
|
+
|
|
171
|
+
if (!form) {
|
|
172
|
+
event.setHeaders({
|
|
173
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
|
|
174
|
+
// "The server must generate an Allow header field in a 405 status code response"
|
|
175
|
+
allow: 'GET'
|
|
176
|
+
});
|
|
177
|
+
return {
|
|
178
|
+
type: 'error',
|
|
179
|
+
error: new SvelteKitError(
|
|
180
|
+
405,
|
|
181
|
+
'Method Not Allowed',
|
|
182
|
+
`POST method not allowed. No form actions exist for ${DEV ? `the page at ${event.route.id}` : 'this page'}`
|
|
183
|
+
)
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (action_id) {
|
|
188
|
+
// @ts-expect-error
|
|
189
|
+
form = with_event(event, () => form.for(JSON.parse(action_id)));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const form_data = await event.request.formData();
|
|
194
|
+
const fn = /** @type {RemoteInfo & { type: 'form' }} */ (/** @type {any} */ (form).__).fn;
|
|
195
|
+
|
|
196
|
+
await with_event(event, () => fn(form_data));
|
|
197
|
+
|
|
198
|
+
// We don't want the data to appear on `let { form } = $props()`, which is why we're not returning it.
|
|
199
|
+
// It is instead available on `myForm.result`, setting of which happens within the remote `form` function.
|
|
200
|
+
return {
|
|
201
|
+
type: 'success',
|
|
202
|
+
status: 200
|
|
203
|
+
};
|
|
204
|
+
} catch (e) {
|
|
205
|
+
const err = normalize_error(e);
|
|
206
|
+
|
|
207
|
+
if (err instanceof Redirect) {
|
|
208
|
+
return {
|
|
209
|
+
type: 'redirect',
|
|
210
|
+
status: err.status,
|
|
211
|
+
location: err.location
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
type: 'error',
|
|
217
|
+
error: check_incorrect_fail_use(err)
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @param {URL} url
|
|
224
|
+
*/
|
|
225
|
+
export function get_remote_id(url) {
|
|
226
|
+
return (
|
|
227
|
+
url.pathname.startsWith(`${base}/${app_dir}/remote/`) &&
|
|
228
|
+
url.pathname.replace(`${base}/${app_dir}/remote/`, '')
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {URL} url
|
|
234
|
+
*/
|
|
235
|
+
export function get_remote_action(url) {
|
|
236
|
+
return url.searchParams.get('/remote');
|
|
237
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEV } from 'esm-env';
|
|
2
2
|
import { json, text } from '@sveltejs/kit';
|
|
3
|
-
import {
|
|
3
|
+
import { Redirect, SvelteKitError } from '@sveltejs/kit/internal';
|
|
4
4
|
import { base, app_dir } from '__sveltekit/paths';
|
|
5
5
|
import { is_endpoint_request, render_endpoint } from './endpoint.js';
|
|
6
6
|
import { render_page } from './page/index.js';
|
|
@@ -33,7 +33,9 @@ import {
|
|
|
33
33
|
strip_data_suffix,
|
|
34
34
|
strip_resolution_suffix
|
|
35
35
|
} from '../pathname.js';
|
|
36
|
+
import { get_remote_id, handle_remote_call } from './remote.js';
|
|
36
37
|
import { with_event } from '../app/server/event.js';
|
|
38
|
+
import { create_event_state, EVENT_STATE } from './event-state.js';
|
|
37
39
|
|
|
38
40
|
/* global __SVELTEKIT_ADAPTER_NAME__ */
|
|
39
41
|
/* global __SVELTEKIT_DEV__ */
|
|
@@ -64,24 +66,35 @@ export async function respond(request, options, manifest, state) {
|
|
|
64
66
|
/** URL but stripped from the potential `/__data.json` suffix and its search param */
|
|
65
67
|
const url = new URL(request.url);
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
const is_route_resolution_request = has_resolution_suffix(url.pathname);
|
|
70
|
+
const is_data_request = has_data_suffix(url.pathname);
|
|
71
|
+
const remote_id = get_remote_id(url);
|
|
72
|
+
|
|
73
|
+
if (options.csrf_check_origin && request.headers.get('origin') !== url.origin) {
|
|
74
|
+
const opts = { status: 403 };
|
|
75
|
+
|
|
76
|
+
if (remote_id && request.method !== 'GET') {
|
|
77
|
+
return json(
|
|
78
|
+
{
|
|
79
|
+
message: 'Cross-site remote requests are forbidden'
|
|
80
|
+
},
|
|
81
|
+
opts
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
68
85
|
const forbidden =
|
|
69
86
|
is_form_content_type(request) &&
|
|
70
87
|
(request.method === 'POST' ||
|
|
71
88
|
request.method === 'PUT' ||
|
|
72
89
|
request.method === 'PATCH' ||
|
|
73
|
-
request.method === 'DELETE')
|
|
74
|
-
request.headers.get('origin') !== url.origin;
|
|
90
|
+
request.method === 'DELETE');
|
|
75
91
|
|
|
76
92
|
if (forbidden) {
|
|
77
|
-
const
|
|
78
|
-
403,
|
|
79
|
-
`Cross-site ${request.method} form submissions are forbidden`
|
|
80
|
-
);
|
|
93
|
+
const message = `Cross-site ${request.method} form submissions are forbidden`;
|
|
81
94
|
if (request.headers.get('accept') === 'application/json') {
|
|
82
|
-
return json(
|
|
95
|
+
return json({ message }, opts);
|
|
83
96
|
}
|
|
84
|
-
return text(
|
|
97
|
+
return text(message, opts);
|
|
85
98
|
}
|
|
86
99
|
}
|
|
87
100
|
|
|
@@ -92,14 +105,11 @@ export async function respond(request, options, manifest, state) {
|
|
|
92
105
|
/** @type {boolean[] | undefined} */
|
|
93
106
|
let invalidated_data_nodes;
|
|
94
107
|
|
|
95
|
-
/**
|
|
96
|
-
* If the request is for a route resolution, first modify the URL, then continue as normal
|
|
97
|
-
* for path resolution, then return the route object as a JS file.
|
|
98
|
-
*/
|
|
99
|
-
const is_route_resolution_request = has_resolution_suffix(url.pathname);
|
|
100
|
-
const is_data_request = has_data_suffix(url.pathname);
|
|
101
|
-
|
|
102
108
|
if (is_route_resolution_request) {
|
|
109
|
+
/**
|
|
110
|
+
* If the request is for a route resolution, first modify the URL, then continue as normal
|
|
111
|
+
* for path resolution, then return the route object as a JS file.
|
|
112
|
+
*/
|
|
103
113
|
url.pathname = strip_resolution_suffix(url.pathname);
|
|
104
114
|
} else if (is_data_request) {
|
|
105
115
|
url.pathname =
|
|
@@ -111,6 +121,9 @@ export async function respond(request, options, manifest, state) {
|
|
|
111
121
|
?.split('')
|
|
112
122
|
.map((node) => node === '1');
|
|
113
123
|
url.searchParams.delete(INVALIDATED_PARAM);
|
|
124
|
+
} else if (remote_id) {
|
|
125
|
+
url.pathname = base;
|
|
126
|
+
url.search = '';
|
|
114
127
|
}
|
|
115
128
|
|
|
116
129
|
/** @type {Record<string, string>} */
|
|
@@ -123,6 +136,7 @@ export async function respond(request, options, manifest, state) {
|
|
|
123
136
|
|
|
124
137
|
/** @type {import('@sveltejs/kit').RequestEvent} */
|
|
125
138
|
const event = {
|
|
139
|
+
[EVENT_STATE]: create_event_state(state, options),
|
|
126
140
|
cookies,
|
|
127
141
|
// @ts-expect-error `fetch` needs to be created after the `event` itself
|
|
128
142
|
fetch: null,
|
|
@@ -164,7 +178,8 @@ export async function respond(request, options, manifest, state) {
|
|
|
164
178
|
},
|
|
165
179
|
url,
|
|
166
180
|
isDataRequest: is_data_request,
|
|
167
|
-
isSubRequest: state.depth > 0
|
|
181
|
+
isSubRequest: state.depth > 0,
|
|
182
|
+
isRemoteRequest: !!remote_id
|
|
168
183
|
};
|
|
169
184
|
|
|
170
185
|
event.fetch = create_fetch({
|
|
@@ -183,23 +198,25 @@ export async function respond(request, options, manifest, state) {
|
|
|
183
198
|
});
|
|
184
199
|
}
|
|
185
200
|
|
|
186
|
-
let resolved_path;
|
|
187
|
-
|
|
188
|
-
const prerendering_reroute_state = state.prerendering?.inside_reroute;
|
|
189
|
-
try {
|
|
190
|
-
// For the duration or a reroute, disable the prerendering state as reroute could call API endpoints
|
|
191
|
-
// which would end up in the wrong logic path if not disabled.
|
|
192
|
-
if (state.prerendering) state.prerendering.inside_reroute = true;
|
|
201
|
+
let resolved_path = url.pathname;
|
|
193
202
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
if (!remote_id) {
|
|
204
|
+
const prerendering_reroute_state = state.prerendering?.inside_reroute;
|
|
205
|
+
try {
|
|
206
|
+
// For the duration or a reroute, disable the prerendering state as reroute could call API endpoints
|
|
207
|
+
// which would end up in the wrong logic path if not disabled.
|
|
208
|
+
if (state.prerendering) state.prerendering.inside_reroute = true;
|
|
209
|
+
|
|
210
|
+
// reroute could alter the given URL, so we pass a copy
|
|
211
|
+
resolved_path =
|
|
212
|
+
(await options.hooks.reroute({ url: new URL(url), fetch: event.fetch })) ?? url.pathname;
|
|
213
|
+
} catch {
|
|
214
|
+
return text('Internal Server Error', {
|
|
215
|
+
status: 500
|
|
216
|
+
});
|
|
217
|
+
} finally {
|
|
218
|
+
if (state.prerendering) state.prerendering.inside_reroute = prerendering_reroute_state;
|
|
219
|
+
}
|
|
203
220
|
}
|
|
204
221
|
|
|
205
222
|
try {
|
|
@@ -254,14 +271,14 @@ export async function respond(request, options, manifest, state) {
|
|
|
254
271
|
return get_public_env(request);
|
|
255
272
|
}
|
|
256
273
|
|
|
257
|
-
if (resolved_path.startsWith(`/${app_dir}`)) {
|
|
274
|
+
if (!remote_id && resolved_path.startsWith(`/${app_dir}`)) {
|
|
258
275
|
// Ensure that 404'd static assets are not cached - some adapters might apply caching by default
|
|
259
276
|
const headers = new Headers();
|
|
260
277
|
headers.set('cache-control', 'public, max-age=0, must-revalidate');
|
|
261
278
|
return text('Not found', { status: 404, headers });
|
|
262
279
|
}
|
|
263
280
|
|
|
264
|
-
if (!state.prerendering?.fallback) {
|
|
281
|
+
if (!state.prerendering?.fallback && !remote_id) {
|
|
265
282
|
// TODO this could theoretically break — should probably be inside a try-catch
|
|
266
283
|
const matchers = await manifest._.matchers();
|
|
267
284
|
|
|
@@ -476,6 +493,10 @@ export async function respond(request, options, manifest, state) {
|
|
|
476
493
|
});
|
|
477
494
|
}
|
|
478
495
|
|
|
496
|
+
if (remote_id) {
|
|
497
|
+
return await handle_remote_call(event, options, manifest, remote_id);
|
|
498
|
+
}
|
|
499
|
+
|
|
479
500
|
if (route) {
|
|
480
501
|
const method = /** @type {import('types').HttpMethod} */ (event.request.method);
|
|
481
502
|
|
package/src/runtime/shared.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/** @import { Transport } from '@sveltejs/kit' */
|
|
2
|
+
import * as devalue from 'devalue';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* @param {string} route_id
|
|
3
6
|
* @param {string} dep
|
|
@@ -14,3 +17,61 @@ export function validate_depends(route_id, dep) {
|
|
|
14
17
|
export const INVALIDATED_PARAM = 'x-sveltekit-invalidated';
|
|
15
18
|
|
|
16
19
|
export const TRAILING_SLASH_PARAM = 'x-sveltekit-trailing-slash';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Try to `devalue.stringify` the data object using the provided transport encoders.
|
|
23
|
+
* @param {any} data
|
|
24
|
+
* @param {Transport} transport
|
|
25
|
+
*/
|
|
26
|
+
export function stringify(data, transport) {
|
|
27
|
+
const encoders = Object.fromEntries(Object.entries(transport).map(([k, v]) => [k, v.encode]));
|
|
28
|
+
|
|
29
|
+
return devalue.stringify(data, encoders);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Stringifies the argument (if any) for a remote function in such a way that
|
|
34
|
+
* it is both a valid URL and a valid file name (necessary for prerendering).
|
|
35
|
+
* @param {any} value
|
|
36
|
+
* @param {Transport} transport
|
|
37
|
+
*/
|
|
38
|
+
export function stringify_remote_arg(value, transport) {
|
|
39
|
+
if (value === undefined) return '';
|
|
40
|
+
|
|
41
|
+
// If people hit file/url size limits, we can look into using something like compress_and_encode_text from svelte.dev beyond a certain size
|
|
42
|
+
const json_string = stringify(value, transport);
|
|
43
|
+
|
|
44
|
+
// Convert to UTF-8 bytes, then base64 - handles all Unicode properly (btoa would fail on exotic characters)
|
|
45
|
+
const utf8_bytes = new TextEncoder().encode(json_string);
|
|
46
|
+
return btoa(String.fromCharCode(...utf8_bytes))
|
|
47
|
+
.replace(/=/g, '')
|
|
48
|
+
.replace(/\+/g, '-')
|
|
49
|
+
.replace(/\//g, '_');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Parses the argument (if any) for a remote function
|
|
54
|
+
* @param {string} string
|
|
55
|
+
* @param {Transport} transport
|
|
56
|
+
*/
|
|
57
|
+
export function parse_remote_arg(string, transport) {
|
|
58
|
+
if (!string) return undefined;
|
|
59
|
+
|
|
60
|
+
const decoders = Object.fromEntries(Object.entries(transport).map(([k, v]) => [k, v.decode]));
|
|
61
|
+
|
|
62
|
+
// We don't need to add back the `=`-padding because atob can handle it
|
|
63
|
+
const base64_restored = string.replace(/-/g, '+').replace(/_/g, '/');
|
|
64
|
+
const binary_string = atob(base64_restored);
|
|
65
|
+
const utf8_bytes = new Uint8Array([...binary_string].map((char) => char.charCodeAt(0)));
|
|
66
|
+
const json_string = new TextDecoder().decode(utf8_bytes);
|
|
67
|
+
|
|
68
|
+
return devalue.parse(json_string, decoders);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {string} id
|
|
73
|
+
* @param {string} payload
|
|
74
|
+
*/
|
|
75
|
+
export function create_remote_cache_key(id, payload) {
|
|
76
|
+
return id + '/' + payload;
|
|
77
|
+
}
|
|
@@ -4,6 +4,8 @@ declare global {
|
|
|
4
4
|
const __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: number;
|
|
5
5
|
const __SVELTEKIT_DEV__: boolean;
|
|
6
6
|
const __SVELTEKIT_EMBEDDED__: boolean;
|
|
7
|
+
/** true if corresponding config option is set to true */
|
|
8
|
+
const __SVELTEKIT_EXPERIMENTAL__REMOTE_FUNCTIONS__: boolean;
|
|
7
9
|
/** True if `config.kit.router.resolution === 'client'` */
|
|
8
10
|
const __SVELTEKIT_CLIENT_ROUTING__: boolean;
|
|
9
11
|
/**
|
package/src/types/internal.d.ts
CHANGED
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
Adapter,
|
|
21
21
|
ServerInit,
|
|
22
22
|
ClientInit,
|
|
23
|
-
|
|
23
|
+
Transport,
|
|
24
|
+
HandleValidationError
|
|
24
25
|
} from '@sveltejs/kit';
|
|
25
26
|
import {
|
|
26
27
|
HttpMethod,
|
|
@@ -45,6 +46,7 @@ export interface ServerInternalModule {
|
|
|
45
46
|
set_safe_public_env(environment: Record<string, string>): void;
|
|
46
47
|
set_version(version: string): void;
|
|
47
48
|
set_fix_stack_trace(fix_stack_trace: (error: unknown) => string): void;
|
|
49
|
+
get_hooks: () => Promise<Record<string, any>>;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
export interface Asset {
|
|
@@ -149,15 +151,16 @@ export interface ServerHooks {
|
|
|
149
151
|
handleFetch: HandleFetch;
|
|
150
152
|
handle: Handle;
|
|
151
153
|
handleError: HandleServerError;
|
|
154
|
+
handleValidationError: HandleValidationError;
|
|
152
155
|
reroute: Reroute;
|
|
153
|
-
transport:
|
|
156
|
+
transport: Transport;
|
|
154
157
|
init?: ServerInit;
|
|
155
158
|
}
|
|
156
159
|
|
|
157
160
|
export interface ClientHooks {
|
|
158
161
|
handleError: HandleClientError;
|
|
159
162
|
reroute: Reroute;
|
|
160
|
-
transport:
|
|
163
|
+
transport: Transport;
|
|
161
164
|
init?: ClientInit;
|
|
162
165
|
}
|
|
163
166
|
|
|
@@ -189,6 +192,10 @@ export interface ManifestData {
|
|
|
189
192
|
universal: string | null;
|
|
190
193
|
};
|
|
191
194
|
nodes: PageNode[];
|
|
195
|
+
remotes: Array<{
|
|
196
|
+
file: string;
|
|
197
|
+
hash: string;
|
|
198
|
+
}>;
|
|
192
199
|
routes: RouteData[];
|
|
193
200
|
matchers: Record<string, string>;
|
|
194
201
|
}
|
|
@@ -216,6 +223,11 @@ export interface PrerenderOptions {
|
|
|
216
223
|
cache?: string; // including this here is a bit of a hack, but it makes it easy to add <meta http-equiv>
|
|
217
224
|
fallback?: boolean;
|
|
218
225
|
dependencies: Map<string, PrerenderDependency>;
|
|
226
|
+
/**
|
|
227
|
+
* For each key the (possibly still pending) result of a prerendered remote function.
|
|
228
|
+
* Used to deduplicate requests to the same remote function with the same arguments.
|
|
229
|
+
*/
|
|
230
|
+
remote_responses: Map<string, Promise<any>>;
|
|
219
231
|
/** True for the duration of a call to the `reroute` hook */
|
|
220
232
|
inside_reroute?: boolean;
|
|
221
233
|
}
|
|
@@ -280,7 +292,18 @@ export type ServerNodesResponse = {
|
|
|
280
292
|
nodes: Array<ServerDataNode | ServerDataSkippedNode | ServerErrorNode | null>;
|
|
281
293
|
};
|
|
282
294
|
|
|
283
|
-
export type
|
|
295
|
+
export type RemoteFunctionResponse =
|
|
296
|
+
| (ServerRedirectNode & {
|
|
297
|
+
/** devalue'd Record<string, any> */
|
|
298
|
+
refreshes?: string;
|
|
299
|
+
})
|
|
300
|
+
| ServerErrorNode
|
|
301
|
+
| {
|
|
302
|
+
type: 'result';
|
|
303
|
+
result: string;
|
|
304
|
+
/** devalue'd Record<string, any> */
|
|
305
|
+
refreshes: string;
|
|
306
|
+
};
|
|
284
307
|
|
|
285
308
|
/**
|
|
286
309
|
* Signals a successful response of the server `load` function.
|
|
@@ -347,6 +370,8 @@ export interface ServerMetadata {
|
|
|
347
370
|
has_server_load: boolean;
|
|
348
371
|
}>;
|
|
349
372
|
routes: Map<string, ServerMetadataRoute>;
|
|
373
|
+
/** For each hashed remote file, a map of export name -> { type, dynamic }, where `dynamic` is `false` for non-dynamic prerender functions */
|
|
374
|
+
remotes: Map<string, Map<string, { type: RemoteInfo['type']; dynamic: boolean }>>;
|
|
350
375
|
}
|
|
351
376
|
|
|
352
377
|
export interface SSRComponent {
|
|
@@ -445,6 +470,7 @@ export interface PageNodeIndexes {
|
|
|
445
470
|
}
|
|
446
471
|
|
|
447
472
|
export type PrerenderEntryGenerator = () => MaybePromise<Array<Record<string, string>>>;
|
|
473
|
+
export type RemotePrerenderInputsGenerator<Input = any> = () => MaybePromise<Input[]>;
|
|
448
474
|
|
|
449
475
|
export type SSREndpoint = Partial<Record<HttpMethod, RequestHandler>> & {
|
|
450
476
|
prerender?: PrerenderOption;
|
|
@@ -519,5 +545,26 @@ export type ValidatedKitConfig = Omit<RecursiveRequired<KitConfig>, 'adapter'> &
|
|
|
519
545
|
adapter?: Adapter;
|
|
520
546
|
};
|
|
521
547
|
|
|
548
|
+
export type RemoteInfo =
|
|
549
|
+
| {
|
|
550
|
+
type: 'query' | 'command';
|
|
551
|
+
id: string;
|
|
552
|
+
name: string;
|
|
553
|
+
}
|
|
554
|
+
| {
|
|
555
|
+
type: 'form';
|
|
556
|
+
id: string;
|
|
557
|
+
name: string;
|
|
558
|
+
fn: (data: FormData) => Promise<any>;
|
|
559
|
+
}
|
|
560
|
+
| {
|
|
561
|
+
type: 'prerender';
|
|
562
|
+
id: string;
|
|
563
|
+
name: string;
|
|
564
|
+
has_arg: boolean;
|
|
565
|
+
dynamic?: boolean;
|
|
566
|
+
inputs?: RemotePrerenderInputsGenerator;
|
|
567
|
+
};
|
|
568
|
+
|
|
522
569
|
export * from '../exports/index.js';
|
|
523
570
|
export * from './private.js';
|
package/src/utils/routing.js
CHANGED
package/src/version.js
CHANGED