@sveltejs/kit 3.0.0-next.1 → 3.0.0-next.11
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/constants.js +2 -0
- package/src/core/adapt/builder.js +32 -49
- package/src/core/adapt/index.js +1 -4
- package/src/core/config/index.js +137 -71
- package/src/core/config/options.js +236 -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/fallback.js +2 -1
- package/src/core/postbuild/prerender.js +114 -48
- package/src/core/postbuild/queue.js +2 -1
- 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 +14 -15
- 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 +30 -5
- package/src/exports/env/index.js +77 -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 +8 -5
- 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 +68 -0
- package/src/exports/public.d.ts +363 -189
- 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 +92 -50
- package/src/exports/vite/index.js +903 -681
- package/src/exports/vite/module_ids.js +0 -2
- package/src/exports/vite/preview/index.js +32 -24
- 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 +40 -35
- 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 +3 -3
- 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 +10 -6
- package/src/runtime/app/server/index.js +1 -1
- 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 +39 -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 +15 -19
- package/src/runtime/server/env_module.js +0 -5
- package/src/runtime/server/errors.js +135 -0
- package/src/runtime/server/fetch.js +1 -1
- package/src/runtime/server/index.js +22 -12
- package/src/runtime/server/internal.js +25 -0
- package/src/runtime/server/page/actions.js +43 -27
- package/src/runtime/server/page/data_serializer.js +10 -10
- package/src/runtime/server/page/index.js +12 -17
- package/src/runtime/server/page/load_data.js +2 -2
- package/src/runtime/server/page/render.js +114 -193
- package/src/runtime/server/page/respond_with_error.js +11 -11
- package/src/runtime/server/page/server_routing.js +27 -18
- package/src/runtime/server/remote-functions.js +596 -0
- package/src/runtime/server/respond.js +100 -68
- package/src/runtime/server/sourcemaps.js +183 -0
- package/src/runtime/server/utils.js +2 -134
- 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-private.d.ts +2 -0
- package/src/types/ambient.d.ts +10 -5
- package/src/types/global-private.d.ts +13 -25
- package/src/types/internal.d.ts +95 -80
- package/src/types/private.d.ts +41 -1
- package/src/utils/error.js +24 -4
- package/src/utils/escape.js +9 -25
- package/src/utils/features.js +1 -1
- package/src/utils/fork.js +7 -2
- package/src/utils/import.js +6 -1
- package/src/utils/imports.js +83 -0
- package/src/utils/mime.js +9 -0
- package/src/utils/page_nodes.js +3 -5
- 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 +537 -557
- package/types/index.d.ts.map +24 -39
- package/src/core/sync/write_root.js +0 -148
- package/src/exports/internal/server.js +0 -22
- package/src/runtime/server/remote.js +0 -457
- package/src/runtime/shared-server.js +0 -7
- package/src/types/synthetic/$lib.md +0 -5
- /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
package/src/types/private.d.ts
CHANGED
|
@@ -61,7 +61,10 @@ export namespace Csp {
|
|
|
61
61
|
| 'unsafe-eval'
|
|
62
62
|
| 'unsafe-hashes'
|
|
63
63
|
| 'unsafe-inline'
|
|
64
|
+
| 'unsafe-allow-redirects'
|
|
65
|
+
| 'unsafe-webtransport-hashes'
|
|
64
66
|
| 'wasm-unsafe-eval'
|
|
67
|
+
| 'trusted-types-eval'
|
|
65
68
|
| 'none';
|
|
66
69
|
type CryptoSource = `${'nonce' | 'sha256' | 'sha384' | 'sha512'}-${string}`;
|
|
67
70
|
type FrameSource = HostSource | SchemeSource | 'self' | 'none';
|
|
@@ -70,7 +73,16 @@ export namespace Csp {
|
|
|
70
73
|
type HostProtocolSchemes = `${string}://` | '';
|
|
71
74
|
type HttpDelineator = '/' | '?' | '#' | '\\';
|
|
72
75
|
type PortScheme = `:${number}` | '' | ':*';
|
|
73
|
-
type SchemeSource =
|
|
76
|
+
type SchemeSource =
|
|
77
|
+
| 'http:'
|
|
78
|
+
| 'https:'
|
|
79
|
+
| 'ws:'
|
|
80
|
+
| 'wss:'
|
|
81
|
+
| 'data:'
|
|
82
|
+
| 'mediastream:'
|
|
83
|
+
| 'blob:'
|
|
84
|
+
| 'filesystem:'
|
|
85
|
+
| (`${string}:` & {});
|
|
74
86
|
type Source = HostSource | SchemeSource | CryptoSource | BaseSource;
|
|
75
87
|
type Sources = Source[];
|
|
76
88
|
}
|
|
@@ -147,10 +159,18 @@ export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' |
|
|
|
147
159
|
export interface Logger {
|
|
148
160
|
(msg: string): void;
|
|
149
161
|
success(msg: string): void;
|
|
162
|
+
/** Print a bold red message to stderr */
|
|
150
163
|
error(msg: string): void;
|
|
164
|
+
/** Print a bold yellow message to stderr */
|
|
151
165
|
warn(msg: string): void;
|
|
166
|
+
/** Print faded text to stdout if `verbose === true` */
|
|
152
167
|
minor(msg: string): void;
|
|
168
|
+
/** Print to stdout if `verbose === true` */
|
|
153
169
|
info(msg: string): void;
|
|
170
|
+
/** Print to stderr without formatting */
|
|
171
|
+
err(msg: string): void;
|
|
172
|
+
/** Print a bold red message, followed by a stack trace for each error (following `.cause` chains) */
|
|
173
|
+
prettyError(error: unknown, caller?: string): void;
|
|
154
174
|
}
|
|
155
175
|
|
|
156
176
|
export type MaybePromise<T> = T | Promise<T>;
|
|
@@ -212,6 +232,10 @@ export interface PrerenderUnseenRoutesHandler {
|
|
|
212
232
|
(details: { routes: string[]; message: string }): void;
|
|
213
233
|
}
|
|
214
234
|
|
|
235
|
+
export interface PrerenderInvalidUrlHandler {
|
|
236
|
+
(details: { href: string; referrer: string | null; message: string }): void;
|
|
237
|
+
}
|
|
238
|
+
|
|
215
239
|
export type PrerenderHttpErrorHandlerValue = 'fail' | 'warn' | 'ignore' | PrerenderHttpErrorHandler;
|
|
216
240
|
export type PrerenderMissingIdHandlerValue = 'fail' | 'warn' | 'ignore' | PrerenderMissingIdHandler;
|
|
217
241
|
export type PrerenderUnseenRoutesHandlerValue =
|
|
@@ -224,6 +248,11 @@ export type PrerenderEntryGeneratorMismatchHandlerValue =
|
|
|
224
248
|
| 'warn'
|
|
225
249
|
| 'ignore'
|
|
226
250
|
| PrerenderEntryGeneratorMismatchHandler;
|
|
251
|
+
export type PrerenderInvalidUrlHandlerValue =
|
|
252
|
+
| 'fail'
|
|
253
|
+
| 'warn'
|
|
254
|
+
| 'ignore'
|
|
255
|
+
| PrerenderInvalidUrlHandler;
|
|
227
256
|
|
|
228
257
|
export type PrerenderOption = boolean | 'auto';
|
|
229
258
|
|
|
@@ -252,3 +281,14 @@ export type DeepPartial<T> = T extends Record<PropertyKey, unknown> | unknown[]
|
|
|
252
281
|
: T | undefined;
|
|
253
282
|
|
|
254
283
|
export type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
284
|
+
|
|
285
|
+
export type HasNonOptionalBoolean<T> =
|
|
286
|
+
IsAny<T> extends true
|
|
287
|
+
? never
|
|
288
|
+
: [T] extends [boolean]
|
|
289
|
+
? true
|
|
290
|
+
: T extends Array<infer U>
|
|
291
|
+
? HasNonOptionalBoolean<U>
|
|
292
|
+
: T extends Record<string, any>
|
|
293
|
+
? { [K in keyof T]: HasNonOptionalBoolean<T[K]> }[keyof T]
|
|
294
|
+
: never;
|
package/src/utils/error.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { HttpError, SvelteKitError } from '@sveltejs/kit/internal';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* For times when you need to throw an error, but without
|
|
5
|
+
* displaying a useless stack trace (since the developer
|
|
6
|
+
* can't do anything useful with it)
|
|
7
|
+
* @param {string} message
|
|
8
|
+
*/
|
|
9
|
+
export function stackless(message) {
|
|
10
|
+
const error = new Error(message);
|
|
11
|
+
error.stack = '';
|
|
12
|
+
return error;
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
/**
|
|
4
16
|
* @param {unknown} err
|
|
5
17
|
* @return {Error}
|
|
@@ -18,16 +30,24 @@ export function coalesce_to_error(err) {
|
|
|
18
30
|
* @param {unknown} error
|
|
19
31
|
*/
|
|
20
32
|
export function normalize_error(error) {
|
|
21
|
-
return /** @type {import('../exports/internal/
|
|
33
|
+
return /** @type {import('../exports/internal/shared.js').Redirect | HttpError | SvelteKitError | Error} */ (
|
|
22
34
|
error
|
|
23
35
|
);
|
|
24
36
|
}
|
|
25
37
|
|
|
26
38
|
/**
|
|
27
|
-
* @param {
|
|
39
|
+
* @param {any} transformed
|
|
40
|
+
* @param {any} [error]
|
|
28
41
|
*/
|
|
29
|
-
export function get_status(error) {
|
|
30
|
-
|
|
42
|
+
export function get_status(transformed, error) {
|
|
43
|
+
const err = error ?? transformed;
|
|
44
|
+
const status = err instanceof HttpError || err instanceof SvelteKitError ? err.status : 500;
|
|
45
|
+
|
|
46
|
+
if (error == null || typeof transformed?.status !== 'number') {
|
|
47
|
+
return status;
|
|
48
|
+
} else {
|
|
49
|
+
return transformed.status;
|
|
50
|
+
}
|
|
31
51
|
}
|
|
32
52
|
|
|
33
53
|
/**
|
package/src/utils/escape.js
CHANGED
|
@@ -21,24 +21,12 @@ const escape_html_dict = {
|
|
|
21
21
|
'<': '<'
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// we match it so that we can match unpaired low surrogates in the same pass
|
|
28
|
-
// TODO: use lookbehind assertions once they are widely supported: (?<![\ud800-udbff])[\udc00-\udfff]
|
|
29
|
-
'[\\ud800-\\udbff][\\udc00-\\udfff]|' +
|
|
30
|
-
// unpaired low surrogate (see previous match)
|
|
31
|
-
'[\\udc00-\\udfff]';
|
|
24
|
+
// `\p{Surrogate}` only matches unpaired surrogates with the `u` flag (a pair is one code point)
|
|
25
|
+
/** @param {Record<string, string>} dict */
|
|
26
|
+
const escape_regex = (dict) => new RegExp(`[${Object.keys(dict).join('')}]|\\p{Surrogate}`, 'gu');
|
|
32
27
|
|
|
33
|
-
const escape_html_attr_regex =
|
|
34
|
-
|
|
35
|
-
'g'
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const escape_html_regex = new RegExp(
|
|
39
|
-
`[${Object.keys(escape_html_dict).join('')}]|` + surrogates,
|
|
40
|
-
'g'
|
|
41
|
-
);
|
|
28
|
+
const escape_html_attr_regex = escape_regex(escape_html_attr_dict);
|
|
29
|
+
const escape_html_regex = escape_regex(escape_html_dict);
|
|
42
30
|
|
|
43
31
|
/**
|
|
44
32
|
* Escapes unpaired surrogates (which are allowed in js strings but invalid in HTML) and
|
|
@@ -51,14 +39,10 @@ const escape_html_regex = new RegExp(
|
|
|
51
39
|
*/
|
|
52
40
|
export function escape_html(str, is_attr) {
|
|
53
41
|
const dict = is_attr ? escape_html_attr_dict : escape_html_dict;
|
|
54
|
-
const escaped_str = str.replace(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return dict[match] ?? `&#${match.charCodeAt(0)};`;
|
|
61
|
-
});
|
|
42
|
+
const escaped_str = str.replace(
|
|
43
|
+
is_attr ? escape_html_attr_regex : escape_html_regex,
|
|
44
|
+
(match) => dict[match] ?? `&#${match.charCodeAt(0)};`
|
|
45
|
+
);
|
|
62
46
|
|
|
63
47
|
return escaped_str;
|
|
64
48
|
}
|
package/src/utils/features.js
CHANGED
package/src/utils/fork.js
CHANGED
|
@@ -38,7 +38,8 @@ export function forked(module, callback) {
|
|
|
38
38
|
const worker = new Worker(fileURLToPath(module), {
|
|
39
39
|
env: {
|
|
40
40
|
...process.env,
|
|
41
|
-
SVELTEKIT_FORK: 'true'
|
|
41
|
+
SVELTEKIT_FORK: 'true',
|
|
42
|
+
FORCE_COLOR: '1'
|
|
42
43
|
}
|
|
43
44
|
});
|
|
44
45
|
|
|
@@ -60,9 +61,13 @@ export function forked(module, callback) {
|
|
|
60
61
|
}
|
|
61
62
|
);
|
|
62
63
|
|
|
64
|
+
worker.once('error', reject);
|
|
65
|
+
|
|
63
66
|
worker.on('exit', (code) => {
|
|
64
67
|
if (code) {
|
|
65
|
-
|
|
68
|
+
const error = new Error(`Failed with code ${code}`);
|
|
69
|
+
error.stack = error.message;
|
|
70
|
+
reject(error);
|
|
66
71
|
}
|
|
67
72
|
});
|
|
68
73
|
});
|
package/src/utils/import.js
CHANGED
|
@@ -2,7 +2,12 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Resolves a peer dependency relative to the current working directory.
|
|
5
|
+
* Resolves a peer dependency relative to the current working directory.
|
|
6
|
+
*
|
|
7
|
+
* Mainly used to resolve the correct Vite package when an app's SvelteKit is a
|
|
8
|
+
* linked local repository.
|
|
9
|
+
*
|
|
10
|
+
* Duplicated with `packages/adapter-auto`
|
|
6
11
|
* @param {string} dependency
|
|
7
12
|
* @param {string} root
|
|
8
13
|
*/
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reads the `imports` field from the package.json at the given directory.
|
|
6
|
+
* @param {string} cwd
|
|
7
|
+
* @returns {Record<string, string | Record<string, string>> | undefined}
|
|
8
|
+
*/
|
|
9
|
+
export function read_package_imports(cwd) {
|
|
10
|
+
const pkg_path = path.resolve(cwd, 'package.json');
|
|
11
|
+
if (fs.existsSync(pkg_path)) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(fs.readFileSync(pkg_path, 'utf-8')).imports;
|
|
14
|
+
} catch {
|
|
15
|
+
// malformed package.json — ignore, the user will see other errors
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Normalizes an import value to a string path, handling both string values
|
|
22
|
+
* and conditional export objects (using the `default` key).
|
|
23
|
+
* @param {string | Record<string, string>} value
|
|
24
|
+
* @returns {string | null}
|
|
25
|
+
*/
|
|
26
|
+
export function normalize_import_value(value) {
|
|
27
|
+
if (typeof value === 'string') {
|
|
28
|
+
return value.replace(/^\.\//, '');
|
|
29
|
+
}
|
|
30
|
+
if (value && typeof value === 'object' && typeof value.default === 'string') {
|
|
31
|
+
return value.default.replace(/^\.\//, '');
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns the `#`-prefixed import keys from the package.json `imports` field.
|
|
38
|
+
* @param {string} cwd
|
|
39
|
+
* @returns {string[]}
|
|
40
|
+
*/
|
|
41
|
+
export function get_hash_import_keys(cwd) {
|
|
42
|
+
const imports = read_package_imports(cwd);
|
|
43
|
+
if (!imports) return [];
|
|
44
|
+
return Object.keys(imports).filter((key) => key.startsWith('#'));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Computes alias entries for `normalize_id` from the package.json `imports` field.
|
|
49
|
+
* Each entry maps a `#xx` alias to its absolute target path. Entries are sorted
|
|
50
|
+
* by path length descending so that longer/more-specific paths are matched first.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} root
|
|
53
|
+
* @param {(p: string) => string} [normalize] - optional path normalizer (e.g. `vite.normalizePath`)
|
|
54
|
+
* @returns {Array<{ alias: string, path: string }>}
|
|
55
|
+
*/
|
|
56
|
+
export function get_import_aliases(root, normalize) {
|
|
57
|
+
const imports = read_package_imports(root);
|
|
58
|
+
if (!imports) return [];
|
|
59
|
+
|
|
60
|
+
/** @type {Array<{ alias: string, path: string }>} */
|
|
61
|
+
const aliases = [];
|
|
62
|
+
|
|
63
|
+
for (const [key, raw_value] of Object.entries(imports)) {
|
|
64
|
+
if (!key.startsWith('#')) continue;
|
|
65
|
+
|
|
66
|
+
const value = normalize_import_value(raw_value);
|
|
67
|
+
if (!value) continue;
|
|
68
|
+
|
|
69
|
+
// For `#xx/*` imports, the target directory is the value without `/*`
|
|
70
|
+
// For `#xx` imports, the target is the file itself
|
|
71
|
+
const target = value.endsWith('/*') ? value.slice(0, -2) : value;
|
|
72
|
+
if (target.includes('*')) continue; // not sure if this is even allowed in Node, anyway it's too niche/complex to support here
|
|
73
|
+
const abs = path.resolve(root, target);
|
|
74
|
+
const alias = key.endsWith('/*') ? key.slice(0, -2) : key;
|
|
75
|
+
|
|
76
|
+
aliases.push({ alias, path: normalize ? normalize(abs) : abs });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Sort by path length descending so longer/more-specific paths match first
|
|
80
|
+
aliases.sort((a, b) => b.path.length - a.path.length);
|
|
81
|
+
|
|
82
|
+
return aliases;
|
|
83
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { mimes, lookup } from 'mrmime';
|
|
2
|
+
|
|
3
|
+
// `mrmime` does not include `.ico` in its database. The IANA-registered
|
|
4
|
+
// type is `image/vnd.microsoft.icon`, but `image/x-icon` is the
|
|
5
|
+
// semi-official type that is universally supported by browsers and other
|
|
6
|
+
// tools, so we use that.
|
|
7
|
+
mimes['ico'] = 'image/x-icon';
|
|
8
|
+
|
|
9
|
+
export { lookup };
|
package/src/utils/page_nodes.js
CHANGED
|
@@ -69,7 +69,7 @@ export class PageNodes {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
get_config() {
|
|
72
|
-
/** @type {any} */
|
|
72
|
+
/** @type {Record<string, any>} */
|
|
73
73
|
let current = {};
|
|
74
74
|
|
|
75
75
|
for (const node of this.data) {
|
|
@@ -77,13 +77,11 @@ export class PageNodes {
|
|
|
77
77
|
|
|
78
78
|
current = {
|
|
79
79
|
...current,
|
|
80
|
-
|
|
81
|
-
...node?.universal?.config
|
|
82
|
-
...node?.server?.config
|
|
80
|
+
...node?.server?.config,
|
|
81
|
+
...node?.universal?.config
|
|
83
82
|
};
|
|
84
83
|
}
|
|
85
84
|
|
|
86
|
-
// TODO 3.0 always return `current`? then we can get rid of `?? {}` in other places
|
|
87
85
|
return Object.keys(current).length ? current : undefined;
|
|
88
86
|
}
|
|
89
87
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {import('types').RouteData[]} routes
|
|
6
|
+
* @returns {Set<string>}
|
|
7
|
+
*/
|
|
8
|
+
export function collect_matcher_names(routes) {
|
|
9
|
+
/** @type {Set<string>} */
|
|
10
|
+
const names = new Set();
|
|
11
|
+
|
|
12
|
+
for (const route of routes) {
|
|
13
|
+
for (const param of route.params) {
|
|
14
|
+
if (param.matcher) names.add(param.matcher);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return names;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {Record<string, unknown>} params
|
|
23
|
+
* @param {Set<string>} names
|
|
24
|
+
* @param {string} [file]
|
|
25
|
+
*/
|
|
26
|
+
export function validate_param_matchers(params, names, file) {
|
|
27
|
+
for (const name of names) {
|
|
28
|
+
if (!(name in params)) {
|
|
29
|
+
throw new Error(`No matcher found for parameter '${name}'${file ? ` in ${file}` : ''}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {{
|
|
36
|
+
* routes: import('types').RouteData[];
|
|
37
|
+
* params_path: string | null;
|
|
38
|
+
* root: string;
|
|
39
|
+
* load?: (file: string) => Promise<Record<string, unknown>>;
|
|
40
|
+
* }} opts
|
|
41
|
+
* @returns {Promise<Record<string, import('@sveltejs/kit').ParamMatcher> | null>}
|
|
42
|
+
*/
|
|
43
|
+
export async function load_and_validate_params({ routes, params_path, root, load }) {
|
|
44
|
+
const names = collect_matcher_names(routes);
|
|
45
|
+
|
|
46
|
+
if (names.size === 0) return null;
|
|
47
|
+
|
|
48
|
+
if (!params_path) {
|
|
49
|
+
throw new Error(`No matcher found for parameter '${names.values().next().value}'`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const file = path.resolve(root, params_path);
|
|
53
|
+
const module = load ? await load(file) : await import(pathToFileURL(file).href);
|
|
54
|
+
|
|
55
|
+
if (!module.params || typeof module.params !== 'object') {
|
|
56
|
+
throw new Error(`${params_path} does not export \`params\` from \`defineParams\``);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
validate_param_matchers(
|
|
60
|
+
/** @type {Record<string, unknown>} */ (module.params),
|
|
61
|
+
names,
|
|
62
|
+
params_path
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return /** @type {Record<string, import('@sveltejs/kit').ParamMatcher>} */ (module.params);
|
|
66
|
+
}
|
package/src/utils/routing.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { BROWSER } from 'esm-env';
|
|
2
|
-
import { decode_params } from './url.js';
|
|
3
2
|
|
|
4
|
-
const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;
|
|
3
|
+
const param_pattern = /^(\[)?(\.\.\.)?([\w-]+)(?:=([\w-]+))?(\])?$/;
|
|
5
4
|
|
|
6
5
|
const root_group_pattern = /^\/\((?:[^)]+)\)$/;
|
|
7
6
|
|
|
@@ -20,7 +19,7 @@ export function parse_route_id(id) {
|
|
|
20
19
|
`^${get_route_segments(id)
|
|
21
20
|
.map((segment) => {
|
|
22
21
|
// special case — /[...rest]/ could contain zero segments
|
|
23
|
-
const rest_match = /^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(segment);
|
|
22
|
+
const rest_match = /^\[\.\.\.([\w-]+)(?:=([\w-]+))?\]$/.exec(segment);
|
|
24
23
|
if (rest_match) {
|
|
25
24
|
params.push({
|
|
26
25
|
name: rest_match[1],
|
|
@@ -32,7 +31,7 @@ export function parse_route_id(id) {
|
|
|
32
31
|
return '(?:/([^]*))?';
|
|
33
32
|
}
|
|
34
33
|
// special case — /[[optional]]/ could contain zero segments
|
|
35
|
-
const optional_match = /^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(segment);
|
|
34
|
+
const optional_match = /^\[\[([\w-]+)(?:=([\w-]+))?\]\]$/.exec(segment);
|
|
36
35
|
if (optional_match) {
|
|
37
36
|
params.push({
|
|
38
37
|
name: optional_match[1],
|
|
@@ -73,7 +72,7 @@ export function parse_route_id(id) {
|
|
|
73
72
|
const match = /** @type {RegExpExecArray} */ (param_pattern.exec(content));
|
|
74
73
|
if (!BROWSER && !match) {
|
|
75
74
|
throw new Error(
|
|
76
|
-
`Invalid param: ${content}. Params and matcher names can only have underscores and alphanumeric characters.`
|
|
75
|
+
`Invalid param: ${content}. Params and matcher names can only have underscores, hyphens, and alphanumeric characters.`
|
|
77
76
|
);
|
|
78
77
|
}
|
|
79
78
|
|
|
@@ -104,7 +103,7 @@ export function parse_route_id(id) {
|
|
|
104
103
|
return { pattern, params };
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
const optional_param_regex = /\/\[\[\w+?(
|
|
106
|
+
const optional_param_regex = /\/\[\[[\w-]+?(?:=[\w-]+)?\]\]/;
|
|
108
107
|
|
|
109
108
|
/**
|
|
110
109
|
* Removes optional params from a route ID.
|
|
@@ -134,13 +133,43 @@ export function get_route_segments(route) {
|
|
|
134
133
|
return route.slice(1).split('/').filter(affects_path);
|
|
135
134
|
}
|
|
136
135
|
|
|
136
|
+
/**
|
|
137
|
+
* @param {import('@sveltejs/kit').ParamMatcher} matcher
|
|
138
|
+
* @param {string} value
|
|
139
|
+
* @returns {{ success: true, value: any } | { success: false }}
|
|
140
|
+
*/
|
|
141
|
+
function run_matcher(matcher, value) {
|
|
142
|
+
const result = matcher['~standard'].validate(value);
|
|
143
|
+
|
|
144
|
+
if (result instanceof Promise) {
|
|
145
|
+
throw new Error('Async param matchers are not supported');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (result.issues) {
|
|
149
|
+
return { success: false };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const parsed = result.value;
|
|
153
|
+
|
|
154
|
+
if (
|
|
155
|
+
typeof parsed !== 'string' &&
|
|
156
|
+
typeof parsed !== 'number' &&
|
|
157
|
+
typeof parsed !== 'boolean' &&
|
|
158
|
+
typeof parsed !== 'bigint'
|
|
159
|
+
) {
|
|
160
|
+
throw new Error('Param matcher must return a string, number, boolean, or bigint');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { success: true, value: parsed };
|
|
164
|
+
}
|
|
165
|
+
|
|
137
166
|
/**
|
|
138
167
|
* @param {RegExpMatchArray} match
|
|
139
168
|
* @param {import('types').RouteParam[]} params
|
|
140
169
|
* @param {Record<string, import('@sveltejs/kit').ParamMatcher>} matchers
|
|
141
170
|
*/
|
|
142
171
|
export function exec(match, params, matchers) {
|
|
143
|
-
/** @type {Record<string,
|
|
172
|
+
/** @type {Record<string, any>} */
|
|
144
173
|
const result = {};
|
|
145
174
|
|
|
146
175
|
const values = match.slice(1);
|
|
@@ -173,37 +202,41 @@ export function exec(match, params, matchers) {
|
|
|
173
202
|
}
|
|
174
203
|
}
|
|
175
204
|
|
|
176
|
-
|
|
177
|
-
result[param.name] = value;
|
|
205
|
+
const decoded = decodeURIComponent(value);
|
|
178
206
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
207
|
+
if (param.matcher) {
|
|
208
|
+
const outcome = run_matcher(matchers[param.matcher], decoded);
|
|
209
|
+
|
|
210
|
+
if (!outcome.success) {
|
|
211
|
+
// in the `/[[a=b]]/...` case, if the value didn't satisfy the matcher,
|
|
212
|
+
// keep track of the number of skipped optional parameters and continue
|
|
213
|
+
if (param.optional && param.chained) {
|
|
214
|
+
buffered++;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
186
217
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
!next_param &&
|
|
190
|
-
!next_value &&
|
|
191
|
-
Object.keys(result).length === values_needing_match.length
|
|
192
|
-
) {
|
|
193
|
-
buffered = 0;
|
|
218
|
+
// otherwise, if the matcher returns `false`, the route did not match
|
|
219
|
+
return;
|
|
194
220
|
}
|
|
195
|
-
|
|
221
|
+
|
|
222
|
+
result[param.name] = outcome.value;
|
|
223
|
+
} else {
|
|
224
|
+
result[param.name] = decoded;
|
|
196
225
|
}
|
|
197
226
|
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
227
|
+
// Now that the params match, reset the buffer if the next param isn't the [...rest]
|
|
228
|
+
// and the next value is defined, otherwise the buffer will cause us to skip values
|
|
229
|
+
const next_param = params[i + 1];
|
|
230
|
+
const next_value = values[i + 1];
|
|
231
|
+
if (next_param && !next_param.rest && next_param.optional && next_value && param.chained) {
|
|
232
|
+
buffered = 0;
|
|
203
233
|
}
|
|
204
234
|
|
|
205
|
-
//
|
|
206
|
-
|
|
235
|
+
// There are no more params and no more values, but all non-empty values have been matched
|
|
236
|
+
if (!next_param && !next_value && Object.keys(result).length === values_needing_match.length) {
|
|
237
|
+
buffered = 0;
|
|
238
|
+
}
|
|
239
|
+
continue;
|
|
207
240
|
}
|
|
208
241
|
|
|
209
242
|
if (buffered) return;
|
|
@@ -227,7 +260,7 @@ function escape(str) {
|
|
|
227
260
|
);
|
|
228
261
|
}
|
|
229
262
|
|
|
230
|
-
const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;
|
|
263
|
+
const basic_param_pattern = /\[(\[)?(\.\.\.)?([\w-]+?)(?:=([\w-]+))?\]\]?/g;
|
|
231
264
|
|
|
232
265
|
/**
|
|
233
266
|
* Populate a route ID with params to resolve a pathname.
|
|
@@ -242,7 +275,7 @@ const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;
|
|
|
242
275
|
* ); // `/blog/hello-world/something/else`
|
|
243
276
|
* ```
|
|
244
277
|
* @param {string} id
|
|
245
|
-
* @param {Record<string,
|
|
278
|
+
* @param {Record<string, import('@sveltejs/kit').ParamValue | undefined>} params
|
|
246
279
|
* @returns {string}
|
|
247
280
|
*/
|
|
248
281
|
export function resolve_route(id, params) {
|
|
@@ -254,20 +287,33 @@ export function resolve_route(id, params) {
|
|
|
254
287
|
segments
|
|
255
288
|
.map((segment) =>
|
|
256
289
|
segment.replace(basic_param_pattern, (_, optional, rest, name) => {
|
|
257
|
-
const
|
|
290
|
+
const value = params[name];
|
|
258
291
|
|
|
259
|
-
|
|
260
|
-
if (!param_value) {
|
|
292
|
+
if (value === undefined || value === '') {
|
|
261
293
|
if (optional) return '';
|
|
262
|
-
if (rest &&
|
|
294
|
+
if (rest && value !== undefined) return '';
|
|
263
295
|
throw new Error(`Missing parameter '${name}' in route ${id}`);
|
|
264
296
|
}
|
|
265
297
|
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
298
|
+
if (typeof value === 'string') {
|
|
299
|
+
if (value.startsWith('/') || value.endsWith('/')) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Parameter '${name}' in route ${id} cannot start or end with a slash -- this would cause an invalid route like foo//bar`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (
|
|
309
|
+
typeof value === 'number' ||
|
|
310
|
+
typeof value === 'boolean' ||
|
|
311
|
+
typeof value === 'bigint'
|
|
312
|
+
) {
|
|
313
|
+
return String(value);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
throw new Error('Parameter values must be a string, number, boolean, or bigint');
|
|
271
317
|
})
|
|
272
318
|
)
|
|
273
319
|
.filter(Boolean)
|
|
@@ -290,7 +336,7 @@ export function has_server_load(node) {
|
|
|
290
336
|
* @param {string} path - The decoded pathname to match
|
|
291
337
|
* @param {Route[]} routes
|
|
292
338
|
* @param {Record<string, import('@sveltejs/kit').ParamMatcher>} matchers
|
|
293
|
-
* @returns {{ route: Route, params: Record<string,
|
|
339
|
+
* @returns {{ route: Route, params: Record<string, any> } | null}
|
|
294
340
|
*/
|
|
295
341
|
export function find_route(path, routes, matchers) {
|
|
296
342
|
for (const route of routes) {
|
|
@@ -301,7 +347,7 @@ export function find_route(path, routes, matchers) {
|
|
|
301
347
|
if (matched) {
|
|
302
348
|
return {
|
|
303
349
|
route,
|
|
304
|
-
params:
|
|
350
|
+
params: matched
|
|
305
351
|
};
|
|
306
352
|
}
|
|
307
353
|
}
|
|
@@ -52,6 +52,11 @@ export class SharedIterator {
|
|
|
52
52
|
/** @type {unknown} */
|
|
53
53
|
#terminal_error = undefined;
|
|
54
54
|
|
|
55
|
+
/** Whether `done()` or `fail()` has been broadcast. */
|
|
56
|
+
get closed() {
|
|
57
|
+
return this.#closed;
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
/**
|
|
56
61
|
* @param {(instance: SharedIterator<T>) => (() => void)} [start]
|
|
57
62
|
*/
|