@sveltejs/kit 3.0.0-next.13 → 3.0.0-next.14
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 +2 -2
- package/src/cli.js +2 -2
- package/src/core/adapt/builder.js +8 -5
- package/src/core/generate_manifest/index.js +6 -0
- package/src/core/postbuild/entities.js +8 -2
- package/src/core/postbuild/fallback.js +2 -1
- package/src/core/postbuild/prerender.js +12 -3
- package/src/core/sync/create_manifest_data/conflict.js +1 -1
- package/src/core/sync/create_manifest_data/index.js +33 -2
- package/src/core/sync/write_app_types.js +72 -27
- package/src/core/sync/write_tsconfig/index.js +2 -1
- package/src/core/sync/write_types/index.js +5 -2
- package/src/exports/node/index.js +2 -7
- package/src/exports/public.d.ts +3 -2
- package/src/exports/vite/index.js +45 -11
- package/src/exports/vite/utils.js +1 -7
- package/src/runtime/app/server/remote/query.js +0 -6
- package/src/runtime/client/client.js +148 -38
- package/src/runtime/client/fetcher.js +2 -13
- package/src/runtime/client/remote-functions/form.svelte.js +3 -7
- package/src/runtime/client/remote-functions/prerender.svelte.js +1 -1
- package/src/runtime/client/remote-functions/query/index.js +1 -1
- package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
- package/src/runtime/client/remote-functions/query-live/proxy.js +0 -10
- package/src/runtime/client/remote-functions/shared.svelte.js +1 -1
- package/src/runtime/client/utils.js +1 -0
- package/src/runtime/form-utils.js +4 -6
- package/src/runtime/pathname.js +37 -0
- package/src/runtime/server/fetch.js +5 -8
- package/src/runtime/server/index.js +10 -24
- package/src/runtime/server/page/crypto.js +2 -2
- package/src/runtime/server/page/csp.js +2 -1
- package/src/runtime/server/page/load_data.js +2 -29
- package/src/runtime/server/page/render.js +19 -1
- package/src/runtime/server/page/serialize_data.js +2 -13
- package/src/runtime/server/page/server_routing.js +58 -9
- package/src/runtime/server/respond.js +20 -27
- package/src/runtime/server/state.js +43 -0
- package/src/runtime/shared.js +4 -2
- package/src/runtime/utils.js +3 -0
- package/src/types/ambient-private.d.ts +1 -1
- package/src/types/ambient.d.ts +58 -7
- package/src/types/global-private.d.ts +1 -1
- package/src/types/internal.d.ts +2 -1
- package/src/utils/hash.js +21 -0
- package/src/utils/http.js +8 -7
- package/src/utils/import.js +2 -1
- package/src/utils/params.js +1 -1
- package/src/utils/regex.js +9 -0
- package/src/utils/routing.js +52 -27
- package/src/utils/url.js +1 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +74 -10
- package/types/index.d.ts.map +1 -1
- package/src/exports/node/polyfills.js +0 -30
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
scroll_state,
|
|
22
22
|
load_css
|
|
23
23
|
} from './utils.js';
|
|
24
|
-
import { base, set_match_implementation } from '$app/paths/internal/client';
|
|
24
|
+
import { base, app_dir, set_match_implementation } from '$app/paths/internal/client';
|
|
25
25
|
import * as devalue from 'devalue';
|
|
26
26
|
import {
|
|
27
27
|
HISTORY_INFO_KEY,
|
|
@@ -41,7 +41,11 @@ import {
|
|
|
41
41
|
import { get_message, get_status } from '../../utils/error.js';
|
|
42
42
|
import { page, navigating, updated, notify_version } from './state.svelte.js';
|
|
43
43
|
import { payload } from './payload.js';
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
add_data_suffix,
|
|
46
|
+
add_resolution_suffix,
|
|
47
|
+
route_id_resolution_pathname
|
|
48
|
+
} from '../pathname.js';
|
|
45
49
|
import { noop_span } from '../telemetry/noop.js';
|
|
46
50
|
import { read_ndjson } from './ndjson.js';
|
|
47
51
|
import Root from '../components/root.svelte';
|
|
@@ -294,6 +298,35 @@ function discard_load_cache() {
|
|
|
294
298
|
*/
|
|
295
299
|
const reroute_cache = new Map();
|
|
296
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Sentinel for a route that exists but has no code to preload, i.e. a `+server.js` with no
|
|
303
|
+
* `+page`. Cached like a parsed route so that repeated `preloadCode(id)` calls for the same
|
|
304
|
+
* id don't re-request it.
|
|
305
|
+
*/
|
|
306
|
+
const ENDPOINT_ONLY = Symbol('endpoint only');
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Cache of route ID -> parsed route for server-side route resolution.
|
|
310
|
+
* Populated whenever a server route resolution occurs (`match`, link preloading,
|
|
311
|
+
* hydration), so that `preloadCode(id)` doesn't need an extra server round trip.
|
|
312
|
+
* Lives until full page reload.
|
|
313
|
+
* @type {Map<string, import('types').CSRRoute | typeof ENDPOINT_ONLY>}
|
|
314
|
+
*/
|
|
315
|
+
const route_id_cache = new Map();
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Parse a server-provided route and record it in `route_id_cache`, so that a subsequent
|
|
319
|
+
* `preloadCode(id)` for the same route doesn't need another server round trip. Always use
|
|
320
|
+
* this rather than calling `parse_server_route` directly.
|
|
321
|
+
* @param {import('types').CSRRouteServer} server_route
|
|
322
|
+
* @returns {import('types').CSRRoute}
|
|
323
|
+
*/
|
|
324
|
+
function parse_and_cache_server_route(server_route) {
|
|
325
|
+
const route = parse_server_route(server_route, app.nodes);
|
|
326
|
+
route_id_cache.set(route.id, route);
|
|
327
|
+
return route;
|
|
328
|
+
}
|
|
329
|
+
|
|
297
330
|
/**
|
|
298
331
|
* Note on before_navigate_callbacks, on_navigate_callbacks and after_navigate_callbacks:
|
|
299
332
|
* do not re-assign as some closures keep references to these Sets
|
|
@@ -661,13 +694,13 @@ function persist_state() {
|
|
|
661
694
|
|
|
662
695
|
/**
|
|
663
696
|
* @param {string | URL} url
|
|
664
|
-
* @param {{ replace?: boolean; reset?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any>; persistState?: boolean }} options
|
|
665
|
-
* @param {number} redirect_count
|
|
697
|
+
* @param {{ type?: import('@sveltejs/kit').NavigationType; replace?: boolean; reset?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any>; persistState?: boolean; event?: Event }} [options]
|
|
698
|
+
* @param {number} [redirect_count]
|
|
666
699
|
* @param {{}} [nav_token]
|
|
667
700
|
* @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
|
|
668
701
|
* @returns {Promise<void>}
|
|
669
702
|
*/
|
|
670
|
-
export async function _goto(url, options, redirect_count, nav_token, intent) {
|
|
703
|
+
export async function _goto(url, options = {}, redirect_count = 0, nav_token = {}, intent) {
|
|
671
704
|
/** @type {Set<string>} */
|
|
672
705
|
let query_keys;
|
|
673
706
|
/** @type {Set<string>} */
|
|
@@ -680,12 +713,13 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
|
|
|
680
713
|
}
|
|
681
714
|
|
|
682
715
|
await navigate({
|
|
683
|
-
type: 'goto',
|
|
716
|
+
type: options.type ?? 'goto',
|
|
684
717
|
url: resolve_url(url),
|
|
685
718
|
reset: options.reset,
|
|
686
719
|
replace_state: options.replace,
|
|
687
720
|
state: options.state,
|
|
688
721
|
persist_state: options.persistState,
|
|
722
|
+
event: options.event,
|
|
689
723
|
redirect_count,
|
|
690
724
|
nav_token,
|
|
691
725
|
intent,
|
|
@@ -786,6 +820,53 @@ async function _preload_data(intent) {
|
|
|
786
820
|
return load_cache.promise;
|
|
787
821
|
}
|
|
788
822
|
|
|
823
|
+
/**
|
|
824
|
+
* Fetch and parse the route with the given ID from the server-side route resolution endpoint.
|
|
825
|
+
* Returns `ENDPOINT_ONLY` if the route exists but has no code to preload, or `undefined` if
|
|
826
|
+
* there is no such route. Only used when `router.resolution === 'server'`.
|
|
827
|
+
* @param {string} id
|
|
828
|
+
* @returns {Promise<import('types').CSRRoute | typeof ENDPOINT_ONLY | undefined>}
|
|
829
|
+
*/
|
|
830
|
+
async function load_route_by_id(id) {
|
|
831
|
+
/** @type {{ route?: import('types').CSRRouteServer, endpoint_only?: boolean }} */
|
|
832
|
+
let module;
|
|
833
|
+
|
|
834
|
+
try {
|
|
835
|
+
module = await import(
|
|
836
|
+
/* @vite-ignore */
|
|
837
|
+
base + route_id_resolution_pathname(app_dir, id)
|
|
838
|
+
);
|
|
839
|
+
} catch {
|
|
840
|
+
// if there's no module at that path the response is a 404 (or, on a static
|
|
841
|
+
// host, fallback HTML with the wrong MIME type) and the import rejects —
|
|
842
|
+
// treat it the same as an unknown route rather than surfacing a cryptic error
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (module.endpoint_only) {
|
|
847
|
+
// The route exists, it just has no code to preload.
|
|
848
|
+
route_id_cache.set(id, ENDPOINT_ONLY);
|
|
849
|
+
return ENDPOINT_ONLY;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (!module.route) return;
|
|
853
|
+
|
|
854
|
+
return parse_and_cache_server_route(module.route);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Import the modules for a route's layout and leaf nodes, without running `load` functions.
|
|
859
|
+
* @param {import('types').CSRRoute} route
|
|
860
|
+
* @returns {Promise<void>}
|
|
861
|
+
*/
|
|
862
|
+
async function load_route_nodes(route) {
|
|
863
|
+
await Promise.all(
|
|
864
|
+
/** @type {[has_server_load: boolean, node_loader: import('types').CSRPageNodeLoader][]} */ (
|
|
865
|
+
[...route.layouts, route.leaf].filter(Boolean)
|
|
866
|
+
).map(([, node_loader]) => node_loader())
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
789
870
|
/**
|
|
790
871
|
* @param {URL} url
|
|
791
872
|
* @returns {Promise<void>}
|
|
@@ -794,11 +875,7 @@ async function _preload_code(url) {
|
|
|
794
875
|
const route = (await get_navigation_intent(url, false))?.route;
|
|
795
876
|
|
|
796
877
|
if (route) {
|
|
797
|
-
await
|
|
798
|
-
/** @type {[has_server_load: boolean, node_loader: import('types').CSRPageNodeLoader][]} */ (
|
|
799
|
-
[...route.layouts, route.leaf].filter(Boolean)
|
|
800
|
-
).map((load) => load[1]())
|
|
801
|
-
);
|
|
878
|
+
await load_route_nodes(route);
|
|
802
879
|
}
|
|
803
880
|
}
|
|
804
881
|
|
|
@@ -1607,7 +1684,7 @@ async function load_root_error_page({ error, url, route }) {
|
|
|
1607
1684
|
// client-side navigation if the root layout loader throws a redirect while
|
|
1608
1685
|
// rendering the default error page
|
|
1609
1686
|
if (error instanceof Redirect) {
|
|
1610
|
-
await _goto(new URL(error.location, location.href)
|
|
1687
|
+
await _goto(new URL(error.location, location.href));
|
|
1611
1688
|
return;
|
|
1612
1689
|
}
|
|
1613
1690
|
|
|
@@ -1729,7 +1806,7 @@ export async function get_navigation_intent(url, invalidating) {
|
|
|
1729
1806
|
return {
|
|
1730
1807
|
id: get_page_key(url),
|
|
1731
1808
|
invalidating,
|
|
1732
|
-
route:
|
|
1809
|
+
route: parse_and_cache_server_route(route),
|
|
1733
1810
|
params,
|
|
1734
1811
|
url
|
|
1735
1812
|
};
|
|
@@ -2656,45 +2733,76 @@ export async function preloadData(href) {
|
|
|
2656
2733
|
* Programmatically imports the code for routes that haven't yet been fetched.
|
|
2657
2734
|
* Typically, you might call this to speed up subsequent navigation.
|
|
2658
2735
|
*
|
|
2659
|
-
*
|
|
2736
|
+
* Takes a route ID such as `/about` or `/blog/[slug]`. Unlike pathnames, route IDs
|
|
2737
|
+
* are never prefixed with the app's [base path](https://svelte.dev/docs/kit/configuration#paths).
|
|
2738
|
+
* If you have a pathname rather than a route ID, you can convert it with
|
|
2739
|
+
* [`match`](https://svelte.dev/docs/kit/$app-paths#match) from `$app/paths`:
|
|
2740
|
+
*
|
|
2741
|
+
* ```js
|
|
2742
|
+
* import { match } from '$app/paths';
|
|
2743
|
+
* import { preloadCode } from '$app/navigation';
|
|
2744
|
+
*
|
|
2745
|
+
* const matched = await match('/blog/hello-world');
|
|
2746
|
+
* if (matched) await preloadCode(matched.id);
|
|
2747
|
+
* ```
|
|
2660
2748
|
*
|
|
2661
2749
|
* Unlike `preloadData`, this won't call `load` functions.
|
|
2662
2750
|
* Returns a Promise that resolves when the modules have been imported.
|
|
2663
2751
|
*
|
|
2664
|
-
* @param {
|
|
2752
|
+
* @param {RouteId} id
|
|
2665
2753
|
* @returns {Promise<void>}
|
|
2666
2754
|
*/
|
|
2667
|
-
export async function preloadCode(
|
|
2755
|
+
export async function preloadCode(id) {
|
|
2668
2756
|
if (!BROWSER) {
|
|
2669
2757
|
throw new Error('Cannot call preloadCode(...) on the server');
|
|
2670
2758
|
}
|
|
2671
2759
|
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2760
|
+
if (DEV && id[0] !== '/') {
|
|
2761
|
+
throw new Error(
|
|
2762
|
+
`argument passed to preloadCode must be a route ID (i.e. "/blog/[slug]" rather than "blog/[slug]")`
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2675
2765
|
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
'argument passed to preloadCode must be a pathname (i.e. "/about" rather than "http://example.com/about"'
|
|
2680
|
-
);
|
|
2681
|
-
}
|
|
2766
|
+
const route = __SVELTEKIT_CLIENT_ROUTING__
|
|
2767
|
+
? routes.find((r) => r.id === id)
|
|
2768
|
+
: (route_id_cache.get(id) ?? (await load_route_by_id(id)));
|
|
2682
2769
|
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2770
|
+
if (route === ENDPOINT_ONLY) {
|
|
2771
|
+
if (DEV) {
|
|
2772
|
+
console.warn(
|
|
2773
|
+
`'${id}' has no \`+page\`, so there is no code to preload. If you meant to warm up an ` +
|
|
2774
|
+
`endpoint, request it with \`fetch\` instead.`
|
|
2686
2775
|
);
|
|
2687
2776
|
}
|
|
2688
2777
|
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
if (!route) {
|
|
2782
|
+
if (DEV) {
|
|
2783
|
+
// warn rather than throw, since under client routing an endpoint-only route id is
|
|
2784
|
+
// indistinguishable from a typo — the client manifest only contains routes with a `+page`
|
|
2785
|
+
let message = `'${id}' did not match any route`;
|
|
2786
|
+
|
|
2787
|
+
if (__SVELTEKIT_CLIENT_ROUTING__) {
|
|
2788
|
+
message += ` (note that routes without a \`+page\` have no code to preload)`;
|
|
2789
|
+
|
|
2790
|
+
// the most common migration mistake is passing a pathname, which used to work
|
|
2791
|
+
const candidates = [id];
|
|
2792
|
+
if (base && id.startsWith(base)) candidates.push(id.slice(base.length) || '/');
|
|
2793
|
+
|
|
2794
|
+
if (candidates.some((path) => routes.some((r) => r.exec(path)))) {
|
|
2795
|
+
message += `. It does match as a pathname — use \`match(...)\` from \`$app/paths\` to convert a pathname into a route ID`;
|
|
2796
|
+
}
|
|
2693
2797
|
}
|
|
2798
|
+
|
|
2799
|
+
console.warn(message);
|
|
2694
2800
|
}
|
|
2801
|
+
|
|
2802
|
+
return;
|
|
2695
2803
|
}
|
|
2696
2804
|
|
|
2697
|
-
|
|
2805
|
+
await load_route_nodes(route);
|
|
2698
2806
|
}
|
|
2699
2807
|
|
|
2700
2808
|
/**
|
|
@@ -2892,7 +3000,7 @@ export async function applyAction(result) {
|
|
|
2892
3000
|
if (result.type === 'error') {
|
|
2893
3001
|
await set_nearest_error_page(result.error);
|
|
2894
3002
|
} else if (result.type === 'redirect') {
|
|
2895
|
-
await _goto(result.location, { refreshAll: true }
|
|
3003
|
+
await _goto(result.location, { refreshAll: true });
|
|
2896
3004
|
} else {
|
|
2897
3005
|
page.form = result.data;
|
|
2898
3006
|
page.status = result.status;
|
|
@@ -3108,11 +3216,13 @@ function _start_router() {
|
|
|
3108
3216
|
setTimeout(fulfil, 100); // fallback for edge case where rAF doesn't fire because e.g. tab was backgrounded
|
|
3109
3217
|
});
|
|
3110
3218
|
|
|
3111
|
-
|
|
3219
|
+
const changed = url.href !== location.href;
|
|
3220
|
+
|
|
3221
|
+
await _goto(url, {
|
|
3112
3222
|
type: 'link',
|
|
3113
|
-
url,
|
|
3114
3223
|
reset: options.reset,
|
|
3115
|
-
|
|
3224
|
+
replace: options.replace_state ?? !changed,
|
|
3225
|
+
refreshAll: !changed,
|
|
3116
3226
|
event
|
|
3117
3227
|
});
|
|
3118
3228
|
});
|
|
@@ -3343,7 +3453,7 @@ async function _hydrate(
|
|
|
3343
3453
|
} else {
|
|
3344
3454
|
// undefined in case of 404
|
|
3345
3455
|
if (server_route) {
|
|
3346
|
-
parsed_route = route =
|
|
3456
|
+
parsed_route = route = parse_and_cache_server_route(server_route);
|
|
3347
3457
|
} else {
|
|
3348
3458
|
route = { id: null };
|
|
3349
3459
|
params = {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BROWSER, DEV } from 'esm-env';
|
|
2
2
|
import { noop } from '../../utils/functions.js';
|
|
3
|
-
import {
|
|
3
|
+
import { hash_request } from '../../utils/hash.js';
|
|
4
4
|
import { base64_decode } from '../utils.js';
|
|
5
5
|
|
|
6
6
|
let loading = 0;
|
|
@@ -172,18 +172,7 @@ function build_selector(resource, opts) {
|
|
|
172
172
|
return null;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
const values = [];
|
|
177
|
-
|
|
178
|
-
if (opts.headers) {
|
|
179
|
-
values.push([...new Headers(opts.headers)].join(','));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (body) {
|
|
183
|
-
values.push(/** @type {import('types').StrictBody} */ (body));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
selector += `[data-hash="${hash(...values)}"]`;
|
|
175
|
+
selector += `[data-hash="${hash_request(opts.headers, body)}"]`;
|
|
187
176
|
}
|
|
188
177
|
|
|
189
178
|
return selector;
|
|
@@ -262,13 +262,9 @@ export function form(id) {
|
|
|
262
262
|
|
|
263
263
|
if (response.redirect) {
|
|
264
264
|
// Use internal version to allow redirects to external URLs
|
|
265
|
-
void _goto(
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
refreshAll: should_refresh
|
|
269
|
-
},
|
|
270
|
-
0
|
|
271
|
-
);
|
|
265
|
+
void _goto(response.redirect, {
|
|
266
|
+
refreshAll: should_refresh
|
|
267
|
+
});
|
|
272
268
|
return true;
|
|
273
269
|
}
|
|
274
270
|
|
|
@@ -50,7 +50,7 @@ export function query_batch(id) {
|
|
|
50
50
|
|
|
51
51
|
if (response.redirect) {
|
|
52
52
|
// Use internal version to allow redirects to external URLs
|
|
53
|
-
await _goto(response.redirect
|
|
53
|
+
await _goto(response.redirect);
|
|
54
54
|
|
|
55
55
|
// settle all batched promises (with `undefined`, like a redirect
|
|
56
56
|
// from a non-batched query) so that callers don't hang forever
|
|
@@ -74,16 +74,6 @@ export class LiveQueryProxy {
|
|
|
74
74
|
return this.#get_cached_query().done;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/**
|
|
78
|
-
* @deprecated Use `for await (const value of liveQuery())` instead.
|
|
79
|
-
* @returns {AsyncGenerator<T>}
|
|
80
|
-
*/
|
|
81
|
-
run() {
|
|
82
|
-
throw new Error(
|
|
83
|
-
'`.run()` has been removed from live queries. Use `for await (const value of liveQuery())` instead.'
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
77
|
reconnect() {
|
|
88
78
|
return this.#get_cached_query().reconnect();
|
|
89
79
|
}
|
|
@@ -197,7 +197,7 @@ export async function remote_request(url, init) {
|
|
|
197
197
|
export async function handle_side_channel_response(response) {
|
|
198
198
|
if (response.type === 'redirect') {
|
|
199
199
|
// Use internal version to allow redirects to external URLs
|
|
200
|
-
await _goto(response.location
|
|
200
|
+
await _goto(response.location);
|
|
201
201
|
throw new Redirect(307, response.location);
|
|
202
202
|
}
|
|
203
203
|
|
|
@@ -122,6 +122,7 @@ export function get_link_info(a, base, uses_hash_router) {
|
|
|
122
122
|
/** @type {URL | undefined} */
|
|
123
123
|
let url;
|
|
124
124
|
|
|
125
|
+
// TODO replace the try/catch with `URL.parse` when browser support allows (Chrome 126, Firefox 126, Safari 18)
|
|
125
126
|
try {
|
|
126
127
|
url = new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);
|
|
127
128
|
|
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
import { DEV } from 'esm-env';
|
|
5
5
|
import * as devalue from 'devalue';
|
|
6
|
-
import { text_encoder } from './utils.js';
|
|
6
|
+
import { text_decoder, text_encoder } from './utils.js';
|
|
7
7
|
import { noop } from '../utils/functions.js';
|
|
8
8
|
import { SvelteKitError } from '@sveltejs/kit/internal';
|
|
9
9
|
|
|
10
|
-
const decoder = new TextDecoder();
|
|
11
|
-
|
|
12
10
|
/**
|
|
13
11
|
* Sets a parsed form field value in a nested object, mutating the original object.
|
|
14
12
|
* @param {Record<string, any>} object
|
|
@@ -283,7 +281,7 @@ export async function deserialize_binary_form(request, form_id) {
|
|
|
283
281
|
const file_offsets_buffer = await get_buffer(HEADER_BYTES + data_length, file_offsets_length);
|
|
284
282
|
if (!file_offsets_buffer) throw deserialize_error('file offset table too short');
|
|
285
283
|
|
|
286
|
-
const parsed_offsets = JSON.parse(
|
|
284
|
+
const parsed_offsets = JSON.parse(text_decoder.decode(file_offsets_buffer));
|
|
287
285
|
|
|
288
286
|
if (
|
|
289
287
|
!Array.isArray(parsed_offsets) ||
|
|
@@ -298,7 +296,7 @@ export async function deserialize_binary_form(request, form_id) {
|
|
|
298
296
|
|
|
299
297
|
/** @type {Array<{ offset: number, size: number }>} */
|
|
300
298
|
const file_spans = [];
|
|
301
|
-
const [data, meta] = devalue.parse(
|
|
299
|
+
const [data, meta] = devalue.parse(text_decoder.decode(data_buffer), {
|
|
302
300
|
File: ([name, type, size, last_modified, index]) => {
|
|
303
301
|
if (
|
|
304
302
|
typeof name !== 'string' ||
|
|
@@ -488,7 +486,7 @@ class LazyFile {
|
|
|
488
486
|
});
|
|
489
487
|
}
|
|
490
488
|
async text() {
|
|
491
|
-
return
|
|
489
|
+
return text_decoder.decode(await this.arrayBuffer());
|
|
492
490
|
}
|
|
493
491
|
}
|
|
494
492
|
|
package/src/runtime/pathname.js
CHANGED
|
@@ -47,3 +47,40 @@ export function add_resolution_suffix(pathname) {
|
|
|
47
47
|
export function strip_resolution_suffix(pathname) {
|
|
48
48
|
return pathname.slice(0, -ROUTE_SUFFIX.length);
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
const ROUTES_PREFIX = '/routes';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The pathname of the route-ID-keyed resolution module for a given route ID,
|
|
55
|
+
* e.g. `/_app/routes/blog/[slug]/__route.js` (before prefixing with `base`).
|
|
56
|
+
* @param {string} app_dir
|
|
57
|
+
* @param {string} route_id
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
export function route_id_resolution_pathname(app_dir, route_id) {
|
|
61
|
+
return add_resolution_suffix(`/${app_dir}${ROUTES_PREFIX}${route_id === '/' ? '' : route_id}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Whether a pathname (with the `/__route.js` suffix already stripped, and `base` NOT yet stripped)
|
|
66
|
+
* is a route-ID resolution request rather than a pathname resolution request.
|
|
67
|
+
* @param {string} pathname
|
|
68
|
+
* @param {string} base
|
|
69
|
+
* @param {string} app_dir
|
|
70
|
+
* @returns {boolean}
|
|
71
|
+
*/
|
|
72
|
+
export function is_route_id_resolution_path(pathname, base, app_dir) {
|
|
73
|
+
const prefix = `${base}/${app_dir}${ROUTES_PREFIX}`;
|
|
74
|
+
return pathname === prefix || pathname.startsWith(prefix + '/');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Extract the route ID from a decoded, base-stripped, suffix-stripped pathname,
|
|
79
|
+
* e.g. `/_app/routes/blog/[slug]` -> `/blog/[slug]`, `/_app/routes` -> `/`.
|
|
80
|
+
* @param {string} pathname
|
|
81
|
+
* @param {string} app_dir
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
export function extract_route_id(pathname, app_dir) {
|
|
85
|
+
return pathname.slice(`/${app_dir}${ROUTES_PREFIX}`.length) || '/';
|
|
86
|
+
}
|
|
@@ -4,6 +4,7 @@ import { respond } from './respond.js';
|
|
|
4
4
|
import * as paths from '$app/paths/internal/server';
|
|
5
5
|
import { read_implementation } from './internal.js';
|
|
6
6
|
import { has_prerendered_path } from './utils.js';
|
|
7
|
+
import { fork_state_for_subrequest } from './state.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @param {{
|
|
@@ -198,6 +199,8 @@ function normalize_fetch_input(info, init, url) {
|
|
|
198
199
|
* @returns {Promise<Response>}
|
|
199
200
|
*/
|
|
200
201
|
async function internal_fetch(request, options, manifest, state) {
|
|
202
|
+
const subrequest_state = fork_state_for_subrequest(state);
|
|
203
|
+
|
|
201
204
|
if (request.signal) {
|
|
202
205
|
if (request.signal.aborted) {
|
|
203
206
|
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
@@ -214,18 +217,12 @@ async function internal_fetch(request, options, manifest, state) {
|
|
|
214
217
|
});
|
|
215
218
|
|
|
216
219
|
const result = await Promise.race([
|
|
217
|
-
respond(request, options, manifest,
|
|
218
|
-
...state,
|
|
219
|
-
depth: state.depth + 1
|
|
220
|
-
}),
|
|
220
|
+
respond(request, options, manifest, subrequest_state),
|
|
221
221
|
abort_promise
|
|
222
222
|
]);
|
|
223
223
|
remove_abort_listener();
|
|
224
224
|
return result;
|
|
225
225
|
} else {
|
|
226
|
-
return await respond(request, options, manifest,
|
|
227
|
-
...state,
|
|
228
|
-
depth: state.depth + 1
|
|
229
|
-
});
|
|
226
|
+
return await respond(request, options, manifest, subrequest_state);
|
|
230
227
|
}
|
|
231
228
|
}
|
|
@@ -67,31 +67,17 @@ export class Server {
|
|
|
67
67
|
const result = read(file);
|
|
68
68
|
if (result instanceof ReadableStream) {
|
|
69
69
|
return result;
|
|
70
|
-
} else {
|
|
71
|
-
return new ReadableStream({
|
|
72
|
-
async start(controller) {
|
|
73
|
-
try {
|
|
74
|
-
const stream = await Promise.resolve(result);
|
|
75
|
-
if (!stream) {
|
|
76
|
-
controller.close();
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const reader = stream.getReader();
|
|
81
|
-
|
|
82
|
-
while (true) {
|
|
83
|
-
const { done, value } = await reader.read();
|
|
84
|
-
if (done) break;
|
|
85
|
-
controller.enqueue(value);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
controller.close();
|
|
89
|
-
} catch (error) {
|
|
90
|
-
controller.error(error);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
70
|
}
|
|
71
|
+
|
|
72
|
+
// TODO remove the cast once TypeScript's lib includes `ReadableStream.from`
|
|
73
|
+
return /** @type {ReadableStream} */ (
|
|
74
|
+
/** @type {any} */ (ReadableStream).from(
|
|
75
|
+
(async function* () {
|
|
76
|
+
const stream = await result;
|
|
77
|
+
if (stream) yield* stream;
|
|
78
|
+
})()
|
|
79
|
+
)
|
|
80
|
+
);
|
|
95
81
|
};
|
|
96
82
|
|
|
97
83
|
set_read_implementation(wrapped_read);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { text_encoder } from '../../utils.js';
|
|
1
|
+
import { base64_encode, text_encoder } from '../../utils.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* SHA-256 hashing function adapted from https://bitwiseshiftleft.github.io/sjcl
|
|
@@ -102,7 +102,7 @@ export function sha256(data) {
|
|
|
102
102
|
const bytes = new Uint8Array(out.buffer);
|
|
103
103
|
reverse_endianness(bytes);
|
|
104
104
|
|
|
105
|
-
return
|
|
105
|
+
return base64_encode(bytes);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/** The SHA-256 initialization vector */
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { escape_html } from '../../../utils/escape.js';
|
|
2
|
+
import { base64_encode } from '../../utils.js';
|
|
2
3
|
import { sha256 } from './crypto.js';
|
|
3
4
|
|
|
4
5
|
const array = new Uint8Array(16);
|
|
5
6
|
|
|
6
7
|
function generate_nonce() {
|
|
7
8
|
crypto.getRandomValues(array);
|
|
8
|
-
return
|
|
9
|
+
return base64_encode(array);
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
const quoted = new Set([
|
|
@@ -336,7 +336,7 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
|
|
|
336
336
|
|
|
337
337
|
const request_body =
|
|
338
338
|
input instanceof Request && cloned_body
|
|
339
|
-
? await
|
|
339
|
+
? await new Response(cloned_body).text()
|
|
340
340
|
: init?.body;
|
|
341
341
|
|
|
342
342
|
if (
|
|
@@ -371,16 +371,7 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
|
|
|
371
371
|
const [a, b] = response.body.tee();
|
|
372
372
|
|
|
373
373
|
void (async () => {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
for await (const chunk of a) {
|
|
377
|
-
const combined = new Uint8Array(result.length + chunk.length);
|
|
378
|
-
|
|
379
|
-
combined.set(result, 0);
|
|
380
|
-
combined.set(chunk, result.length);
|
|
381
|
-
|
|
382
|
-
result = combined;
|
|
383
|
-
}
|
|
374
|
+
const result = new Uint8Array(await new Response(a).arrayBuffer());
|
|
384
375
|
|
|
385
376
|
if (dependency) {
|
|
386
377
|
dependency.body = new Uint8Array(result);
|
|
@@ -496,21 +487,3 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts)
|
|
|
496
487
|
return response;
|
|
497
488
|
};
|
|
498
489
|
}
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* @param {ReadableStream<Uint8Array>} stream
|
|
502
|
-
*/
|
|
503
|
-
async function stream_to_string(stream) {
|
|
504
|
-
let result = '';
|
|
505
|
-
const reader = stream.getReader();
|
|
506
|
-
const decoder = new TextDecoder();
|
|
507
|
-
while (true) {
|
|
508
|
-
const { done, value } = await reader.read();
|
|
509
|
-
if (done) {
|
|
510
|
-
result += decoder.decode();
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
|
-
result += decoder.decode(value, { stream: true });
|
|
514
|
-
}
|
|
515
|
-
return result;
|
|
516
|
-
}
|