@sveltejs/kit 3.0.0-next.6 → 3.0.0-next.8
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 +8 -3
- package/src/core/adapt/builder.js +11 -39
- package/src/core/config/index.js +76 -71
- package/src/core/config/options.js +280 -285
- package/src/core/config/types.d.ts +1 -1
- package/src/core/env.js +86 -1
- package/src/core/generate_manifest/index.js +12 -15
- package/src/core/sync/create_manifest_data/index.js +7 -45
- package/src/core/sync/sync.js +0 -2
- package/src/core/sync/write_client_manifest.js +7 -21
- package/src/core/sync/write_non_ambient.js +12 -9
- package/src/core/sync/write_server.js +0 -4
- package/src/core/sync/write_types/index.js +28 -24
- package/src/core/utils.js +2 -2
- package/src/exports/index.js +3 -3
- package/src/exports/internal/client.js +5 -0
- package/src/exports/internal/index.js +1 -91
- 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 +5 -9
- package/src/exports/params.js +63 -0
- package/src/exports/public.d.ts +103 -46
- package/src/exports/url.js +84 -0
- package/src/exports/vite/dev/index.js +31 -20
- package/src/exports/vite/index.js +280 -199
- package/src/exports/vite/preview/index.js +14 -15
- package/src/exports/vite/utils.js +8 -10
- package/src/runtime/app/env/internal.js +4 -4
- package/src/runtime/app/forms.js +2 -2
- package/src/runtime/app/paths/client.js +3 -7
- 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/server.js +3 -3
- package/src/runtime/app/server/remote/query.js +6 -12
- 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 +230 -238
- package/src/runtime/client/entry.js +24 -3
- 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/form.svelte.js +11 -30
- package/src/runtime/client/remote-functions/prerender.svelte.js +10 -3
- package/src/runtime/client/remote-functions/query/instance.svelte.js +18 -9
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +16 -6
- package/src/runtime/client/remote-functions/shared.svelte.js +1 -2
- package/src/runtime/client/state.svelte.js +66 -49
- package/src/runtime/client/types.d.ts +3 -7
- package/src/runtime/client/utils.js +0 -96
- package/src/runtime/components/root.svelte +66 -0
- package/src/runtime/form-utils.js +16 -6
- package/src/runtime/server/cookie.js +17 -7
- package/src/runtime/server/index.js +1 -1
- package/src/runtime/server/page/index.js +7 -14
- package/src/runtime/server/page/load_data.js +1 -1
- package/src/runtime/server/page/render.js +72 -88
- package/src/runtime/server/page/server_routing.js +13 -9
- package/src/runtime/server/remote.js +23 -13
- package/src/runtime/server/respond.js +11 -8
- package/src/runtime/server/utils.js +28 -5
- package/src/runtime/telemetry/otel.js +1 -1
- package/src/runtime/types.d.ts +8 -0
- package/src/types/ambient.d.ts +5 -1
- package/src/types/global-private.d.ts +11 -18
- package/src/types/internal.d.ts +25 -30
- package/src/utils/error.js +1 -1
- package/src/utils/import.js +6 -1
- package/src/utils/mime.js +9 -0
- package/src/utils/params.js +66 -0
- package/src/utils/routing.js +90 -44
- package/src/utils/streaming.js +14 -4
- package/src/utils/url.js +0 -79
- package/src/version.js +1 -1
- package/types/index.d.ts +127 -125
- package/types/index.d.ts.map +12 -8
- package/src/core/sync/write_root.js +0 -148
- package/src/exports/internal/server.js +0 -22
- /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
|
@@ -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
|
|
@@ -146,10 +150,7 @@ export async function deserialize_binary_form(request) {
|
|
|
146
150
|
throw deserialize_error('no body');
|
|
147
151
|
}
|
|
148
152
|
|
|
149
|
-
|
|
150
|
-
const reader = /** @type {ReadableStreamDefaultReader<Uint8Array<ArrayBuffer>>} */ (
|
|
151
|
-
request.body.getReader()
|
|
152
|
-
);
|
|
153
|
+
const reader = request.body.getReader();
|
|
153
154
|
|
|
154
155
|
/** @type {Array<Promise<Uint8Array<ArrayBuffer> | undefined>>} */
|
|
155
156
|
const chunks = [];
|
|
@@ -320,7 +321,7 @@ export async function deserialize_binary_form(request) {
|
|
|
320
321
|
const chunk = await get_chunk(chunks.length);
|
|
321
322
|
has_more = !!chunk;
|
|
322
323
|
}
|
|
323
|
-
})();
|
|
324
|
+
})().catch(noop); // prevent unhandled rejection potentially crashing the process
|
|
324
325
|
|
|
325
326
|
return { data, meta, form_data: null };
|
|
326
327
|
}
|
|
@@ -504,6 +505,10 @@ export function deep_set(object, keys, value) {
|
|
|
504
505
|
}
|
|
505
506
|
|
|
506
507
|
if (!exists) {
|
|
508
|
+
if (value === DELETE_KEY) {
|
|
509
|
+
// don't create the nested structure if we want to delete the key anyway
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
507
512
|
current[key] = is_array ? [] : {};
|
|
508
513
|
}
|
|
509
514
|
|
|
@@ -512,7 +517,12 @@ export function deep_set(object, keys, value) {
|
|
|
512
517
|
|
|
513
518
|
const final_key = keys[keys.length - 1];
|
|
514
519
|
check_prototype_pollution(final_key);
|
|
515
|
-
|
|
520
|
+
|
|
521
|
+
if (value === DELETE_KEY) {
|
|
522
|
+
delete current[final_key];
|
|
523
|
+
} else {
|
|
524
|
+
current[final_key] = value;
|
|
525
|
+
}
|
|
516
526
|
}
|
|
517
527
|
|
|
518
528
|
/**
|
|
@@ -12,10 +12,11 @@ import { text_encoder } from '../utils.js';
|
|
|
12
12
|
const cookie_paths = {};
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Cookies
|
|
16
|
-
*
|
|
15
|
+
* Cookies whose name and value combined are larger than this size are
|
|
16
|
+
* discarded by browsers. This is the limit codified for the name/value pair
|
|
17
|
+
* in RFC 6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20#section-5.6-7.5.1
|
|
17
18
|
*/
|
|
18
|
-
const MAX_COOKIE_SIZE =
|
|
19
|
+
const MAX_COOKIE_SIZE = 4096;
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Generates a unique key for a cookie based on its domain, path, and name in
|
|
@@ -124,7 +125,13 @@ export function get_cookies(request, url) {
|
|
|
124
125
|
|
|
125
126
|
// Add the most specific cookies to the result
|
|
126
127
|
for (const c of lookup.values()) {
|
|
127
|
-
cookies
|
|
128
|
+
// tombstones (deleted cookies) shadow request-header cookies,
|
|
129
|
+
// mirroring the behavior of `get()`
|
|
130
|
+
if (c.options.maxAge === 0) {
|
|
131
|
+
delete cookies[c.name];
|
|
132
|
+
} else {
|
|
133
|
+
cookies[c.name] = c.value;
|
|
134
|
+
}
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
return /** @type {Array<{ name: string; value: string }>} */ (
|
|
@@ -219,9 +226,12 @@ export function get_cookies(request, url) {
|
|
|
219
226
|
new_cookies.set(cookie_key, cookie);
|
|
220
227
|
|
|
221
228
|
if (DEV) {
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
229
|
+
const size =
|
|
230
|
+
// only the name/value pair counts towards MAX_COOKIE_SIZE, not the other attributes
|
|
231
|
+
text_encoder.encode(name).byteLength +
|
|
232
|
+
text_encoder.encode((options.encode ?? encodeURIComponent)(value)).byteLength;
|
|
233
|
+
|
|
234
|
+
if (size > MAX_COOKIE_SIZE) {
|
|
225
235
|
throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
|
|
226
236
|
}
|
|
227
237
|
|
|
@@ -27,7 +27,7 @@ export class Server {
|
|
|
27
27
|
this.#manifest = manifest;
|
|
28
28
|
|
|
29
29
|
// Since AsyncLocalStorage is not working in webcontainers, we don't reset `sync_store`
|
|
30
|
-
// in `src/exports/internal/event.js` and handle only one request at a time.
|
|
30
|
+
// in `src/exports/internal/server/event.js` and handle only one request at a time.
|
|
31
31
|
if (IN_WEBCONTAINER) {
|
|
32
32
|
const respond = this.respond.bind(this);
|
|
33
33
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
/** @import { Component } from 'svelte' */
|
|
1
2
|
/** @import { ActionResult, RequestEvent, SSRManifest } from '@sveltejs/kit' */
|
|
2
|
-
/** @import { PageNodeIndexes, RequestState, RequiredResolveOptions, ServerDataNode,
|
|
3
|
+
/** @import { PageNodeIndexes, RequestState, RequiredResolveOptions, ServerDataNode, SSRNode, SSROptions, SSRState } from 'types' */
|
|
3
4
|
import { text } from '@sveltejs/kit';
|
|
4
5
|
import { HttpError, Redirect } from '@sveltejs/kit/internal';
|
|
5
6
|
import { compact } from '../../../utils/array.js';
|
|
@@ -316,13 +317,7 @@ export async function render_page(
|
|
|
316
317
|
},
|
|
317
318
|
status,
|
|
318
319
|
error,
|
|
319
|
-
error_components: await load_error_components(
|
|
320
|
-
options,
|
|
321
|
-
ssr,
|
|
322
|
-
error_branch,
|
|
323
|
-
page,
|
|
324
|
-
manifest
|
|
325
|
-
),
|
|
320
|
+
error_components: await load_error_components(ssr, error_branch, page, manifest),
|
|
326
321
|
branch: error_branch,
|
|
327
322
|
fetched,
|
|
328
323
|
data_serializer
|
|
@@ -374,7 +369,7 @@ export async function render_page(
|
|
|
374
369
|
action_result,
|
|
375
370
|
fetched,
|
|
376
371
|
data_serializer: !ssr ? server_data_serializer(event, event_state, options) : data_serializer,
|
|
377
|
-
error_components: await load_error_components(
|
|
372
|
+
error_components: await load_error_components(ssr, branch, page, manifest)
|
|
378
373
|
});
|
|
379
374
|
} catch (e) {
|
|
380
375
|
// a remote function could have thrown a redirect during render
|
|
@@ -398,18 +393,16 @@ export async function render_page(
|
|
|
398
393
|
}
|
|
399
394
|
|
|
400
395
|
/**
|
|
401
|
-
*
|
|
402
|
-
* @param {SSROptions} options
|
|
403
396
|
* @param {boolean} ssr
|
|
404
397
|
* @param {Array<import('./types.js').Loaded | null>} branch
|
|
405
398
|
* @param {PageNodeIndexes} page
|
|
406
399
|
* @param {SSRManifest} manifest
|
|
407
400
|
*/
|
|
408
|
-
async function load_error_components(
|
|
409
|
-
/** @type {Array<
|
|
401
|
+
async function load_error_components(ssr, branch, page, manifest) {
|
|
402
|
+
/** @type {Array<Component | undefined> | undefined} */
|
|
410
403
|
let error_components;
|
|
411
404
|
|
|
412
|
-
if (
|
|
405
|
+
if (ssr) {
|
|
413
406
|
let last_idx = -1;
|
|
414
407
|
error_components = await Promise.all(
|
|
415
408
|
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
@@ -377,7 +377,7 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
void push_fetched(base64_encode(result), true);
|
|
380
|
-
})();
|
|
380
|
+
})().catch(noop); // prevent unhandled rejection potentially crashing the process
|
|
381
381
|
|
|
382
382
|
return (teed_body = b);
|
|
383
383
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
/** @import { RenderNode } from '../../types.js' */
|
|
1
2
|
import * as devalue from 'devalue';
|
|
2
|
-
import { readable, writable } from 'svelte/store';
|
|
3
3
|
import { DEV } from 'esm-env';
|
|
4
4
|
import { isRedirect, text } from '@sveltejs/kit';
|
|
5
5
|
import * as paths from '$app/paths/internal/server';
|
|
@@ -22,14 +22,11 @@ import {
|
|
|
22
22
|
} from '../utils.js';
|
|
23
23
|
import * as env from '__sveltekit/env';
|
|
24
24
|
import { collect_remote_data } from '../remote.js';
|
|
25
|
+
import Root from '../../components/root.svelte';
|
|
26
|
+
import { render } from 'svelte/server';
|
|
25
27
|
|
|
26
28
|
// TODO rename this function/module
|
|
27
29
|
|
|
28
|
-
const updated = {
|
|
29
|
-
...readable(false),
|
|
30
|
-
check: () => false
|
|
31
|
-
};
|
|
32
|
-
|
|
33
30
|
/**
|
|
34
31
|
* Creates the HTML response.
|
|
35
32
|
* @param {{
|
|
@@ -46,7 +43,7 @@ const updated = {
|
|
|
46
43
|
* resolve_opts: import('types').RequiredResolveOptions;
|
|
47
44
|
* action_result?: import('@sveltejs/kit').ActionResult;
|
|
48
45
|
* data_serializer: import('./types.js').ServerDataSerializer;
|
|
49
|
-
* error_components?: Array<import('
|
|
46
|
+
* error_components?: Array<import('svelte').Component | undefined>
|
|
50
47
|
* }} opts
|
|
51
48
|
*/
|
|
52
49
|
export async function render_response({
|
|
@@ -91,7 +88,8 @@ export async function render_response({
|
|
|
91
88
|
// TODO if we add a client entry point one day, we will need to include inline_styles with the entry, otherwise stylesheets will be linked even if they are below inlineStyleThreshold
|
|
92
89
|
const inline_styles = new Map();
|
|
93
90
|
|
|
94
|
-
|
|
91
|
+
// TODO `svelte/server` should expose `RenderOutput`
|
|
92
|
+
/** @type {{ head: string, body: string, hashes: { script: string[] } }} */
|
|
95
93
|
let rendered;
|
|
96
94
|
|
|
97
95
|
const form_value =
|
|
@@ -142,49 +140,47 @@ export async function render_response({
|
|
|
142
140
|
if (page_config.ssr) {
|
|
143
141
|
/** @type {Record<string, any>} */
|
|
144
142
|
const props = {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
form: form_value
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
if (error_components) {
|
|
163
|
-
if (error) {
|
|
164
|
-
props.error = error;
|
|
143
|
+
components: [],
|
|
144
|
+
resetters: [],
|
|
145
|
+
form: form_value,
|
|
146
|
+
tree: /** @type {RenderNode} */ ({}),
|
|
147
|
+
error,
|
|
148
|
+
page: {
|
|
149
|
+
error,
|
|
150
|
+
params: /** @type {Record<string, any>} */ (event.params),
|
|
151
|
+
route: event.route,
|
|
152
|
+
status,
|
|
153
|
+
url: event.url,
|
|
154
|
+
data: {},
|
|
155
|
+
form: form_value,
|
|
156
|
+
state: {}
|
|
165
157
|
}
|
|
166
|
-
|
|
167
|
-
}
|
|
158
|
+
};
|
|
168
159
|
|
|
169
|
-
let
|
|
160
|
+
let current_node = props.tree;
|
|
161
|
+
let data = props.page.data;
|
|
170
162
|
|
|
171
|
-
// props_n (instead of props[n]) makes it easy to avoid
|
|
172
|
-
// unnecessary updates for layout components
|
|
173
163
|
for (let i = 0; i < branch.length; i += 1) {
|
|
174
|
-
|
|
175
|
-
|
|
164
|
+
const node = branch[i];
|
|
165
|
+
|
|
166
|
+
data = { ...data, ...node.data };
|
|
167
|
+
|
|
168
|
+
// TODO this is undefined sometimes... where does the default error component come from?
|
|
169
|
+
const error = error_components?.slice(0, i + 1).findLast((x) => x);
|
|
170
|
+
|
|
171
|
+
current_node.error = error;
|
|
172
|
+
current_node.component = await node.node.component?.();
|
|
173
|
+
current_node.data = data;
|
|
174
|
+
|
|
175
|
+
if (i < branch.length - 1) {
|
|
176
|
+
current_node.child = /** @type {import('../../types.js').RenderNode} */ ({});
|
|
177
|
+
current_node = current_node.child;
|
|
178
|
+
}
|
|
176
179
|
}
|
|
177
180
|
|
|
178
|
-
props.page =
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
route: event.route,
|
|
182
|
-
status,
|
|
183
|
-
url: event.url,
|
|
184
|
-
data,
|
|
185
|
-
form: form_value,
|
|
186
|
-
state: {}
|
|
187
|
-
};
|
|
181
|
+
props.page.data = data;
|
|
182
|
+
|
|
183
|
+
const render_state = { ...event_state, is_in_render: true };
|
|
188
184
|
|
|
189
185
|
const render_opts = {
|
|
190
186
|
context: new Map([
|
|
@@ -197,15 +193,28 @@ export async function render_response({
|
|
|
197
193
|
]),
|
|
198
194
|
csp: csp.script_needs_nonce ? { nonce: csp.nonce } : { hash: csp.script_needs_hash },
|
|
199
195
|
transformError: error_components
|
|
200
|
-
? /** @param {unknown} e */
|
|
196
|
+
? /** @param {unknown} e */ (e) => {
|
|
201
197
|
if (isRedirect(e)) {
|
|
202
198
|
throw e;
|
|
203
199
|
}
|
|
204
200
|
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
const handled = handle_error_and_jsonify(event, render_state, options, e);
|
|
202
|
+
|
|
203
|
+
// TODO 4.0 make this an async function and await `handled`
|
|
204
|
+
if (handled instanceof Promise) {
|
|
205
|
+
return handled.then((e) => {
|
|
206
|
+
error = e;
|
|
207
|
+
props.page.error = error;
|
|
208
|
+
props.page.status = status = error.status;
|
|
209
|
+
return error;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
error = handled;
|
|
214
|
+
props.page.error = error;
|
|
215
|
+
props.page.status = status = error.status;
|
|
216
|
+
|
|
217
|
+
return error;
|
|
209
218
|
}
|
|
210
219
|
: undefined
|
|
211
220
|
};
|
|
@@ -231,53 +240,27 @@ export async function render_response({
|
|
|
231
240
|
};
|
|
232
241
|
}
|
|
233
242
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
rendered = await with_request_store({ event, state }, async () => {
|
|
237
|
-
// use relative paths during rendering, so that the resulting HTML is as
|
|
238
|
-
// portable as possible, but reset afterwards
|
|
239
|
-
if (paths.relative) paths.override({ base, assets });
|
|
240
|
-
|
|
241
|
-
const maybe_promise = options.root.render(props, render_opts);
|
|
243
|
+
rendered = await with_request_store({ event, state: render_state }, async () => {
|
|
242
244
|
// We have to invoke .then eagerly here in order to kick off rendering: it's only starting on access,
|
|
243
245
|
// and `await maybe_promise` would eagerly access the .then property but call its function only after a tick, which is too late
|
|
244
246
|
// for the paths.reset() below and for any eager getRequestEvent() calls during rendering without AsyncLocalStorage available.
|
|
245
|
-
const rendered =
|
|
246
|
-
options.async && 'then' in maybe_promise
|
|
247
|
-
? /** @type {ReturnType<typeof options.root.render> & Promise<any>} */ (
|
|
248
|
-
maybe_promise
|
|
249
|
-
).then((r) => r)
|
|
250
|
-
: maybe_promise;
|
|
251
|
-
|
|
252
|
-
// TODO 3.0 remove options.async
|
|
253
|
-
if (options.async) {
|
|
254
|
-
// we reset this synchronously, rather than after async rendering is complete,
|
|
255
|
-
// to avoid cross-talk between requests. This is a breaking change for
|
|
256
|
-
// anyone who opts into async SSR, since `base` and `assets` will no
|
|
257
|
-
// longer be relative to the current pathname.
|
|
258
|
-
// TODO 3.0 remove `base` and `assets` in favour of `resolve(...)` and `asset(...)`
|
|
259
|
-
paths.reset();
|
|
260
|
-
}
|
|
247
|
+
const rendered = render(Root, { ...render_opts, props });
|
|
261
248
|
|
|
262
|
-
const { head,
|
|
263
|
-
options.async ? await rendered : rendered
|
|
264
|
-
);
|
|
249
|
+
const { head, body, hashes } = await rendered;
|
|
265
250
|
|
|
266
251
|
if (hashes) {
|
|
267
252
|
csp.add_script_hashes(hashes.script);
|
|
268
253
|
}
|
|
269
254
|
|
|
270
|
-
return { head,
|
|
255
|
+
return { head, body, hashes };
|
|
271
256
|
});
|
|
272
257
|
} finally {
|
|
273
258
|
if (DEV) {
|
|
274
259
|
globalThis.fetch = fetch;
|
|
275
260
|
}
|
|
276
|
-
|
|
277
|
-
paths.reset(); // just in case `options.root.render(...)` failed
|
|
278
261
|
}
|
|
279
262
|
} else {
|
|
280
|
-
rendered = { head: '',
|
|
263
|
+
rendered = { head: '', body: '', hashes: { script: [] } };
|
|
281
264
|
}
|
|
282
265
|
|
|
283
266
|
for (const { node } of branch) {
|
|
@@ -298,7 +281,7 @@ export async function render_response({
|
|
|
298
281
|
}
|
|
299
282
|
|
|
300
283
|
const head = new Head(rendered.head);
|
|
301
|
-
let body = rendered.
|
|
284
|
+
let body = rendered.body;
|
|
302
285
|
|
|
303
286
|
/** @param {string} path */
|
|
304
287
|
const prefixed = (path) => {
|
|
@@ -421,7 +404,7 @@ export async function render_response({
|
|
|
421
404
|
|
|
422
405
|
const blocks = [];
|
|
423
406
|
|
|
424
|
-
const properties = [`base: ${base_expression}`];
|
|
407
|
+
const properties = [`base: ${base_expression}`, `version: ${s(__SVELTEKIT_APP_VERSION__)}`];
|
|
425
408
|
|
|
426
409
|
if (paths.assets) {
|
|
427
410
|
properties.push(`assets: ${s(paths.assets)}`);
|
|
@@ -444,7 +427,9 @@ export async function render_response({
|
|
|
444
427
|
if (client.inline) {
|
|
445
428
|
app_declaration = `const app = ${global}.app.app;`;
|
|
446
429
|
} else if (client.app) {
|
|
447
|
-
app_declaration = `const
|
|
430
|
+
app_declaration = `const kit = await import(${s(prefixed(client.start))});
|
|
431
|
+
kit.init(${global});
|
|
432
|
+
const app = await import(${s(prefixed(client.app))});`;
|
|
448
433
|
} else {
|
|
449
434
|
app_declaration = `const { app } = await import(${s(prefixed(client.start))});`;
|
|
450
435
|
}
|
|
@@ -538,10 +523,9 @@ export async function render_response({
|
|
|
538
523
|
|
|
539
524
|
${serialized_data}${global}.app.start(${args.join(', ')});`
|
|
540
525
|
: client.app
|
|
541
|
-
? `
|
|
542
|
-
|
|
543
|
-
import(${s(prefixed(client.app))})
|
|
544
|
-
]).then(([kit, app]) => {
|
|
526
|
+
? `import(${s(prefixed(client.start))}).then(async (kit) => {
|
|
527
|
+
kit.init(${global});
|
|
528
|
+
const app = await import(${s(prefixed(client.app))});
|
|
545
529
|
${serialized_data}kit.start(app, ${args.join(', ')});
|
|
546
530
|
});`
|
|
547
531
|
: `import(${s(prefixed(client.start))}).then((app) => {
|
|
@@ -69,15 +69,19 @@ export async function resolve_route(resolved_path, url, manifest) {
|
|
|
69
69
|
return text('Server-side route resolution disabled', { status: 400 });
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
try {
|
|
73
|
+
const matchers = await manifest._.matchers();
|
|
74
|
+
const result = find_route(resolved_path, manifest._.client.routes, matchers);
|
|
75
|
+
|
|
76
|
+
return create_server_routing_response(
|
|
77
|
+
result?.route ?? null,
|
|
78
|
+
result?.params ?? {},
|
|
79
|
+
url,
|
|
80
|
+
manifest._.client
|
|
81
|
+
).response;
|
|
82
|
+
} catch {
|
|
83
|
+
return text('Error resolving route', { status: 500 });
|
|
84
|
+
}
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
/**
|
|
@@ -361,14 +361,15 @@ export async function collect_remote_data(data, event, state, options) {
|
|
|
361
361
|
* @returns {Promise<App.Error>}
|
|
362
362
|
*/
|
|
363
363
|
function convert_error(error) {
|
|
364
|
-
|
|
364
|
+
// TODO 4.0 remove the `Promise.resolve(...)`
|
|
365
|
+
return Promise.resolve(handle_error_and_jsonify(event, state, options, error));
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
/** @type {Promise<any>[]} */
|
|
368
369
|
const promises = [];
|
|
369
370
|
|
|
370
371
|
if (state.remote.explicit) {
|
|
371
|
-
for (const [remote_key, { internals,
|
|
372
|
+
for (const [remote_key, { internals, fn }] of state.remote.explicit) {
|
|
372
373
|
// there were explicit refreshes/reconnects (via `refresh()`/`set()`/`reconnect()`),
|
|
373
374
|
// so the client should apply these single-flight updates instead of calling `invalidateAll()`
|
|
374
375
|
data.r = true;
|
|
@@ -377,18 +378,27 @@ export async function collect_remote_data(data, event, state, options) {
|
|
|
377
378
|
internals.type === 'query_live' ? 'l' : internals.type[0]
|
|
378
379
|
);
|
|
379
380
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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;
|
|
397
|
+
}
|
|
389
398
|
|
|
390
|
-
|
|
391
|
-
|
|
399
|
+
((data[type] ??= {})[remote_key] ??= {}).e = await convert_error(e);
|
|
400
|
+
}
|
|
401
|
+
)
|
|
392
402
|
);
|
|
393
403
|
}
|
|
394
404
|
}
|
|
@@ -336,14 +336,17 @@ export async function internal_respond(request, options, manifest, state) {
|
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
if (!state.prerendering?.fallback) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
339
|
+
try {
|
|
340
|
+
const matchers = await manifest._.matchers();
|
|
341
|
+
const result = find_route(resolved_path, manifest._.routes, matchers);
|
|
342
|
+
|
|
343
|
+
if (result) {
|
|
344
|
+
route = result.route;
|
|
345
|
+
event.route = { id: route.id };
|
|
346
|
+
event.params = result.params;
|
|
347
|
+
}
|
|
348
|
+
} catch (e) {
|
|
349
|
+
return await handle_fatal_error(event, event_state, options, e);
|
|
347
350
|
}
|
|
348
351
|
}
|
|
349
352
|
|
|
@@ -98,9 +98,9 @@ export async function handle_fatal_error(event, state, options, error) {
|
|
|
98
98
|
* @param {import('types').RequestState} state
|
|
99
99
|
* @param {import('types').SSROptions} options
|
|
100
100
|
* @param {any} error
|
|
101
|
-
* @returns {Promise<App.Error>}
|
|
101
|
+
* @returns {App.Error | Promise<App.Error>}
|
|
102
102
|
*/
|
|
103
|
-
export
|
|
103
|
+
export function handle_error_and_jsonify(event, state, options, error) {
|
|
104
104
|
if (error instanceof HttpError) {
|
|
105
105
|
// @ts-expect-error custom user errors may not have a message field if App.Error is overwritten
|
|
106
106
|
return { message: 'Unknown Error', ...error.body };
|
|
@@ -113,11 +113,34 @@ export async function handle_error_and_jsonify(event, state, options, error) {
|
|
|
113
113
|
const status = get_status(error);
|
|
114
114
|
const message = get_message(error);
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
// TODO 4.0 await this, rather than handling the non-Promise case
|
|
117
|
+
const result = with_request_store({ event, state }, () =>
|
|
117
118
|
options.hooks.handleError({ error, event, status, message })
|
|
118
|
-
)
|
|
119
|
+
) ?? { status, message };
|
|
120
|
+
|
|
121
|
+
if (result instanceof Promise) {
|
|
122
|
+
if (!__SVELTEKIT_SUPPORTS_ASYNC__ && state.is_in_render) {
|
|
123
|
+
console.warn(
|
|
124
|
+
`To use an async \`handleError\` hook to handle errors that occur during rendering, you must enable \`compilerOptions.experimental.async\` in the SvelteKit plugin of your Vite config. The returned error has been replaced with a generic object`
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// we're discarding the result, but we still need to prevent an unhandled
|
|
128
|
+
// rejection if the user's async `handleError` hook rejects
|
|
129
|
+
result.catch(() => {});
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
status,
|
|
133
|
+
message: 'Internal Error'
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return result.then((body) => {
|
|
138
|
+
body ??= { status, message };
|
|
139
|
+
return { ...body, status: get_status(body, error) };
|
|
140
|
+
});
|
|
141
|
+
}
|
|
119
142
|
|
|
120
|
-
return { ...
|
|
143
|
+
return { ...result, status: get_status(result, error) };
|
|
121
144
|
}
|
|
122
145
|
|
|
123
146
|
/**
|
|
@@ -15,7 +15,7 @@ if (__SVELTEKIT_SERVER_TRACING_ENABLED__) {
|
|
|
15
15
|
})
|
|
16
16
|
.catch(() => {
|
|
17
17
|
throw new Error(
|
|
18
|
-
'Tracing is enabled (see the SvelteKit plugin `
|
|
18
|
+
'Tracing is enabled (see the SvelteKit plugin `tracing.server` option in your vite.config.js), but `@opentelemetry/api` is not available. This error will likely resolve itself when you set up your tracing instrumentation in `instrumentation.server.js`. For more information, see https://svelte.dev/docs/kit/observability#opentelemetry-api'
|
|
19
19
|
);
|
|
20
20
|
});
|
|
21
21
|
}
|
package/src/types/ambient.d.ts
CHANGED
|
@@ -32,23 +32,27 @@ declare namespace App {
|
|
|
32
32
|
/**
|
|
33
33
|
* The interface that defines `event.locals`, which can be accessed in server [hooks](https://svelte.dev/docs/kit/hooks) (`handle`, and `handleError`), server-only `load` functions, and `+server.js` files.
|
|
34
34
|
*/
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
35
36
|
export interface Locals {}
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
|
-
* Defines the common shape of the [page.data state](https://svelte.dev/docs/kit/$app-state#page)
|
|
39
|
+
* Defines the common shape of the [page.data state](https://svelte.dev/docs/kit/$app-state#page) - that is, the data that is shared between all pages.
|
|
39
40
|
* The `Load` and `ServerLoad` functions in `./$types` will be narrowed accordingly.
|
|
40
41
|
* Use optional properties for data that is only present on specific pages. Do not add an index signature (`[key: string]: any`).
|
|
41
42
|
*/
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
42
44
|
export interface PageData {}
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
47
|
* The shape of the `page.state` object, which can be manipulated using the [`pushState`](https://svelte.dev/docs/kit/$app-navigation#pushState) and [`replaceState`](https://svelte.dev/docs/kit/$app-navigation#replaceState) functions from `$app/navigation`.
|
|
46
48
|
*/
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
47
50
|
export interface PageState {}
|
|
48
51
|
|
|
49
52
|
/**
|
|
50
53
|
* If your adapter provides [platform-specific context](https://svelte.dev/docs/kit/adapters#Platform-specific-context) via `event.platform`, you can specify it here.
|
|
51
54
|
*/
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
52
56
|
export interface Platform {}
|
|
53
57
|
}
|
|
54
58
|
|