@sveltejs/kit 3.0.0-next.10 → 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.
Files changed (51) hide show
  1. package/package.json +1 -1
  2. package/src/constants.js +2 -0
  3. package/src/core/adapt/builder.js +3 -6
  4. package/src/core/config/index.js +6 -1
  5. package/src/core/config/options.js +11 -69
  6. package/src/core/postbuild/fallback.js +2 -1
  7. package/src/core/postbuild/prerender.js +75 -28
  8. package/src/core/postbuild/queue.js +2 -1
  9. package/src/core/sync/write_server.js +2 -2
  10. package/src/core/utils.js +28 -3
  11. package/src/exports/env/index.js +68 -3
  12. package/src/exports/internal/env.js +6 -3
  13. package/src/exports/params.js +9 -4
  14. package/src/exports/public.d.ts +41 -20
  15. package/src/exports/vite/dev/index.js +50 -6
  16. package/src/exports/vite/index.js +130 -125
  17. package/src/exports/vite/module_ids.js +0 -2
  18. package/src/exports/vite/preview/index.js +3 -2
  19. package/src/exports/vite/utils.js +20 -0
  20. package/src/runtime/app/paths/server.js +1 -1
  21. package/src/runtime/app/server/index.js +1 -1
  22. package/src/runtime/app/server/remote/query.js +1 -1
  23. package/src/runtime/client/ndjson.js +1 -1
  24. package/src/runtime/client/stream.js +3 -2
  25. package/src/runtime/server/data/index.js +1 -1
  26. package/src/runtime/server/errors.js +135 -0
  27. package/src/runtime/server/fetch.js +1 -1
  28. package/src/runtime/server/index.js +20 -10
  29. package/src/runtime/server/internal.js +25 -0
  30. package/src/runtime/server/page/actions.js +2 -1
  31. package/src/runtime/server/page/data_serializer.js +10 -10
  32. package/src/runtime/server/page/index.js +3 -2
  33. package/src/runtime/server/page/load_data.js +1 -1
  34. package/src/runtime/server/page/render.js +3 -7
  35. package/src/runtime/server/page/respond_with_error.js +6 -5
  36. package/src/runtime/server/{remote.js → remote-functions.js} +1 -1
  37. package/src/runtime/server/respond.js +3 -7
  38. package/src/runtime/server/sourcemaps.js +183 -0
  39. package/src/runtime/server/utils.js +2 -157
  40. package/src/types/ambient-private.d.ts +2 -0
  41. package/src/types/global-private.d.ts +0 -5
  42. package/src/types/internal.d.ts +6 -6
  43. package/src/types/private.d.ts +8 -0
  44. package/src/utils/escape.js +9 -25
  45. package/src/utils/features.js +1 -1
  46. package/src/utils/fork.js +7 -2
  47. package/src/utils/page_nodes.js +3 -5
  48. package/src/version.js +1 -1
  49. package/types/index.d.ts +72 -22
  50. package/types/index.d.ts.map +3 -1
  51. package/src/runtime/shared-server.js +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "3.0.0-next.10",
3
+ "version": "3.0.0-next.11",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
package/src/constants.js CHANGED
@@ -11,3 +11,5 @@ export const ENDPOINT_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIO
11
11
  export const MUTATIVE_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE'];
12
12
 
13
13
  export const PAGE_METHODS = ['GET', 'POST', 'HEAD'];
14
+
15
+ export const SRC_ROOT = import.meta.dirname;
@@ -8,7 +8,7 @@ import * as devalue from 'devalue';
8
8
  import { createReadStream, createWriteStream, existsSync, statSync } from 'node:fs';
9
9
  import { extname, resolve, join, dirname, relative } from 'node:path';
10
10
  import { pipeline } from 'node:stream';
11
- import { promisify, styleText } from 'node:util';
11
+ import { promisify } from 'node:util';
12
12
  import zlib from 'node:zlib';
13
13
  import { copy, rimraf, mkdirp } from '../../utils/filesystem.js';
14
14
  import { posixify } from '../../utils/os.js';
@@ -142,11 +142,8 @@ export function create_builder({
142
142
  });
143
143
 
