@sveltejs/kit 3.0.0-next.2 → 3.0.0-next.21

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.
Files changed (195) hide show
  1. package/package.json +62 -36
  2. package/src/cli.js +15 -12
  3. package/src/constants.js +6 -1
  4. package/src/core/adapt/builder.js +66 -72
  5. package/src/core/adapt/index.js +17 -6
  6. package/src/core/config/index.js +134 -80
  7. package/src/core/config/options.js +295 -273
  8. package/src/core/config/types.d.ts +1 -1
  9. package/src/core/env.js +146 -16
  10. package/src/core/generate_manifest/index.js +18 -15
  11. package/src/core/postbuild/analyse.js +8 -20
  12. package/src/core/postbuild/crawl.js +22 -6
  13. package/src/core/postbuild/entities.js +8 -2
  14. package/src/core/postbuild/fallback.js +4 -2
  15. package/src/core/postbuild/prerender.js +222 -70
  16. package/src/core/postbuild/queue.js +2 -1
  17. package/src/core/sync/create_manifest_data/conflict.js +1 -1
  18. package/src/core/sync/create_manifest_data/index.js +82 -62
  19. package/src/core/sync/sync.js +40 -31
  20. package/src/core/sync/ts.js +1 -1
  21. package/src/core/sync/utils.js +22 -2
  22. package/src/core/sync/{write_non_ambient.js → write_app_types.js} +107 -58
  23. package/src/core/sync/write_client_manifest.js +14 -29
  24. package/src/core/sync/write_env.js +7 -5
  25. package/src/core/sync/write_server.js +37 -45
  26. package/src/core/sync/write_tsconfig/index.js +274 -0
  27. package/src/core/sync/write_tsconfig/test-app/package.json +7 -0
  28. package/src/core/sync/write_tsconfig/utils.js +77 -0
  29. package/src/core/sync/write_tsconfig/validate.js +128 -0
  30. package/src/core/sync/write_types/index.js +127 -123
  31. package/src/core/utils.js +30 -5
  32. package/src/exports/env/index.js +77 -0
  33. package/src/exports/env/public.d.ts +55 -0
  34. package/src/exports/hooks/index.js +3 -9
  35. package/src/exports/hooks/public.d.ts +195 -0
  36. package/src/exports/hooks/sequence.js +9 -6
  37. package/src/exports/index.js +52 -29
  38. package/src/exports/internal/client.js +5 -0
  39. package/src/exports/internal/env.js +8 -5
  40. package/src/exports/internal/index.js +1 -90
  41. package/src/exports/internal/{event.js → server/event.js} +2 -3
  42. package/src/exports/internal/server/index.js +37 -0
  43. package/src/exports/internal/server/telemetry.js +95 -0
  44. package/src/exports/internal/shared.js +90 -0
  45. package/src/exports/node/index.js +64 -22
  46. package/src/exports/params/index.js +70 -0
  47. package/src/exports/params/public.d.ts +63 -0
  48. package/src/exports/public.d.ts +50 -1733
  49. package/src/exports/remote/index.js +11 -0
  50. package/src/exports/remote/public.d.ts +519 -0
  51. package/src/exports/url.js +86 -0
  52. package/src/exports/vite/build/build_server.js +54 -65
  53. package/src/exports/vite/build/remote.js +24 -19
  54. package/src/exports/vite/build/utils.js +0 -8
  55. package/src/exports/vite/dev/index.js +218 -146
  56. package/src/exports/vite/index.js +1590 -858
  57. package/src/exports/vite/module_ids.js +2 -2
  58. package/src/exports/vite/preview/index.js +40 -32
  59. package/src/exports/vite/public.d.ts +588 -0
  60. package/src/exports/vite/utils.js +84 -46
  61. package/src/pathname.js +55 -0
  62. package/src/runner.js +15 -0
  63. package/src/runtime/app/env/internal.js +4 -4
  64. package/src/runtime/app/env/types.d.ts +1 -1
  65. package/src/runtime/app/environment/index.js +3 -3
  66. package/src/runtime/app/{forms.js → forms/index.js} +73 -42
  67. package/src/runtime/app/forms/public.d.ts +2 -0
  68. package/src/runtime/app/forms/types.d.ts +56 -0
  69. package/src/runtime/app/internal/transport.js +53 -0
  70. package/src/runtime/app/manifest/index.js +1 -0
  71. package/src/runtime/app/{navigation.js → navigation/index.js} +2 -1
  72. package/src/runtime/app/navigation/public.d.ts +237 -0
  73. package/src/runtime/app/paths/client.js +37 -37
  74. package/src/runtime/app/paths/index.js +1 -1
  75. package/src/runtime/app/paths/internal/client.js +34 -2
  76. package/src/runtime/app/paths/internal/server.js +6 -23
  77. package/src/runtime/app/paths/internal.d.ts +3 -0
  78. package/src/runtime/app/paths/public.d.ts +1 -29
  79. package/src/runtime/app/paths/server.js +36 -17
  80. package/src/runtime/app/paths/types.d.ts +11 -19
  81. package/src/runtime/app/server/index.js +2 -2
  82. package/src/runtime/app/server/public.d.ts +201 -0
  83. package/src/runtime/app/server/remote/command.js +13 -11
  84. package/src/runtime/app/server/remote/form.js +61 -39
  85. package/src/runtime/app/server/remote/prerender.js +45 -45
  86. package/src/runtime/app/server/remote/query.js +106 -108
  87. package/src/runtime/app/server/remote/requested.js +27 -19
  88. package/src/runtime/app/server/remote/shared.js +76 -76
  89. package/src/runtime/app/service-worker/index.js +24 -0
  90. package/src/runtime/app/state/client.js +4 -2
  91. package/src/runtime/app/state/index.js +7 -5
  92. package/src/runtime/app/state/public.d.ts +72 -0
  93. package/src/runtime/app/state/server.js +3 -0
  94. package/src/runtime/app/stores.js +15 -78
  95. package/src/runtime/client/bundle.js +1 -1
  96. package/src/runtime/client/client-entry.js +3 -0
  97. package/src/runtime/client/client.js +1336 -648
  98. package/src/runtime/client/constants.js +3 -6
  99. package/src/runtime/client/entry.js +24 -3
  100. package/src/runtime/client/fetcher.js +25 -25
  101. package/src/runtime/client/ndjson.js +1 -1
  102. package/src/runtime/client/parse.js +1 -1
  103. package/src/runtime/client/payload.js +17 -0
  104. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  105. package/src/runtime/client/remote-functions/command.svelte.js +37 -61
  106. package/src/runtime/client/remote-functions/form.svelte.js +267 -178
  107. package/src/runtime/client/remote-functions/prerender.svelte.js +32 -11
  108. package/src/runtime/client/remote-functions/query/index.js +10 -17
  109. package/src/runtime/client/remote-functions/query/instance.svelte.js +64 -18
  110. package/src/runtime/client/remote-functions/query/proxy.js +5 -5
  111. package/src/runtime/client/remote-functions/query-batch.svelte.js +61 -70
  112. package/src/runtime/client/remote-functions/query-live/index.js +1 -1
  113. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +63 -21
  114. package/src/runtime/client/remote-functions/query-live/iterator.js +15 -12
  115. package/src/runtime/client/remote-functions/query-live/proxy.js +2 -12
  116. package/src/runtime/client/remote-functions/shared.svelte.js +90 -65
  117. package/src/runtime/client/snapshots.js +147 -0
  118. package/src/runtime/client/state.svelte.js +94 -55
  119. package/src/runtime/client/stream.js +3 -2
  120. package/src/runtime/client/types.d.ts +13 -9
  121. package/src/runtime/client/utils.js +22 -110
  122. package/src/runtime/components/root.svelte +56 -0
  123. package/src/runtime/env/dynamic/private.js +7 -0
  124. package/src/runtime/env/dynamic/public.js +7 -0
  125. package/src/runtime/env/static/private.js +6 -0
  126. package/src/runtime/env/static/public.js +6 -0
  127. package/src/runtime/form-utils.js +172 -61
  128. package/src/runtime/pathname.js +20 -32
  129. package/src/runtime/props.svelte.js +72 -0
  130. package/src/runtime/server/constants.js +0 -3
  131. package/src/runtime/server/cookie.js +72 -55
  132. package/src/runtime/server/csrf.js +65 -0
  133. package/src/runtime/server/data/index.js +49 -53
  134. package/src/runtime/server/dev.js +22 -0
  135. package/src/runtime/server/endpoint.js +6 -7
  136. package/src/runtime/server/env_module.js +0 -5
  137. package/src/runtime/server/errors.js +160 -0
  138. package/src/runtime/server/fetch.js +32 -39
  139. package/src/runtime/server/index.js +90 -64
  140. package/src/runtime/server/internal.js +71 -0
  141. package/src/runtime/server/page/actions.js +85 -64
  142. package/src/runtime/server/page/crypto.js +2 -2
  143. package/src/runtime/server/page/csp.js +88 -104
  144. package/src/runtime/server/page/data_serializer.js +24 -25
  145. package/src/runtime/server/page/index.js +36 -53
  146. package/src/runtime/server/page/load_data.js +50 -57
  147. package/src/runtime/server/page/render.js +173 -239
  148. package/src/runtime/server/page/respond_with_error.js +17 -31
  149. package/src/runtime/server/page/serialize_data.js +2 -13
  150. package/src/runtime/server/page/server_routing.js +85 -26
  151. package/src/runtime/server/remote-functions.js +640 -0
  152. package/src/runtime/server/respond.js +190 -131
  153. package/src/runtime/server/sourcemaps.js +183 -0
  154. package/src/runtime/server/state.js +53 -0
  155. package/src/runtime/server/utils.js +13 -155
  156. package/src/runtime/shared.js +20 -40
  157. package/src/runtime/utils.js +3 -0
  158. package/src/types/ambient-private.d.ts +11 -1
  159. package/src/types/ambient.d.ts +87 -36
  160. package/src/types/global-private.d.ts +25 -25
  161. package/src/types/internal.d.ts +163 -157
  162. package/src/types/private.d.ts +41 -1
  163. package/src/utils/error.js +28 -4
  164. package/src/utils/escape.js +9 -25
  165. package/src/utils/features.js +1 -1
  166. package/src/utils/filesystem.js +1 -23
  167. package/src/utils/fork.js +7 -2
  168. package/src/utils/hash.js +21 -0
  169. package/src/utils/http.js +8 -7
  170. package/src/utils/import.js +9 -2
  171. package/src/utils/imports.js +83 -0
  172. package/src/utils/mime.js +9 -0
  173. package/src/utils/page_nodes.js +6 -7
  174. package/src/utils/params.js +67 -0
  175. package/src/utils/regex.js +9 -0
  176. package/src/utils/routing.js +145 -73
  177. package/src/utils/shared-iterator.js +5 -0
  178. package/src/utils/streaming.js +14 -4
  179. package/src/utils/url.js +33 -2
  180. package/src/version.js +1 -1
  181. package/types/index.d.ts +2616 -2433
  182. package/types/index.d.ts.map +120 -106
  183. package/src/core/sync/write_ambient.js +0 -18
  184. package/src/core/sync/write_root.js +0 -148
  185. package/src/core/sync/write_tsconfig.js +0 -250
  186. package/src/exports/internal/server.js +0 -22
  187. package/src/exports/node/polyfills.js +0 -30
  188. package/src/runtime/server/app.js +0 -9
  189. package/src/runtime/server/remote.js +0 -457
  190. package/src/runtime/shared-server.js +0 -7
  191. package/src/runtime/telemetry/otel.js +0 -21
  192. package/src/runtime/telemetry/record_span.js +0 -65
  193. package/src/types/synthetic/$lib.md +0 -5
  194. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
  195. /package/src/{runtime/telemetry/noop.js → telemetry.js} +0 -0
