@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,12 +1,12 @@
|
|
|
1
1
|
/** @import { ActionResult, RemoteForm, RequestEvent, SSRManifest } from '@sveltejs/kit' */
|
|
2
|
-
/** @import { RemoteFormInternals, RemoteFunctionResponse, RemoteInternals, RequestState, SSROptions } from 'types' */
|
|
2
|
+
/** @import { RemoteFormInternals, RemoteFunctionData, RemoteFunctionResponse, RemoteInternals, RequestState, SSROptions } from 'types' */
|
|
3
3
|
|
|
4
4
|
import { json, error } from '@sveltejs/kit';
|
|
5
|
-
import {
|
|
5
|
+
import { Redirect, SvelteKitError } from '@sveltejs/kit/internal';
|
|
6
6
|
import { with_request_store, merge_tracing } from '@sveltejs/kit/internal/server';
|
|
7
7
|
import { app_dir, base } from '$app/paths/internal/server';
|
|
8
8
|
import { is_form_content_type } from '../../utils/http.js';
|
|
9
|
-
import { parse_remote_arg, split_remote_key, stringify } from '../shared.js';
|
|
9
|
+
import { create_remote_key, parse_remote_arg, split_remote_key, stringify } from '../shared.js';
|
|
10
10
|
import { handle_error_and_jsonify } from './utils.js';
|
|
11
11
|
import { normalize_error } from '../../utils/error.js';
|
|
12
12
|
import { check_incorrect_fail_use } from './page/actions.js';
|
|
@@ -14,6 +14,13 @@ import { DEV } from 'esm-env';
|
|
|
14
14
|
import { record_span } from '../telemetry/record_span.js';
|
|
15
15
|
import { deserialize_binary_form } from '../form-utils.js';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* How long (in milliseconds) to wait after the last message was sent before
|
|
19
|
+
* sending a `: keep-alive` SSE comment, to prevent proxies/load balancers with
|
|
20
|
+
* an idle timeout from closing an otherwise-quiet `query.live` connection.
|
|
21
|
+
*/
|
|
22
|
+
const KEEP_ALIVE_INTERVAL = 30_000;
|
|
23
|
+
|
|
17
24
|
/** @type {typeof handle_remote_call_internal} */
|
|
18
25
|
export async function handle_remote_call(event, state, options, manifest, id) {
|
|
19
26
|
return record_span({
|
|
@@ -41,10 +48,10 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
41
48
|
const [hash, name, additional_args] = id.split('/');
|
|
42
49
|
const remotes = manifest._.remotes;
|
|
43
50
|
|
|
44
|
-
if (!remotes
|
|
51
|
+
if (!Object.hasOwn(remotes, hash)) error(404);
|
|
45
52
|
|
|
46
53
|
const module = await remotes[hash]();
|
|
47
|
-
const fn = module.default[name];
|
|
54
|
+
const fn = Object.hasOwn(module.default, name) ? module.default[name] : undefined;
|
|
48
55
|
|
|
49
56
|
if (!fn) error(404);
|
|
50
57
|
|
|
@@ -57,278 +64,400 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
|
|
|
57
64
|
'sveltekit.remote.call.name': internals.name
|
|
58
65
|
});
|
|
59
66
|
|
|
67
|
+
/** @type {HeadersInit | undefined} */
|
|
68
|
+
const headers = state.prerendering ? undefined : { 'cache-control': 'private, no-store' };
|
|
69
|
+
|
|
60
70
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
/** @type {RemoteFunctionData} */
|
|
72
|
+
const data = {};
|
|
73
|
+
|
|
74
|
+
switch (internals.type) {
|
|
75
|
+
case 'query_live': {
|
|
76
|
+
if (event.request.method !== 'GET') {
|
|
77
|
+
throw new SvelteKitError(
|
|
78
|
+
405,
|
|
79
|
+
'Method Not Allowed',
|
|
80
|
+
`\`query.live\` functions must be invoked via GET request, not ${event.request.method}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const payload = /** @type {string} */ (
|
|
85
|
+
new URL(event.request.url).searchParams.get('payload')
|
|
67
86
|
);
|
|
68
|
-
}
|
|
69
87
|
|
|
70
|
-
|
|
71
|
-
|
|
88
|
+
const generator = internals.run(event, state, parse_remote_arg(payload, transport));
|
|
89
|
+
|
|
90
|
+
const encoder = new TextEncoder();
|
|
91
|
+
|
|
92
|
+
let closed = false;
|
|
93
|
+
|
|
94
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
95
|
+
let keep_alive;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* (Re)schedule the keep-alive comment. Called whenever a message is sent, so
|
|
99
|
+
* that a keep-alive is only emitted once `KEEP_ALIVE_INTERVAL` has elapsed
|
|
100
|
+
* without any other activity.
|
|
101
|
+
* @param {ReadableStreamDefaultController} controller
|
|
102
|
+
*/
|
|
103
|
+
function schedule_keep_alive(controller) {
|
|
104
|
+
clearTimeout(keep_alive);
|
|
105
|
+
keep_alive = setTimeout(() => {
|
|
106
|
+
if (closed || event.request.signal.aborted) return;
|
|
107
|
+
// SSE comments (lines starting with `:`) are ignored by the client
|
|
108
|
+
controller.enqueue(encoder.encode(': keep-alive\n\n'));
|
|
109
|
+
schedule_keep_alive(controller);
|
|
110
|
+
}, KEEP_ALIVE_INTERVAL);
|
|
111
|
+
}
|
|
72
112
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
113
|
+
/**
|
|
114
|
+
* @param {ReadableStreamDefaultController} controller
|
|
115
|
+
* @param {any} payload
|
|
116
|
+
*/
|
|
117
|
+
function send(controller, payload) {
|
|
118
|
+
controller.enqueue(encoder.encode('data: ' + JSON.stringify(payload) + '\n\n'));
|
|
119
|
+
schedule_keep_alive(controller);
|
|
120
|
+
}
|
|
76
121
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
122
|
+
/** @type {string | undefined} */
|
|
123
|
+
let result = undefined;
|
|
80
124
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
125
|
+
async function cancel() {
|
|
126
|
+
if (closed) return;
|
|
127
|
+
closed = true;
|
|
128
|
+
clearTimeout(keep_alive);
|
|
129
|
+
await generator.return(undefined);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
event.request.signal.addEventListener('abort', cancel, { once: true });
|
|
133
|
+
|
|
134
|
+
return new Response(
|
|
135
|
+
new ReadableStream({
|
|
136
|
+
start(controller) {
|
|
137
|
+
schedule_keep_alive(controller);
|
|
138
|
+
},
|
|
139
|
+
async pull(controller) {
|
|
140
|
+
if (event.request.signal.aborted) {
|
|
141
|
+
await cancel();
|
|
142
|
+
controller.close();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
while (true) {
|
|
148
|
+
const { value, done } = await generator.next();
|
|
149
|
+
|
|
150
|
+
if (done) {
|
|
151
|
+
await cancel();
|
|
152
|
+
controller.close();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// only send changed data
|
|
157
|
+
if (result !== (result = stringify(value, transport))) {
|
|
158
|
+
send(controller, {
|
|
159
|
+
type: 'result',
|
|
160
|
+
result
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (!event.request.signal.aborted) {
|
|
168
|
+
if (error instanceof Redirect) {
|
|
169
|
+
send(controller, {
|
|
170
|
+
type: 'redirect',
|
|
171
|
+
location: error.location
|
|
172
|
+
});
|
|
173
|
+
} else {
|
|
174
|
+
const transformed = await handle_error_and_jsonify(
|
|
175
|
+
event,
|
|
176
|
+
state,
|
|
177
|
+
options,
|
|
178
|
+
error
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
send(controller, {
|
|
182
|
+
type: 'error',
|
|
183
|
+
error: transformed
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
88
187
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
188
|
+
await cancel();
|
|
189
|
+
controller.close();
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
cancel
|
|
193
|
+
}),
|
|
194
|
+
{
|
|
195
|
+
headers: {
|
|
196
|
+
'cache-control': 'private, no-store',
|
|
197
|
+
'content-type': 'text/event-stream'
|
|
198
|
+
}
|
|
199
|
+
}
|
|
95
200
|
);
|
|
96
201
|
}
|
|
97
202
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
)
|
|
203
|
+
case 'query_batch': {
|
|
204
|
+
if (event.request.method !== 'POST') {
|
|
205
|
+
throw new SvelteKitError(
|
|
206
|
+
405,
|
|
207
|
+
'Method Not Allowed',
|
|
208
|
+
`\`query.batch\` functions must be invoked via POST request, not ${event.request.method}`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** @type {{ payloads: string[] }} */
|
|
213
|
+
const { payloads } = await event.request.json();
|
|
214
|
+
|
|
215
|
+
const args = await Promise.all(
|
|
216
|
+
payloads.map((payload) => parse_remote_arg(payload, transport))
|
|
105
217
|
);
|
|
106
|
-
}
|
|
107
218
|
|
|
108
|
-
|
|
109
|
-
state.remote.requested = create_requested_map(meta.remote_refreshes);
|
|
219
|
+
data._ = await with_request_store({ event, state }, () => internals.run(args, options));
|
|
110
220
|
|
|
111
|
-
|
|
112
|
-
// Note that additional_args will only be set if the form is not enhanced, as enhanced forms transfer the key inside `data`.
|
|
113
|
-
if (additional_args && !('id' in data)) {
|
|
114
|
-
data.id = JSON.parse(decodeURIComponent(additional_args));
|
|
221
|
+
break;
|
|
115
222
|
}
|
|
116
223
|
|
|
117
|
-
|
|
118
|
-
|
|
224
|
+
case 'form': {
|
|
225
|
+
if (event.request.method !== 'POST') {
|
|
226
|
+
throw new SvelteKitError(
|
|
227
|
+
405,
|
|
228
|
+
'Method Not Allowed',
|
|
229
|
+
`\`form\` functions must be invoked via POST request, not ${event.request.method}`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
119
232
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
: await serialize_singleflight(state.remote.reconnects)
|
|
130
|
-
})
|
|
131
|
-
);
|
|
132
|
-
}
|
|
233
|
+
if (!is_form_content_type(event.request)) {
|
|
234
|
+
throw new SvelteKitError(
|
|
235
|
+
415,
|
|
236
|
+
'Unsupported Media Type',
|
|
237
|
+
`\`form\` functions expect form-encoded data — received ${event.request.headers.get(
|
|
238
|
+
'content-type'
|
|
239
|
+
)}`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
133
242
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const { payload, refreshes } = await event.request.json();
|
|
137
|
-
state.remote.requested = create_requested_map(refreshes);
|
|
138
|
-
const arg = parse_remote_arg(payload, transport);
|
|
139
|
-
const data = await with_request_store({ event, state }, () => fn(arg));
|
|
243
|
+
const { data: input, meta, form_data } = await deserialize_binary_form(event.request);
|
|
244
|
+
state.remote.requested = create_requested_map(meta.remote_refreshes);
|
|
140
245
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
reconnects: await serialize_singleflight(state.remote.reconnects)
|
|
147
|
-
})
|
|
148
|
-
);
|
|
149
|
-
}
|
|
246
|
+
// If this is a keyed form instance (created via form.for(key)), add the key to the form data (unless already set)
|
|
247
|
+
// Note that additional_args will only be set if the form is not enhanced, as enhanced forms transfer the key inside `data`.
|
|
248
|
+
if (additional_args && !('id' in input)) {
|
|
249
|
+
input.id = JSON.parse(decodeURIComponent(additional_args));
|
|
250
|
+
}
|
|
150
251
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
'Method Not Allowed',
|
|
156
|
-
`\`query.live\` functions must be invoked via GET request, not ${event.request.method}`
|
|
252
|
+
const fn = internals.fn;
|
|
253
|
+
data._ = await with_request_store(
|
|
254
|
+
{ event, state: { ...state, is_in_remote_form_or_command: true } },
|
|
255
|
+
() => fn(input, meta, form_data)
|
|
157
256
|
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const payload = /** @type {string} */ (
|
|
161
|
-
new URL(event.request.url).searchParams.get('payload')
|
|
162
|
-
);
|
|
163
257
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
258
|
+
if (data._.issues) {
|
|
259
|
+
// special case — don't serialize refreshes/reconnects
|
|
260
|
+
return json(
|
|
261
|
+
/** @type {RemoteFunctionResponse} */ ({
|
|
262
|
+
type: 'result',
|
|
263
|
+
data: stringify(data, transport)
|
|
264
|
+
}),
|
|
265
|
+
{ headers }
|
|
266
|
+
);
|
|
267
|
+
}
|
|
167
268
|
|
|
168
|
-
|
|
169
|
-
* @param {ReadableStreamDefaultController} controller
|
|
170
|
-
* @param {any} payload
|
|
171
|
-
*/
|
|
172
|
-
function send(controller, payload) {
|
|
173
|
-
controller.enqueue(encoder.encode(JSON.stringify(payload) + '\n'));
|
|
269
|
+
break;
|
|
174
270
|
}
|
|
175
271
|
|
|
176
|
-
|
|
272
|
+
case 'command': {
|
|
273
|
+
/** @type {{ payload: string, refreshes?: string[] }} */
|
|
274
|
+
const { payload, refreshes } = await event.request.json();
|
|
275
|
+
state.remote.requested = create_requested_map(refreshes);
|
|
276
|
+
const arg = parse_remote_arg(payload, transport);
|
|
177
277
|
|
|
178
|
-
|
|
179
|
-
|
|
278
|
+
data._ = await with_request_store(
|
|
279
|
+
{ event, state: { ...state, is_in_remote_form_or_command: true } },
|
|
280
|
+
() => fn(arg)
|
|
281
|
+
);
|
|
180
282
|
|
|
181
|
-
|
|
182
|
-
if (closed) return;
|
|
183
|
-
closed = true;
|
|
184
|
-
await generator.return(undefined);
|
|
283
|
+
break;
|
|
185
284
|
}
|
|
186
285
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
async pull(controller) {
|
|
192
|
-
if (event.request.signal.aborted) {
|
|
193
|
-
await cancel();
|
|
194
|
-
controller.close();
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
while (true) {
|
|
200
|
-
const { value, done } = await generator.next();
|
|
286
|
+
case 'prerender': {
|
|
287
|
+
data._ = await with_request_store({ event, state }, () =>
|
|
288
|
+
fn(parse_remote_arg(additional_args, transport))
|
|
289
|
+
);
|
|
201
290
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
controller.close();
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
207
293
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
});
|
|
294
|
+
case 'query': {
|
|
295
|
+
const payload = /** @type {string} */ (
|
|
296
|
+
// new URL(...) necessary because we're hiding the URL from the user in the event object
|
|
297
|
+
new URL(event.request.url).searchParams.get('payload')
|
|
298
|
+
);
|
|
214
299
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
} catch (error) {
|
|
219
|
-
if (!event.request.signal.aborted) {
|
|
220
|
-
if (error instanceof Redirect) {
|
|
221
|
-
send(controller, {
|
|
222
|
-
type: 'redirect',
|
|
223
|
-
location: error.location
|
|
224
|
-
});
|
|
225
|
-
} else {
|
|
226
|
-
const status =
|
|
227
|
-
error instanceof HttpError || error instanceof SvelteKitError
|
|
228
|
-
? error.status
|
|
229
|
-
: 500;
|
|
230
|
-
|
|
231
|
-
send(controller, {
|
|
232
|
-
type: 'error',
|
|
233
|
-
error: await handle_error_and_jsonify(event, state, options, error),
|
|
234
|
-
status
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
}
|
|
300
|
+
data._ = await with_request_store({ event, state }, () =>
|
|
301
|
+
fn(parse_remote_arg(payload, transport))
|
|
302
|
+
);
|
|
238
303
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
cancel
|
|
244
|
-
}),
|
|
245
|
-
{
|
|
246
|
-
headers: {
|
|
247
|
-
'cache-control': 'private, no-store',
|
|
248
|
-
'content-type': 'application/x-ndjson'
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
);
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
252
306
|
}
|
|
253
307
|
|
|
254
|
-
|
|
255
|
-
internals.type === 'prerender'
|
|
256
|
-
? additional_args
|
|
257
|
-
: /** @type {string} */ (
|
|
258
|
-
// new URL(...) necessary because we're hiding the URL from the user in the event object
|
|
259
|
-
new URL(event.request.url).searchParams.get('payload')
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
const data = await with_request_store({ event, state }, () =>
|
|
263
|
-
fn(parse_remote_arg(payload, transport))
|
|
264
|
-
);
|
|
308
|
+
await collect_remote_data(data, event, state, options);
|
|
265
309
|
|
|
266
310
|
return json(
|
|
267
311
|
/** @type {RemoteFunctionResponse} */ ({
|
|
268
312
|
type: 'result',
|
|
269
|
-
|
|
270
|
-
})
|
|
313
|
+
data: stringify(data, transport)
|
|
314
|
+
}),
|
|
315
|
+
{ headers }
|
|
271
316
|
);
|
|
272
317
|
} catch (error) {
|
|
273
318
|
if (error instanceof Redirect) {
|
|
319
|
+
const data = await collect_remote_data({ redirect: error.location }, event, state, options);
|
|
320
|
+
|
|
274
321
|
return json(
|
|
275
322
|
/** @type {RemoteFunctionResponse} */ ({
|
|
276
|
-
type: '
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
})
|
|
323
|
+
type: 'result',
|
|
324
|
+
data: stringify(data, transport)
|
|
325
|
+
}),
|
|
326
|
+
{ headers }
|
|
281
327
|
);
|
|
282
328
|
}
|
|
283
329
|
|
|
284
|
-
const
|
|
285
|
-
error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500;
|
|
330
|
+
const transformed = await handle_error_and_jsonify(event, state, options, error);
|
|
286
331
|
|
|
287
332
|
return json(
|
|
288
333
|
/** @type {RemoteFunctionResponse} */ ({
|
|
289
334
|
type: 'error',
|
|
290
|
-
error:
|
|
291
|
-
status
|
|
335
|
+
error: transformed
|
|
292
336
|
}),
|
|
293
337
|
{
|
|
294
338
|
// By setting a non-200 during prerendering we fail the prerender process (unless handleHttpError handles it).
|
|
295
339
|
// Errors at runtime will be passed to the client and are handled there
|
|
296
|
-
status: state.prerendering ? status : undefined,
|
|
340
|
+
status: state.prerendering ? transformed.status : undefined,
|
|
297
341
|
headers: {
|
|
298
342
|
'cache-control': 'private, no-store'
|
|
299
343
|
}
|
|
300
344
|
}
|
|
301
345
|
);
|
|
302
346
|
}
|
|
347
|
+
}
|
|
303
348
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Collects all the query/prerender data that was retrieved
|
|
351
|
+
* during the request and adds it to `data`
|
|
352
|
+
* @param {RemoteFunctionData} data
|
|
353
|
+
* @param {RequestEvent} event
|
|
354
|
+
* @param {RequestState} state
|
|
355
|
+
* @param {SSROptions} options
|
|
356
|
+
*/
|
|
357
|
+
export async function collect_remote_data(data, event, state, options) {
|
|
358
|
+
/**
|
|
359
|
+
*
|
|
360
|
+
* @param {unknown} error
|
|
361
|
+
* @returns {Promise<App.Error>}
|
|
362
|
+
*/
|
|
363
|
+
function convert_error(error) {
|
|
364
|
+
// TODO 4.0 remove the `Promise.resolve(...)`
|
|
365
|
+
return Promise.resolve(handle_error_and_jsonify(event, state, options, error));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** @type {Promise<any>[]} */
|
|
369
|
+
const promises = [];
|
|
370
|
+
|
|
371
|
+
if (state.remote.explicit) {
|
|
372
|
+
for (const [remote_key, { internals, fn }] of state.remote.explicit) {
|
|
373
|
+
// there were explicit refreshes/reconnects (via `refresh()`/`set()`/`reconnect()`),
|
|
374
|
+
// so the client should apply these single-flight updates instead of calling `invalidateAll()`
|
|
375
|
+
data.r = true;
|
|
309
376
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
377
|
+
const type = /** @type {'p' | 'q' | 'l'} */ (
|
|
378
|
+
internals.type === 'query_live' ? 'l' : internals.type[0]
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
// `fn` is deferred until now so the query runs after any state mutations
|
|
382
|
+
// in the command/form body. If the query was re-awaited in the meantime,
|
|
383
|
+
// `fn` returns the existing (fresh) cache entry rather than re-running.
|
|
384
|
+
// Kick off the query immediately and collect the promise so that multiple
|
|
385
|
+
// explicit refreshes run concurrently rather than serially.
|
|
386
|
+
const promise = fn();
|
|
387
|
+
|
|
388
|
+
promises.push(
|
|
389
|
+
promise.then(
|
|
390
|
+
(v) => {
|
|
391
|
+
((data[type] ??= {})[remote_key] ??= {}).v = v;
|
|
392
|
+
},
|
|
393
|
+
async (e) => {
|
|
394
|
+
if (e instanceof Redirect) {
|
|
395
|
+
// already handled elsewhere
|
|
396
|
+
return;
|
|
324
397
|
}
|
|
325
|
-
];
|
|
326
|
-
}
|
|
327
|
-
})
|
|
328
|
-
);
|
|
329
398
|
|
|
330
|
-
|
|
399
|
+
((data[type] ??= {})[remote_key] ??= {}).e = await convert_error(e);
|
|
400
|
+
}
|
|
401
|
+
)
|
|
402
|
+
);
|
|
403
|
+
}
|
|
331
404
|
}
|
|
405
|
+
|
|
406
|
+
await Promise.all(promises);
|
|
407
|
+
|
|
408
|
+
if (state.remote.implicit) {
|
|
409
|
+
for (const [internals, record] of state.remote.implicit) {
|
|
410
|
+
// Private (non-exported) remote functions have no `id` and must never be
|
|
411
|
+
// serialized into the response — otherwise their (potentially private) result
|
|
412
|
+
// would be shipped to the client under a malformed `undefined/...` key.
|
|
413
|
+
if (!internals.id) continue;
|
|
414
|
+
|
|
415
|
+
for (const key in record) {
|
|
416
|
+
// form outputs are registered under the client-side action id directly
|
|
417
|
+
const remote_key = internals.type === 'form' ? key : create_remote_key(internals.id, key);
|
|
418
|
+
|
|
419
|
+
const type = /** @type {'p' | 'q' | 'l' | 'f'} */ (
|
|
420
|
+
internals.type === 'query_live' ? 'l' : internals.type[0]
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
const promise = state.remote.data?.get(internals)?.[key] ?? record[key]();
|
|
424
|
+
|
|
425
|
+
// If the promise is still pending (e.g. the query was rendered in its loading
|
|
426
|
+
// state during SSR), omit it from the payload entirely so that the client
|
|
427
|
+
// fetches it itself — an entry without `v`/`e` would hydrate as `undefined`.
|
|
428
|
+
let resolved = true;
|
|
429
|
+
|
|
430
|
+
await Promise.race([
|
|
431
|
+
Promise.resolve(promise).then(
|
|
432
|
+
(v) => {
|
|
433
|
+
if (resolved) {
|
|
434
|
+
((data[type] ??= {})[remote_key] ??= {}).v = v;
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
(e) => {
|
|
438
|
+
if (e instanceof Redirect) {
|
|
439
|
+
// already handled elsewhere
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (resolved) {
|
|
444
|
+
promises.push(
|
|
445
|
+
convert_error(e).then((e) => {
|
|
446
|
+
((data[type] ??= {})[remote_key] ??= {}).e = e;
|
|
447
|
+
})
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
),
|
|
452
|
+
Promise.resolve().then(() => (resolved = false))
|
|
453
|
+
]);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
await Promise.all(promises);
|
|
459
|
+
|
|
460
|
+
return data;
|
|
332
461
|
}
|
|
333
462
|
|
|
334
463
|
/**
|
|
@@ -377,11 +506,16 @@ export async function handle_remote_form_post(event, state, manifest, id) {
|
|
|
377
506
|
* @returns {Promise<ActionResult>}
|
|
378
507
|
*/
|
|
379
508
|
async function handle_remote_form_post_internal(event, state, manifest, id) {
|
|
380
|
-
|
|
509
|
+
// `hash` and `name` can never contain a `/`, but the JSON-stringified key of a
|
|
510
|
+
// keyed (`form.for(key)`) instance can — rejoin the remaining segments
|
|
511
|
+
const [hash, name, ...rest] = id.split('/');
|
|
512
|
+
const action_id = rest.join('/');
|
|
381
513
|
const remotes = manifest._.remotes;
|
|
382
|
-
const module = await remotes[hash]
|
|
514
|
+
const module = Object.hasOwn(remotes, hash) ? await remotes[hash]() : undefined;
|
|
383
515
|
|
|
384
|
-
let form = /** @type {RemoteForm<any, any>} */ (
|
|
516
|
+
let form = /** @type {RemoteForm<any, any>} */ (
|
|
517
|
+
module && Object.hasOwn(module.default, name) ? module.default[name] : undefined
|
|
518
|
+
);
|
|
385
519
|
|
|
386
520
|
if (!form) {
|
|
387
521
|
event.setHeaders({
|
|
@@ -391,6 +525,7 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
|
|
|
391
525
|
});
|
|
392
526
|
return {
|
|
393
527
|
type: 'error',
|
|
528
|
+
// We're lying a bit with the types here; this will be transformed into a proper App.Error object later
|
|
394
529
|
error: new SvelteKitError(
|
|
395
530
|
405,
|
|
396
531
|
'Method Not Allowed',
|
|
@@ -413,7 +548,10 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
|
|
|
413
548
|
data.id = JSON.parse(decodeURIComponent(action_id));
|
|
414
549
|
}
|
|
415
550
|
|
|
416
|
-
await with_request_store(
|
|
551
|
+
await with_request_store(
|
|
552
|
+
{ event, state: { ...state, is_in_remote_form_or_command: true } },
|
|
553
|
+
() => fn(data, meta, form_data)
|
|
554
|
+
);
|
|
417
555
|
|
|
418
556
|
// We don't want the data to appear on `let { form } = $props()`, which is why we're not returning it.
|
|
419
557
|
// It is instead available on `myForm.result`, setting of which happens within the remote `form` function.
|
|
@@ -434,6 +572,7 @@ async function handle_remote_form_post_internal(event, state, manifest, id) {
|
|
|
434
572
|
|
|
435
573
|
return {
|
|
436
574
|
type: 'error',
|
|
575
|
+
// @ts-expect-error We're lying a bit with the types here; this will be transformed into a proper App.Error object later
|
|
437
576
|
error: check_incorrect_fail_use(err)
|
|
438
577
|
};
|
|
439
578
|
}
|