144
144
  if (existsSync(dest)) {
145
- console.log(
146
- styleText(
147
- ['bold', 'yellow'],
148
- `Overwriting ${dest} with fallback page. Consider using a different name for the fallback.`
149
- )
145
+ log.warn(
146
+ `\nOverwriting ${dest} with fallback page. Consider using a different name for the fallback.\n`
150
147
  );
151
148
  }
152
149
 
@@ -6,6 +6,7 @@ import fs from 'node:fs';
6
6
  import path from 'node:path';
7
7
  import process from 'node:process';
8
8
  import * as url from 'node:url';
9
+ import { styleText } from 'node:util';
9
10
  import {
10
11
  validate_kit_options,
11
12
  kit_options,
@@ -223,6 +224,10 @@ export function validate_config(config) {
223
224
  return validated;
224
225
  } catch (e) {
225
226
  const error = /** @type {Error} */ (e);
226
- throw stackless(`Error loading SvelteKit options from Vite config: ${error.message}`);
227
+
228
+ // Print a nicer version of the error to the console
229
+ console.log(styleText(['bold', 'red'], `\n${error.message}\n`));
230
+
231
+ throw stackless('Failed to load SvelteKit options from Vite config');
227
232
  }
228
233
  }
@@ -60,6 +60,12 @@ export const validate_svelte_options = object(
60
60
  true
61
61
  );
62
62
 
63
+ const prerender_handler = validate(undefined, (input, keypath) => {
64
+ if (typeof input === 'function') return input;
65
+ if (['fail', 'warn', 'ignore'].includes(input)) return input;
66
+ throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
67
+ });
68
+
63
69
  /** @type {Validator<ValidatedKitConfig>} */
64
70
  export const validate_kit_options = object({
65
71
  adapter: validate(undefined, (input, keypath) => {
@@ -247,75 +253,11 @@ export const validate_kit_options = object({
247
253
  return input;
248
254
  }),
249
255
 
250
- handleHttpError: validate(
251
- (/** @type {any} */ { message }) => {
252
- throw new Error(
253
- message +
254
- '\nTo suppress or handle this error, implement `handleHttpError` in https://svelte.dev/docs/kit/configuration#prerender'
255
- );
256
- },
257
- (input, keypath) => {
258
- if (typeof input === 'function') return input;
259
- if (['fail', 'warn', 'ignore'].includes(input)) return input;
260
- throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
261
- }
262
- ),
263
-
264
- handleMissingId: validate(
265
- (/** @type {any} */ { message }) => {
266
- throw new Error(
267
- message +
268
- '\nTo suppress or handle this error, implement `handleMissingId` in https://svelte.dev/docs/kit/configuration#prerender'
269
- );
270
- },
271
- (input, keypath) => {
272
- if (typeof input === 'function') return input;
273
- if (['fail', 'warn', 'ignore'].includes(input)) return input;
274
- throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
275
- }
276
- ),
277
-
278
- handleEntryGeneratorMismatch: validate(
279
- (/** @type {any} */ { message }) => {
280
- throw new Error(
281
- message +
282
- '\nTo suppress or handle this error, implement `handleEntryGeneratorMismatch` in https://svelte.dev/docs/kit/configuration#prerender'
283
- );
284
- },
285
- (input, keypath) => {
286
- if (typeof input === 'function') return input;
287
- if (['fail', 'warn', 'ignore'].includes(input)) return input;
288
- throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
289
- }
290
- ),
291
-
292
- handleUnseenRoutes: validate(
293
- (/** @type {any} */ { message }) => {
294
- throw new Error(
295
- message +
296
- '\nTo suppress or handle this error, implement `handleUnseenRoutes` in https://svelte.dev/docs/kit/configuration#prerender'
297
- );
298
- },
299
- (input, keypath) => {
300
- if (typeof input === 'function') return input;
301
- if (['fail', 'warn', 'ignore'].includes(input)) return input;
302
- throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
303
- }
304
- ),
305
-
306
- handleInvalidUrl: validate(
307
- (/** @type {any} */ { message }) => {
308
- throw new Error(
309
- message +
310
- '\nTo suppress or handle this error, implement `handleInvalidUrl` in https://svelte.dev/docs/kit/configuration#prerender'
311
- );
312
- },
313
- (input, keypath) => {
314
- if (typeof input === 'function') return input;
315
- if (['fail', 'warn', 'ignore'].includes(input)) return input;
316
- throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
317
- }
318
- ),
256
+ handleHttpError: prerender_handler,
257
+ handleMissingId: prerender_handler,
258
+ handleEntryGeneratorMismatch: prerender_handler,
259
+ handleUnseenRoutes: prerender_handler,
260
+ handleInvalidUrl: prerender_handler,
319
261
 
320
262
  origin: removed(
321
263
  (keypath) => `\`${keypath}\` has been removed in favour of \`config.paths.origin\``
@@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import { forked } from '../../utils/fork.js';
5
+ import { stackless } from '../../utils/error.js';
5
6
 
6
7
  export default forked(import.meta.url, generate_fallback);
7
8
 
@@ -47,5 +48,5 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets }
47
48
  return await response.text();
48
49
  }
49
50
 
50
- throw new Error(`Could not create a fallback page — failed with status ${response.status}`);
51
+ throw stackless('Could not create a fallback page');
51
52
  }
@@ -16,6 +16,7 @@ import * as devalue from 'devalue';
16
16
  import { createReadableStream } from '@sveltejs/kit/node';
17
17
  import generate_fallback from './fallback.js';
18
18
  import { stringify_remote_arg } from '../../runtime/shared.js';
19
+ import { log_response } from '../../exports/vite/utils.js';
19
20
 
20
21
  export default forked(import.meta.url, prerender);
21
22
 
@@ -57,29 +58,60 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
57
58
  /** @type {import('types').ServerModule} */
58
59
  const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
59
60
 
61
+ const throw_handled = () => {
62
+ throw new Error('__handled__');
63
+ };
64
+
60
65
  /**
61
66
  * @template {{message: string}} T
62
67
  * @template {Omit<T, 'message'>} K
63
- * @param {import('types').Logger} log
64
- * @param {'fail' | 'warn' | 'ignore' | ((details: T) => void)} input
68
+ * @param {string} name
69
+ * @param {'fail' | 'warn' | 'ignore' | undefined | ((details: T) => void)} input
65
70
  * @param {(details: K) => string} format
66
- * @returns {(details: K) => void}
71
+ * @returns {(details: K & { error?: unknown }) => void}
67
72
  */
68
- function normalise_error_handler(log, input, format) {
73
+ function normalise_error_handler(name, input, format) {
74
+ /**
75
+ * @param {any} details
76
+ */
77
+ function log_failure(details) {
78
+ const message = format(details);
79
+ log.error(`\n${message}\n`);
80
+ }
81
+
69
82
  switch (input) {
70
83
  case 'fail':
71
84
  return (details) => {
72
- throw new Error(format(details));
85
+ log_failure(details);
86
+ throw_handled();
73
87
  };
74
88
  case 'warn':
75
- return (details) => {
76
- log.error(format(details));
77
- };
89
+ return log_failure;
78
90
  case 'ignore':
79
91
  return noop;
92
+ case undefined: {
93
+ return (details) => {
94
+ log_failure(details);
95
+
96
+ log.err(
97
+ `To suppress or handle this error, implement \`${name}\` in https://svelte.dev/docs/kit/configuration#prerender\n`
98
+ );
99
+
100
+ throw_handled();
101
+ };
102
+ }
80
103
  default:
81
- // @ts-expect-error TS thinks T might be of a different kind, but it's not
82
- return (details) => input({ ...details, message: format(details) });
104
+ return (details) => {
105
+ const message = format(details);
106
+
107
+ try {
108
+ // @ts-expect-error TS thinks T might be of a different kind, but it's not
109
+ input({ ...details, message });
110
+ } catch (error) {
111
+ log.prettyError(error, import.meta.dirname);
112
+ throw_handled();
113
+ }
114
+ };
83
115
  }
84
116
  }
85
117
 
@@ -141,48 +173,55 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
141
173
  const saved = new Map();
142
174
 
143
175
  const handle_http_error = normalise_error_handler(
144
- log,
176
+ 'handleHttpError',
145
177
  config.prerender.handleHttpError,
146
178
  ({ status, path, referrer, referenceType }) => {
147
- const message =
148
- status === 404 && !path.startsWith(config.paths.base)
149
- ? `${path} does not begin with \`base\`. You can fix this by using \`resolve('${path}')\` from \`$app/paths\`. The base path is configurable from \`paths.base\` - see https://svelte.dev/docs/kit/configuration#paths for more info`
150
- : path;
179
+ let message = `Failed to prerender ${path}`;
180
+
181
+ if (status === 404) {
182
+ if (!path.startsWith(config.paths.base)) {
183
+ message = referrer ? `${path} (${referenceType} from ${referrer})` : path;
184
+
185
+ message += ` does not begin with \`base\`. You can fix this by using \`resolve('${path}')\` from \`$app/paths\`. The base path is configurable from \`paths.base\``;
186
+ } else if (referrer) {
187
+ message = `${path} was ${referenceType} from ${referrer}`;
188
+ }
189
+ }
151
190
 
152
- return `${status} ${message}${referrer ? ` (${referenceType} from ${referrer})` : ''}`;
191
+ return message;
153
192
  }
154
193
  );
155
194
 
156
195
  const handle_missing_id = normalise_error_handler(
157
- log,
196
+ 'handleMissingId',
158
197
  config.prerender.handleMissingId,
159
198
  ({ path, id, referrers }) => {
160
199
  return (
161
- `The following pages contain links to ${path}#${id}, but no element with id="${id}" exists on ${path} - see the \`handleMissingId\` option in https://svelte.dev/docs/kit/configuration#prerender for more info:` +
200
+ `The following pages contain links to ${path}#${id}, but no element with id="${id}" exists on ${path}:` +
162
201
  referrers.map((l) => `\n - ${l}`).join('')
163
202
  );
164
203
  }
165
204
  );
166
205
 
167
206
  const handle_entry_generator_mismatch = normalise_error_handler(
168
- log,
207
+ 'handleEntryGeneratorMismatch',
169
208
  config.prerender.handleEntryGeneratorMismatch,
170
209
  ({ generatedFromId, entry, matchedId }) => {
171
- return `The entries export from ${generatedFromId} generated entry ${entry}, which was matched by ${matchedId} - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.`;
210
+ return `The entries export from ${generatedFromId} generated entry ${entry}, which was matched by ${matchedId === entry ? 'a static route' : matchedId}`;
172
211
  }
173
212
  );
174
213
 
175
214
  const handle_not_prerendered_route = normalise_error_handler(
176
- log,
215
+ 'handleUnseenRoutes',
177
216
  config.prerender.handleUnseenRoutes,
178
217
  ({ routes }) => {
179
218
  const list = routes.map((id) => ` - ${id}`).join('\n');
180
- return `The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\n${list}\n\nSee the \`handleUnseenRoutes\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.`;
219
+ return `The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\n${list}`;
181
220
  }
182
221
  );
183
222
 
184
223
  const handle_invalid_url = normalise_error_handler(
185
- log,
224
+ 'handleInvalidUrl',
186
225
  config.prerender.handleInvalidUrl,
187
226
  ({ href, referrer }) => {
188
227
  return `Invalid URL ${href}${referrer ? ` (linked from ${referrer})` : ''}`;
@@ -260,7 +299,9 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
260
299
  /** @type {Map<string, import('types').PrerenderDependency>} */
261
300
  const dependencies = new Map();
262
301
 
263
- const response = await server.respond(new Request(prerender_origin + encoded), {
302
+ const request = new Request(prerender_origin + encoded);
303
+
304
+ const response = await server.respond(request, {
264
305
  getClientAddress() {
265
306
  throw new Error('Cannot read clientAddress during prerendering');
266
307
  },
@@ -298,6 +339,10 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
298
339
  });
299
340
  }
300
341
 
342
+ if (response.status !== 204) {
343
+ log_response(response.status, request);
344
+ }
345
+
301
346
  const body = Buffer.from(await response.arrayBuffer());
302
347
 
303
348
  const category = decoded.startsWith(remote_prefix) ? 'data' : 'pages';
@@ -428,8 +473,6 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
428
473
  if (!headers['x-sveltekit-normalize']) {
429
474
  mkdirp(dirname(dest));
430
475
 
431
- log.warn(`${response.status} ${decoded} -> ${location}`);
432
-
433
476
  writeFileSync(
434
477
  dest,
435
478
  `<script>location.href=${devalue.uneval(
@@ -476,7 +519,6 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
476
519
 
477
520
  mkdirp(dir);
478
521
 
479
- log.info(`${response.status} ${decoded}`);
480
522
  writeFileSync(dest, body);
481
523
  written.add(file);
482
524
 
@@ -492,7 +534,12 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
492
534
 
493
535
  prerendered.paths.push(decoded);
494
536
  } else if (response_type !== OK) {
495
- handle_http_error({ status: response.status, path: decoded, referrer, referenceType });
537
+ handle_http_error({
538
+ status: response.status,
539
+ path: decoded,
540
+ referrer,
541
+ referenceType
542
+ });
496
543
  }
497
544
 
498
545
  manifest.assets.add(file);
@@ -28,7 +28,7 @@ export function queue(concurrency) {
28
28
 
29
29
  if (task) {
30
30
  current += 1;
31
- const promise = Promise.resolve(task.fn());
31
+ const promise = (async () => task.fn())(); // could throw synchronously
32
32
 
33
33
  void promise
34
34
  .then(task.fulfil, (err) => {
@@ -54,6 +54,7 @@ export function queue(concurrency) {
54
54
  const promise = new Promise((fulfil, reject) => {
55
55
  tasks.push({ fn, fulfil, reject });
56
56
  });
57
+ promise.catch(() => {});
57
58
 
58
59
  dequeue();
59
60
  return promise;
@@ -26,7 +26,7 @@ const server_template = ({
26
26
  }) => `
27
27
  import { set_building, set_prerendering } from '$app/env/internal';
28
28
  import { set_assets } from '$app/paths/internal/server';
29
- import { set_manifest, set_read_implementation } from '__sveltekit/server';
29
+ import { set_fix_stack_trace, set_manifest, set_read_implementation } from '__sveltekit/server';
30
30
  import error from '../shared/error-template.js';
31
31
 
32
32
  export const options = {
@@ -80,7 +80,7 @@ export async function get_hooks() {
80
80
  };
81
81
  }
82
82
 
83
- export { set_assets, set_building, set_manifest, set_prerendering, set_read_implementation };
83
+ export { set_assets, set_building, set_fix_stack_trace, set_manifest, set_prerendering, set_read_implementation };
84
84
  `;
85
85
 
86
86
  // TODO need to re-run this whenever src/app.html or src/error.html are
package/src/core/utils.js CHANGED
@@ -30,18 +30,43 @@ export function get_runtime_base(root) {
30
30
  }
31
31
 
32
32
  /** @param {{ verbose: boolean }} opts */
33
- export function logger({ verbose }) {
33
+ export function logger({ verbose } = { verbose: true }) {
34
34
  /** @type {import('types').Logger} */
35
- const log = (msg) => console.log(msg.replace(/^/gm, ' '));
35
+ const log = (msg) => console.log(msg);
36
36
 
37
37
  /** @param {string} msg */
38
- const err = (msg) => console.error(msg.replace(/^/gm, ' '));
38
+ const err = (msg) => console.error(msg);
39
39
 
40
40
  log.success = (msg) => log(styleText('green', `✔ ${msg}`));
41
41
  log.error = (msg) => err(styleText(['bold', 'red'], msg));
42
42
  log.warn = (msg) => log(styleText(['bold', 'yellow'], msg));
43
43
  log.minor = verbose ? (msg) => log(styleText('grey', msg)) : noop;
44
44
  log.info = verbose ? log : noop;
45
+ log.err = err;
46
+
47
+ log.prettyError = (error, caller) => {
48
+ /** @type {unknown} */
49
+ let e = error;
50
+
51
+ while (e instanceof Error) {
52
+ let stack = e.stack;
53
+ if (stack) {
54
+ if (caller) {
55
+ const i = stack.indexOf(caller);
56
+ // Cut the stack trace off at the point when our internal one starts
57
+ stack = stack.slice(0, stack.lastIndexOf('\n', i));
58
+ }
59
+
60
+ err(stack);
61
+ }
62
+
63
+ e = e.cause;
64
+ }
65
+
66
+ if (e) {
67
+ err(String(e));
68
+ }
69
+ };
45
70
 
46
71
  return log;
47
72
  }
@@ -1,12 +1,77 @@
1
- /** @import { EnvVarConfig } from '@sveltejs/kit' */
1
+ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
2
+ /** @import { DefinedEnvVars, EnvVarConfig } from '@sveltejs/kit' */
2
3
 
3
4
  /**
4
5
  * Utility for defining [environment variables](https://svelte.dev/docs/kit/environment-variables),
5
6
  * which are made available via `$app/env/public` and `$app/env/private`.
7
+ *
8
+ * @example
9
+ * ```js
10
+ * import { defineEnvVars } from '@sveltejs/kit/env';
11
+ * import * as v from 'valibot';
12
+ *
13
+ * export const variables = defineEnvVars({
14
+ * API_URL: {
15
+ * schema: v.pipe(v.string(), v.url())
16
+ * },
17
+ * PORT: {
18
+ * schema: (value) => {
19
+ * if (value === undefined) return 3000;
20
+ * const port = Number(value);
21
+ * if (!Number.isInteger(port)) throw new Error('PORT must be an integer');
22
+ * return port;
23
+ * }
24
+ * }
25
+ * });
26
+ * ```
27
+ *
6
28
  * @template {Record<string, EnvVarConfig<any>>} T
7
29
  * @param {T} variables
8
- * @returns {T}
30
+ * @returns {DefinedEnvVars<T>}
9
31
  */
10
32
  export function defineEnvVars(variables) {
11
- return variables;
33
+ /** @type {Record<string, EnvVarConfig<any>>} */
34
+ const normalized = {};
35
+
36
+ for (const [name, config] of Object.entries(variables)) {
37
+ // standard schemas can be callable (e.g. ArkType), so a function is only a validator if it isn't one
38
+ normalized[name] =
39
+ typeof config.schema === 'function' && !('~standard' in config.schema)
40
+ ? { ...config, schema: normalize_env_schema(config.schema) }
41
+ : config;
42
+ }
43
+
44
+ return /** @type {DefinedEnvVars<T>} */ (normalized);
45
+ }
46
+
47
+ /**
48
+ * @param {(value: string | undefined) => any} fn
49
+ * @returns {StandardSchemaV1<string | undefined, any>}
50
+ */
51
+ function normalize_env_schema(fn) {
52
+ return /** @type {StandardSchemaV1<string | undefined, any>} */ (
53
+ /** @type {unknown} */ ({
54
+ '~standard': {
55
+ validate(/** @type {unknown} */ value) {
56
+ let result;
57
+
58
+ try {
59
+ result = fn(/** @type {string | undefined} */ (value));
60
+ } catch (error) {
61
+ return {
62
+ issues: [
63
+ {
64
+ message: error instanceof Error ? error.message || 'Invalid value' : String(error)
65
+ }
66
+ ]
67
+ };
68
+ }
69
+
70
+ if (result instanceof Promise) return result; // will be rejected upstream
71
+
72
+ return { value: result };
73
+ }
74
+ }
75
+ })
76
+ );
12
77
  }
@@ -4,7 +4,7 @@
4
4
  import { stackless } from '../../utils/error.js';
5
5
 
6
6
  const MISSING = {
7
- message: `Value is missing. If it is optional, add a Standard Schema validator declaring it as such.`
7
+ message: `Value is missing. If it is optional, add a validator declaring it as such.`
8
8
  };
9
9
 
10
10
  const BAD_VALIDATOR = {
@@ -24,10 +24,13 @@ const ASYNC_VALIDATOR = {
24
24
  */
25
25
  export function validate(variables, value, name, issues) {
26
26
  const config = variables[name] ?? {};
27
- const validator = config.schema;
27
+ // `defineEnvVars` normalizes function validators to standard schemas
28
+ const validator = /** @type {StandardSchemaV1<string | undefined, any> | undefined} */ (
29
+ config.schema
30
+ );
28
31
 
29
32
  if (!validator) {
30
- if (!value) issues[name] = [MISSING];
33
+ if (value === undefined) issues[name] = [MISSING];
31
34
  return value;
32
35
  }
33
36
 
@@ -35,6 +35,15 @@ export function defineParams(definitions) {
35
35
  * @returns {import('@sveltejs/kit').ParamMatcher}
36
36
  */
37
37
  export function normalize_param_definition(definition) {
38
+ // standard schemas can be callable (e.g. ArkType), so this must be checked before the function case
39
+ if (
40
+ definition &&
41
+ (typeof definition === 'object' || typeof definition === 'function') &&
42
+ '~standard' in definition
43
+ ) {
44
+ return definition;
45
+ }
46
+
38
47
  if (typeof definition === 'function') {
39
48
  return /** @type {import('@sveltejs/kit').ParamMatcher} */ (
40
49
  /** @type {unknown} */ ({
@@ -55,9 +64,5 @@ export function normalize_param_definition(definition) {
55
64
  );
56
65
  }
57
66
 
58
- if (definition && typeof definition === 'object' && '~standard' in definition) {
59
- return definition;
60
- }
61
-
62
67
  throw new Error('Invalid param definition');
63
68
  }