@@ -0,0 +1,95 @@
1
+ /** @import { Tracer, SpanStatusCode, PropagationAPI, ContextAPI } from '@opentelemetry/api' */
2
+ /** @import { RecordSpan } from 'types' */
3
+ import { HttpError, Redirect } from '../shared.js';
4
+ import { noop_span } from '../../../telemetry.js';
5
+
6
+ // Import this module by its bare specifier so bundled and external code share its state.
7
+
8
+ /** @type {Promise<{ tracer: Tracer, SpanStatusCode: typeof SpanStatusCode, propagation: PropagationAPI, context: ContextAPI }> | null} */
9
+ export let otel = null;
10
+
11
+ /**
12
+ * The caller passes in `import('@opentelemetry/api')` so the import lives behind
13
+ * `__SVELTEKIT_SERVER_TRACING_ENABLED__` in the bundled runtime and is eliminated
14
+ * from builds with tracing disabled, where the package may not be installed.
15
+ * @param {Promise<typeof import('@opentelemetry/api')>} api
16
+ * @returns {void}
17
+ */
18
+ export function init_tracing(api) {
19
+ otel ??= api
20
+ .then((module) => {
21
+ return {
22
+ tracer: module.trace.getTracer('sveltekit'),
23
+ propagation: module.propagation,
24
+ context: module.context,
25
+ SpanStatusCode: module.SpanStatusCode
26
+ };
27
+ })
28
+ .catch(() => {
29
+ throw new Error(
30
+ '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'
31
+ );
32
+ });
33
+ }
34
+
35
+ /** @type {RecordSpan} */
36
+ export async function record_span({ name, attributes, fn }) {
37
+ if (otel === null) {
38
+ return fn(noop_span);
39
+ }
40
+
41
+ const { SpanStatusCode, tracer } = await otel;
42
+
43
+ return tracer.startActiveSpan(name, { attributes }, async (span) => {
44
+ try {
45
+ return await fn(span);
46
+ } catch (error) {
47
+ if (error instanceof HttpError) {
48
+ span.setAttributes({
49
+ [`${name}.result.type`]: 'known_error',
50
+ [`${name}.result.status`]: error.status,
51
+ [`${name}.result.message`]: error.body.message
52
+ });
53
+ if (error.status >= 500) {
54
+ span.recordException({
55
+ name: 'HttpError',
56
+ message: error.body.message
57
+ });
58
+ span.setStatus({
59
+ code: SpanStatusCode.ERROR,
60
+ message: error.body.message
61
+ });
62
+ }
63
+ } else if (error instanceof Redirect) {
64
+ span.setAttributes({
65
+ [`${name}.result.type`]: 'redirect',
66
+ [`${name}.result.status`]: error.status,
67
+ [`${name}.result.location`]: error.location
68
+ });
69
+ } else if (error instanceof Error) {
70
+ span.setAttributes({
71
+ [`${name}.result.type`]: 'unknown_error'
72
+ });
73
+ span.recordException({
74
+ name: error.name,
75
+ message: error.message,
76
+ // conditional so this compiles under consumers' `exactOptionalPropertyTypes`
77
+ ...(error.stack !== undefined && { stack: error.stack })
78
+ });
79
+ span.setStatus({
80
+ code: SpanStatusCode.ERROR,
81
+ message: error.message
82
+ });
83
+ } else {
84
+ span.setAttributes({
85
+ [`${name}.result.type`]: 'unknown_error'
86
+ });
87
+ span.setStatus({ code: SpanStatusCode.ERROR });
88
+ }
89
+
90
+ throw error;
91
+ } finally {
92
+ span.end();
93
+ }
94
+ });
95
+ }
@@ -0,0 +1,90 @@
1
+ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
2
+
3
+ export class HttpError {
4
+ /**
5
+ * @param {App.Error} error
6
+ */
7
+ constructor(error) {
8
+ this.status = error.status;
9
+ this.body = error;
10
+ }
11
+
12
+ toString() {
13
+ return JSON.stringify(this.body);
14
+ }
15
+ }
16
+
17
+ /**
18
+ * An `HttpError` whose body is already in its final, user-facing form — either produced by the
19
+ * `handleError` hook on the server and reconstructed here from the response, or authored directly
20
+ * by the client runtime. Unlike a plain `HttpError` (which represents a fresh `error(...)` call
21
+ * that the hook has yet to see), `handleError` must not run on it.
22
+ * @extends HttpError
23
+ */
24
+ export class HandledHttpError extends HttpError {}
25
+
26
+ export class Redirect {
27
+ /**
28
+ * @param {300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308} status
29
+ * @param {string} location
30
+ */
31
+ constructor(status, location) {
32
+ try {
33
+ new Headers({ location });
34
+ } catch {
35
+ throw new Error(
36
+ `Invalid redirect location ${JSON.stringify(location)}: ` +
37
+ 'this string contains characters that cannot be used in HTTP headers'
38
+ );
39
+ }
40
+
41
+ this.status = status;
42
+ this.location = location;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * An error that was thrown from within the SvelteKit runtime that is not fatal and doesn't result in a 500, such as a 404.
48
+ * `SvelteKitError` goes through `handleError`.
49
+ * @extends Error
50
+ */
51
+ export class SvelteKitError extends Error {
52
+ /**
53
+ * @param {number} status
54
+ * @param {string} text
55
+ * @param {string} message
56
+ */
57
+ constructor(status, text, message) {
58
+ super(message);
59
+ this.status = status;
60
+ this.text = text;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @template [T=undefined]
66
+ */
67
+ export class ActionFailure {
68
+ /**
69
+ * @param {number} status
70
+ * @param {T} data
71
+ */
72
+ constructor(status, data) {
73
+ this.status = status;
74
+ this.data = data;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Error thrown when form validation fails imperatively
80
+ */
81
+ export class ValidationError extends Error {
82
+ /**
83
+ * @param {StandardSchemaV1.Issue[]} issues
84
+ */
85
+ constructor(issues) {
86
+ super('Validation failed');
87
+ this.name = 'ValidationError';
88
+ this.issues = issues;
89
+ }
90
+ }
@@ -1,8 +1,11 @@
1
1
  import { createReadStream } from 'node:fs';
2
2
  import { Readable } from 'node:stream';
3
- import { SvelteKitError } from '../internal/index.js';
3
+ import { SvelteKitError } from '../internal/shared.js';
4
4
  import { noop } from '../../utils/functions.js';
5
5
 
6
+ /** @type {WeakMap<import('http').IncomingMessage, (chunk: Buffer) => void>} */
7
+ const body_data_listeners = new WeakMap();
8
+
6
9
  /**
7
10
  * @param {import('http').IncomingMessage} req
8
11
  * @param {number} [body_size_limit]
@@ -47,17 +50,19 @@ function get_raw_body(req, body_size_limit) {
47
50
  return;
48
51
  }
49
52
 
50
- req.on('error', (error) => {
53
+ /** @param {Error} error */
54
+ const on_error = (error) => {
51
55
  cancelled = true;
52
56
  controller.error(error);
53
- });
57
+ };
54
58
 
55
- req.on('end', () => {
59
+ const on_end = () => {
56
60
  if (cancelled) return;
57
61
  controller.close();
58
- });
62
+ };
59
63
 
60
- req.on('data', (chunk) => {
64
+ /** @param {Buffer} chunk */
65
+ const on_data = (chunk) => {
61
66
  if (cancelled) return;
62
67
 
63
68
  size += chunk.length;
@@ -89,7 +94,12 @@ function get_raw_body(req, body_size_limit) {
89
94
  if (controller.desiredSize === null || controller.desiredSize <= 0) {
90
95
  req.pause();
91
96
  }
92
- });
97
+ };
98
+
99
+ req.on('error', on_error);
100
+ req.on('end', on_end);
101
+ req.on('data', on_data);
102
+ body_data_listeners.set(req, on_data);
93
103
  },
94
104
 
95
105
  pull() {
@@ -109,11 +119,9 @@ function get_raw_body(req, body_size_limit) {
109
119
  * base: string;
110
120
  * bodySizeLimit?: number;
111
121
  * }} options
112
- * @returns {Promise<Request>}
122
+ * @returns {Request}
113
123
  */
114
- // TODO 3.0 make the signature synchronous?
115
- // eslint-disable-next-line @typescript-eslint/require-await
116
- export async function getRequest({ request, base, bodySizeLimit }) {
124
+ export function getRequest({ request, base, bodySizeLimit }) {
117
125
  let headers = /** @type {Record<string, string>} */ (request.headers);
118
126
  if (request.httpVersionMajor >= 2) {
119
127
  // the Request constructor rejects headers with ':' in the name
@@ -129,14 +137,9 @@ export async function getRequest({ request, base, bodySizeLimit }) {
129
137
  }
130
138
 
131
139
  const controller = new AbortController();
132
- // TODO: Whenever Node >=22.17 is the minimum supported version, we can do `if (request.readableAborted) controller.abort()` instead
133
- // see https://github.com/nodejs/node/blob/5cf3c3e24c7257a0c6192ed8ef71efec8ddac22b/lib/internal/streams/readable.js#L1443-L1453
134
- let errored = false;
135
- let end_emitted = false;
136
- request.once('error', () => (errored = true));
137
- request.once('end', () => (end_emitted = true));
140
+
138
141
  request.once('close', () => {
139
- if ((errored || request.destroyed) && !end_emitted) {
142
+ if (request.readableAborted) {
140
143
  controller.abort();
141
144
  }
142
145
  });
@@ -154,14 +157,53 @@ export async function getRequest({ request, base, bodySizeLimit }) {
154
157
  });
155
158
  }
156
159
 
160
+ /**
161
+ * Drains any unconsumed request body once the response has been sent. When a
162
+ * route doesn't read the request body (for example a page route receiving a
163
+ * POST), the unread bytes remain buffered in the socket. On keep-alive
164
+ * connections Node's HTTP parser then reads those leftover bytes as the next
165
+ * request, fails to parse them, and resets the connection — losing any
166
+ * pipelined request. Resuming the request discards the bytes so the connection
167
+ * stays usable.
168
+ *
169
+ * Because `get_raw_body` attaches a `data` listener, Node marks the request as
170
+ * being consumed (`req._consuming`) and skips its own automatic drain, so we
171
+ * have to do it ourselves. The whole remaining body is read and discarded; this
172
+ * is the intended trade-off (keeping the connection reusable) over destroying it.
173
+ * @see https://github.com/sveltejs/kit/issues/14916
174
+ * @see https://github.com/sveltejs/kit/issues/15526
175
+ * @param {import('http').ServerResponse} res
176
+ */
177
+ function drain_request(res) {
178
+ const req = res.req;
179
+ if (!req || req.readableEnded || req.destroyed) return;
180
+
181
+ // When the body went unread, get_raw_body's `data` listener is still attached
182
+ // and enqueues into a ReadableStream nobody consumes; it pauses the request at
183
+ // the high water mark, so one chunk sits in memory and the rest stays buffered
184
+ // in the socket. Remove only that `data` listener so the resumed stream drops
185
+ // the remaining bytes instead of re-buffering them. The `end` and `error`
186
+ // listeners stay attached so the body's ReadableStream is still closed (or
187
+ // errored) once draining completes, and a consumer that stopped reading
188
+ // mid-body sees a clean end instead of hanging.
189
+ const on_data = body_data_listeners.get(req);
190
+ if (on_data) {
191
+ req.removeListener('data', on_data);
192
+ body_data_listeners.delete(req);
193
+ }
194
+
195
+ req.resume();
196
+ }
197
+
157
198
  /**
158
199
  * @param {import('http').ServerResponse} res
159
200
  * @param {Response} response
160
- * @returns {Promise<void>}
201
+ * @returns {void}
161
202
  */
162
- // TODO 3.0 make the signature synchronous?
163
- // eslint-disable-next-line @typescript-eslint/require-await
164
- export async function setResponse(res, response) {
203
+ export function setResponse(res, response) {
204
+ res.once('finish', () => drain_request(res));
205
+ res.once('close', () => drain_request(res));
206
+
165
207
  for (const [key, value] of response.headers) {
166
208
  try {
167
209
  res.setHeader(key, key === 'set-cookie' ? response.headers.getSetCookie() : value);
@@ -0,0 +1,70 @@
1
+ /** @import { ParamDefinition, ParamMatcher } from '@sveltejs/kit/params' */
2
+
3
+ /**
4
+ * Define [parameter matchers](https://svelte.dev/docs/kit/advanced-routing#Matching) for your app.
5
+ *
6
+ * @example
7
+ * ```js
8
+ * import { defineParams } from '@sveltejs/kit/params';
9
+ * import * as v from 'valibot';
10
+ *
11
+ * export const params = defineParams({
12
+ * locale: (param) => {
13
+ * if (param !== 'de' && param !== 'en') return;
14
+ * return param;
15
+ * },
16
+ * number: v.pipe(v.string(), v.toNumber())
17
+ * });
18
+ * ```
19
+ *
20
+ * @template {Record<string, import('./public.js').ParamDefinition>} T
21
+ * @param {T} definitions
22
+ * @returns {import('./public.js').DefinedParams<T>}
23
+ */
24
+ export function defineParams(definitions) {
25
+ /** @type {Record<string, import('./public.js').ParamMatcher>} */
26
+ const matchers = {};
27
+
28
+ for (const [key, definition] of Object.entries(definitions)) {
29
+ matchers[key] = normalize_param_definition(definition);
30
+ }
31
+
32
+ return /** @type {import('./public.js').DefinedParams<T>} */ (matchers);
33
+ }
34
+
35
+ /**
36
+ * @param {ParamDefinition} definition
37
+ * @returns {ParamMatcher}
38
+ */
39
+ export function normalize_param_definition(definition) {
40
+ // standard schemas can be callable (e.g. ArkType), so this must be checked before the function case
41
+ if (
42
+ definition &&
43
+ (typeof definition === 'object' || typeof definition === 'function') &&
44
+ '~standard' in definition
45
+ ) {
46
+ return definition;
47
+ }
48
+
49
+ if (typeof definition === 'function') {
50
+ return /** @type {ParamMatcher} */ (
51
+ /** @type {unknown} */ ({
52
+ '~standard': {
53
+ validate(/** @type {unknown} */ value) {
54
+ const result = definition(/** @type {string} */ (value));
55
+
56
+ if (result === undefined) {
57
+ return { issues: [{ message: 'Invalid param' }] };
58
+ }
59
+
60
+ if (/** @type {any} */ (result) instanceof Promise) return result; // will be validated and rejected upstream
61
+
62
+ return { value: result };
63
+ }
64
+ }
65
+ })
66
+ );
67
+ }
68
+
69
+ throw new Error('Invalid param definition');
70
+ }
@@ -0,0 +1,63 @@
1
+ import { StandardSchemaV1 } from '@standard-schema/spec';
2
+
3
+ /**
4
+ * The shape of a param matcher. See [matching](https://svelte.dev/docs/kit/advanced-routing#Matching) for more info.
5
+ */
6
+ export type ParamMatcher<Output = any> = StandardSchemaV1<string, Output>;
7
+
8
+ /**
9
+ * A value that can be parsed from a URL param and losslessly encoded with `String(...)`.
10
+ */
11
+ export type ParamValue = string | number | boolean | bigint;
12
+
13
+ /**
14
+ * A param matcher definition passed to [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit-params#defineParams).
15
+ */
16
+ export type ParamDefinition =
17
+ | ((param: string) => ParamValue | undefined)
18
+ | StandardSchemaV1<string, ParamValue>;
19
+
20
+ /**
21
+ * The return type of [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit-params#defineParams).
22
+ */
23
+ export type DefinedParams<T extends Record<string, ParamDefinition>> = {
24
+ readonly [K in keyof T]: ParamEntry<T[K]>;
25
+ };
26
+
27
+ /**
28
+ * Normalizes a property of defineParams (schema or function) to standard schema.
29
+ */
30
+ type ParamEntry<M> =
31
+ M extends StandardSchemaV1<any, any>
32
+ ? StandardSchemaV1.InferOutput<M> extends ParamValue
33
+ ? StandardSchemaV1<any, M>
34
+ : StandardSchemaV1<any, never>
35
+ : M extends (param: string) => infer R
36
+ ? Exclude<R, undefined> extends ParamValue
37
+ ? StandardSchemaV1<any, Exclude<R, undefined>>
38
+ : StandardSchemaV1<any, never>
39
+ : never;
40
+
41
+ /**
42
+ * Extracts the param type from a matcher.
43
+ */
44
+ export type MatcherParam<M extends StandardSchemaV1<any, any>> =
45
+ M extends StandardSchemaV1<any, infer Inner>
46
+ ? Inner extends ParamValue
47
+ ? Inner
48
+ : Inner extends StandardSchemaV1<any, any>
49
+ ? StandardSchemaV1.InferOutput<Inner> extends ParamValue
50
+ ? StandardSchemaV1.InferOutput<Inner>
51
+ : never
52
+ : never
53
+ : never;
54
+
55
+ /**
56
+ * Define [parameter matchers](https://svelte.dev/docs/kit/advanced-routing#Matching) for your app.
57
+ *
58
+ * @template T
59
+ * @param definitions
60
+ */
61
+ export function defineParams<T extends Record<string, ParamDefinition>>(
62
+ definitions: T
63
+ ): DefinedParams<T>;