@sveltejs/kit 3.0.0-next.11 → 3.0.0-next.13

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 (84) hide show
  1. package/package.json +10 -10
  2. package/src/cli.js +8 -8
  3. package/src/constants.js +1 -1
  4. package/src/core/config/options.js +43 -35
  5. package/src/core/env.js +25 -11
  6. package/src/core/postbuild/prerender.js +3 -3
  7. package/src/core/sync/sync.js +11 -14
  8. package/src/core/sync/utils.js +21 -1
  9. package/src/core/sync/{write_non_ambient.js → write_app_types.js} +24 -23
  10. package/src/core/sync/write_client_manifest.js +4 -12
  11. package/src/core/sync/write_env.js +6 -4
  12. package/src/core/sync/write_server.js +10 -15
  13. package/src/core/sync/write_tsconfig/index.js +257 -0
  14. package/src/core/sync/write_tsconfig/utils.js +162 -0
  15. package/src/core/sync/write_types/index.js +80 -88
  16. package/src/exports/index.js +23 -12
  17. package/src/exports/internal/server/index.js +3 -1
  18. package/src/exports/internal/shared.js +4 -12
  19. package/src/exports/public.d.ts +87 -62
  20. package/src/exports/vite/build/remote.js +10 -12
  21. package/src/exports/vite/dev/index.js +113 -88
  22. package/src/exports/vite/index.js +452 -237
  23. package/src/exports/vite/module_ids.js +3 -1
  24. package/src/exports/vite/utils.js +1 -10
  25. package/src/runner.js +13 -0
  26. package/src/runtime/app/forms.js +4 -0
  27. package/src/runtime/app/manifest/index.js +1 -0
  28. package/src/runtime/app/paths/client.js +30 -30
  29. package/src/runtime/app/paths/internal/client.js +26 -0
  30. package/src/runtime/app/paths/server.js +22 -9
  31. package/src/runtime/app/paths/types.d.ts +11 -19
  32. package/src/runtime/app/server/remote/command.js +12 -7
  33. package/src/runtime/app/server/remote/form.js +37 -26
  34. package/src/runtime/app/server/remote/prerender.js +22 -11
  35. package/src/runtime/app/server/remote/query.js +5 -4
  36. package/src/runtime/app/server/remote/requested.js +4 -4
  37. package/src/runtime/app/server/remote/shared.js +48 -30
  38. package/src/runtime/app/service-worker/index.js +24 -0
  39. package/src/runtime/app/state/client.js +3 -0
  40. package/src/runtime/app/state/index.js +3 -3
  41. package/src/runtime/app/state/server.js +3 -0
  42. package/src/runtime/client/client.js +627 -311
  43. package/src/runtime/client/constants.js +2 -6
  44. package/src/runtime/client/fetcher.js +27 -17
  45. package/src/runtime/client/remote-functions/form.svelte.js +82 -58
  46. package/src/runtime/client/remote-functions/prerender.svelte.js +1 -1
  47. package/src/runtime/client/remote-functions/query/index.js +2 -2
  48. package/src/runtime/client/remote-functions/query/instance.svelte.js +2 -2
  49. package/src/runtime/client/remote-functions/query-batch.svelte.js +3 -4
  50. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +2 -2
  51. package/src/runtime/client/remote-functions/query-live/iterator.js +15 -10
  52. package/src/runtime/client/remote-functions/shared.svelte.js +15 -11
  53. package/src/runtime/client/state.svelte.js +54 -33
  54. package/src/runtime/client/types.d.ts +1 -2
  55. package/src/runtime/client/utils.js +21 -14
  56. package/src/runtime/components/root.svelte +4 -14
  57. package/src/runtime/form-utils.js +89 -54
  58. package/src/runtime/props.svelte.js +71 -0
  59. package/src/runtime/server/cookie.js +1 -1
  60. package/src/runtime/server/data/index.js +31 -28
  61. package/src/runtime/server/errors.js +1 -1
  62. package/src/runtime/server/fetch.js +4 -6
  63. package/src/runtime/server/page/actions.js +3 -3
  64. package/src/runtime/server/page/csp.js +86 -103
  65. package/src/runtime/server/page/index.js +1 -2
  66. package/src/runtime/server/page/load_data.js +15 -5
  67. package/src/runtime/server/page/render.js +46 -52
  68. package/src/runtime/server/page/respond_with_error.js +1 -3
  69. package/src/runtime/server/remote-functions.js +66 -35
  70. package/src/runtime/server/respond.js +56 -16
  71. package/src/runtime/server/utils.js +10 -0
  72. package/src/types/ambient-private.d.ts +9 -1
  73. package/src/types/ambient.d.ts +23 -28
  74. package/src/types/global-private.d.ts +12 -0
  75. package/src/types/internal.d.ts +15 -10
  76. package/src/types/private.d.ts +3 -12
  77. package/src/utils/url.js +12 -0
  78. package/src/version.js +1 -1
  79. package/types/index.d.ts +172 -155
  80. package/types/index.d.ts.map +5 -1
  81. package/src/core/sync/write_ambient.js +0 -18
  82. package/src/core/sync/write_tsconfig.js +0 -258
  83. package/src/runtime/types.d.ts +0 -8
  84. /package/src/core/sync/{write_tsconfig_test → write_tsconfig/test-app}/package.json +0 -0
@@ -1,11 +1,10 @@
1
1
  import path from 'node:path';
2
- import { styleText } from 'node:util';
3
2
  import { hash } from '../../utils/hash.js';
4
3
  import { resolve_entry } from '../../utils/filesystem.js';
5
4
  import { posixify } from '../../utils/os.js';
6
5
  import { s } from '../../utils/misc.js';
7
6
  import { load_error_page, load_template } from '../config/index.js';
8
- import { write_if_changed } from './utils.js';
7
+ import { check_spelling, write_if_changed } from './utils.js';
9
8
  import { escape_html } from '../../utils/escape.js';
10
9
 
11
10
  /**
@@ -54,6 +53,7 @@ export const options = {
54
53
  )},
55
54
  error
56
55
  },
56
+ version: ${s(config.kit.version.name)},
57
57
  version_hash: ${s(hash(config.kit.version.name))}
58
58
  };
59
59
 
@@ -83,10 +83,6 @@ export async function get_hooks() {
83
83
  export { set_assets, set_building, set_fix_stack_trace, set_manifest, set_prerendering, set_read_implementation };
84
84
  `;
85
85
 
86
- // TODO need to re-run this whenever src/app.html or src/error.html are
87
- // created or changed, or src/service-worker.js is created or deleted.
88
- // Also, need to check that updating hooks.server.js works
89
-
90
86
  /**
91
87
  * Write server configuration to disk
92
88
  * @param {import('types').ValidatedConfig} config
@@ -97,15 +93,14 @@ export function write_server(config, output, root) {
97
93
  const server_hooks_file = resolve_entry(config.kit.files.hooks.server);
98
94
  const universal_hooks_file = resolve_entry(config.kit.files.hooks.universal);
99
95
 
100
- const typo = resolve_entry('src/+hooks.server');
101
- if (typo) {
102
- console.log(
103
- styleText(
104
- ['bold', 'yellow'],
105
- `Unexpected + prefix. Did you mean ${typo.split('/').at(-1)?.slice(1)}?` +
106
- ` at ${path.resolve(typo)}`
107
- )
108
- );
96
+ if (!server_hooks_file) {
97
+ check_spelling('src/hooks.server', 'src/+hooks.server', 'Unexpected + prefix');
98
+ check_spelling('src/hooks.server', 'src/hook.server', 'Missing s suffix');
99
+ }
100
+
101
+ if (!universal_hooks_file) {
102
+ check_spelling('src/hooks', 'src/+hooks', 'Unexpected + prefix');
103
+ check_spelling('src/hooks', 'src/hook', 'Missing s suffix');
109
104
  }
110
105
 
111
106
  /** @param {string} file */
@@ -0,0 +1,257 @@
1
+ /** @import { ValidatedKitConfig } from 'types' */
2
+ import process from 'node:process';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { styleText } from 'node:util';
6
+ import { write_if_changed } from '../utils.js';
7
+ import {
8
+ ESSENTIAL_OPTIONS,
9
+ extends_id,
10
+ get_subpath_imports,
11
+ normalize_config,
12
+ RECOMMENDED_OPTIONS,
13
+ remove_trailing_slashstar,
14
+ validate_resolved_config
15
+ } from './utils.js';
16
+
17
+ /** @type {import('typescript')} */
18
+ let ts;
19
+ try {
20
+ ts = await import('typescript');
21
+ } catch {
22
+ // The user has not installed TypeScript.
23
+ }
24
+
25
+ /**
26
+ * Generates the tsconfig that the user's tsconfig inherits from.
27
+ * @param {import('types').ValidatedKitConfig} kit
28
+ * @param {string} root
29
+ */
30
+ export function write_tsconfig(kit, root) {
31
+ const paths = get_paths(kit, root);
32
+
33
+ write_parent_tsconfig(
34
+ root,
35
+ root,
36
+ '$app/tsconfig',
37
+ {
38
+ compilerOptions: {
39
+ paths,
40
+ rootDirs: ['.', `${kit.outDir}/types`],
41
+ types: ['$app/types'],
42
+
43
+ // This is required for svelte-package to work as expected
44
+ // Can be overwritten
45
+ lib: ['ESNext', 'DOM', 'DOM.Iterable'],
46
+
47
+ ...ESSENTIAL_OPTIONS,
48
+ ...RECOMMENDED_OPTIONS
49
+ },
50
+ exclude: [kit.files.serviceWorker]
51
+ },
52
+ {
53
+ extends: '$app/tsconfig',
54
+ include: ['src']
55
+ },
56
+ kit.typescript.config
57
+ );
58
+
59
+ write_parent_tsconfig(
60
+ root,
61
+ kit.files.serviceWorker,
62
+ '$app/tsconfig/service-worker',
63
+ {
64
+ compilerOptions: {
65
+ paths,
66
+ types: ['$app/types'],
67
+ lib: ['ESNext', 'WebWorker'],
68
+ ...ESSENTIAL_OPTIONS
69
+ }
70
+ },
71
+ {
72
+ extends: '$app/tsconfig/service-worker'
73
+ }
74
+ );
75
+ }
76
+
77
+ /**
78
+ * Write a generated `tsconfig.json` inside `node_modules`, for the
79
+ * user config to extend
80
+ * @param {string} root The project root
81
+ * @param {string} dir The directory to resolve a user config from
82
+ * @param {string} id The id of the generated config
83
+ * @param {any} config The contents of the generated tsconfig, with paths relative to `root`
84
+ * @param {any} example What to print if the user config does _not_ extend the generated config
85
+ * @param {ValidatedKitConfig['typescript']['config']} [transform] TODO get rid of this
86
+ */
87
+ function write_parent_tsconfig(root, dir, id, config, example, transform) {
88
+ const out_file = path.join(root, `node_modules/${id}/tsconfig.json`);
89
+
90
+ let normalized = normalize_config(out_file, config);
91
+ normalized = transform?.(normalized) ?? normalized;
92
+
93
+ write_if_changed(out_file, JSON.stringify(normalized, null, '\t'));
94
+
95
+ if (!ts) {
96
+ // The user has not installed TypeScript. Skip validation of config.
97
+ return;
98
+ }
99
+
100
+ const user_config = load_user_tsconfig(dir);
101
+
102
+ if (user_config && modified_since_last_check(user_config.file)) {
103
+ // now that we've written the parent config, we can resolve the
104
+ // user config and validate that nothing important was overwritten
105
+ if (!extends_id(user_config.options, id)) {
106
+ console.warn(
107
+ styleText(
108
+ ['bold', 'yellow'],
109
+ `${path.relative(process.cwd(), user_config.file)} should extend SvelteKit's built-in configuration:`
110
+ )
111
+ );
112
+
113
+ console.warn(JSON.stringify(example, null, ' '));
114
+
115
+ return;
116
+ }
117
+
118
+ const resolved = ts.parseJsonConfigFileContent(user_config.options, ts.sys, dir).options;
119
+ const warnings = validate_resolved_config(resolved, config.compilerOptions);
120
+
121
+ if (warnings.length > 0) {
122
+ console.warn(
123
+ styleText(
124
+ ['bold', 'yellow'],
125
+ `Found issues while validating ${path.relative(process.cwd(), user_config.file)}`
126
+ )
127
+ );
128
+
129
+ for (const warning of warnings) {
130
+ console.warn(` - ${warning}`);
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ /** @type {Map<string, number>} */
137
+ const mtimes = new Map();
138
+
139
+ /**
140
+ * Returns true if the file was modified since we last got here,
141
+ * otherwise we can skip warnings
142
+ * @param {string} file
143
+ */
144
+ function modified_since_last_check(file) {
145
+ const a = mtimes.get(file) ?? -1;
146
+ const b = fs.statSync(file).mtimeMs;
147
+ mtimes.set(file, b);
148
+
149
+ return b > a;
150
+ }
151
+
152
+ /**
153
+ * @param {string} cwd
154
+ * @param {string} file
155
+ */
156
+ function maybe_file(cwd, file) {
157
+ const resolved = path.resolve(cwd, file);
158
+ if (fs.existsSync(resolved)) {
159
+ return resolved;
160
+ }
161
+ }
162
+
163
+ /** @param {string} cwd */
164
+ function load_user_tsconfig(cwd) {
165
+ const file = maybe_file(cwd, 'tsconfig.json') || maybe_file(cwd, 'jsconfig.json');
166
+ if (!file) return;
167
+
168
+ const options = load_tsconfig(file);
169
+
170
+ return {
171
+ file,
172
+ kind: path.basename(file),
173
+ options
174
+ };
175
+ }
176
+
177
+ /**
178
+ * @param {string} file
179
+ */
180
+ function load_tsconfig(file) {
181
+ const options = ts.readConfigFile(file, ts.sys.readFile);
182
+
183
+ if (options.error) {
184
+ let message = `Failed to parse TypeScript config`;
185
+
186
+ if (typeof options.error.messageText === 'string') {
187
+ message += `: ${options.error.messageText}`;
188
+ }
189
+
190
+ const error = new Error(message);
191
+ error.stack = '';
192
+
193
+ if (options.error.file && options.error.start !== undefined) {
194
+ const line_start = options.error.file.text.lastIndexOf('\n', options.error.start);
195
+
196
+ const line =
197
+ line_start === -1
198
+ ? 0
199
+ : options.error.file.text.slice(0, options.error.start).split('\n').length;
200
+ const column = options.error.start - line_start;
201
+
202
+ error.stack = `${error.message}\n at ${path.relative(process.cwd(), file)}:${line}:${column}`;
203
+ }
204
+
205
+ throw error;
206
+ }
207
+
208
+ return options.config;
209
+ }
210
+
211
+ /** Matches anything (except the empty string/string with newline), and separates any trailing `/*` */
212
+ const alias_key = /^(.+?)(\/\*)?$/;
213
+
214
+ /** Matches anything (except the empty string/string with newline), and separates any trailing `/*` or file extension */
215
+ const alias_value = /^(.+?)((\/\*)|(\.\w+))?$/;
216
+
217
+ /**
218
+ * Generates tsconfig path aliases from kit's aliases and the package.json `imports` field.
219
+ * Related to vite alias creation.
220
+ *
221
+ * @param {import('types').ValidatedKitConfig} config
222
+ * @param {string} root
223
+ * @returns {Record<string, string[]>}
224
+ */
225
+ function get_paths(config, root) {
226
+ const alias = {
227
+ ...config.alias,
228
+ ...get_subpath_imports(root)
229
+ };
230
+
231
+ /** @type {Record<string, string[]>} */
232
+ const paths = {};
233
+
234
+ for (const [key, value] of Object.entries(alias)) {
235
+ const key_match = alias_key.exec(key);
236
+ if (!key_match) throw new Error(`Invalid alias key: ${key}`);
237
+
238
+ const value_match = alias_value.exec(value);
239
+ if (!value_match) throw new Error(`Invalid alias value: ${value}`);
240
+
241
+ const resolved = path.resolve(root, remove_trailing_slashstar(value));
242
+ const slashstar = key_match[2];
243
+
244
+ if (slashstar) {
245
+ paths[key] = [resolved + '/*'];
246
+ } else {
247
+ paths[key] = [resolved];
248
+ const fileending = value_match[4];
249
+
250
+ if (!fileending && !(key + '/*' in alias)) {
251
+ paths[key + '/*'] = [resolved + '/*'];
252
+ }
253
+ }
254
+ }
255
+
256
+ return paths;
257
+ }
@@ -0,0 +1,162 @@
1
+ import path from 'node:path';
2
+ import { normalize_import_value, read_package_imports } from '../../../utils/imports.js';
3
+ import { posixify } from '../../../utils/os.js';
4
+
5
+ /**
6
+ * Without these, compilation will fail
7
+ * @type {Record<string, any>}
8
+ */
9
+ export const ESSENTIAL_OPTIONS = {
10
+ // svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
11
+ // to enforce using \`import type\` instead of \`import\` for Types.
12
+ // Also, TypeScript doesn't know about import usages in the template because it only sees the
13
+ // script of a Svelte file. Therefore preserve all value imports.
14
+ verbatimModuleSyntax: true,
15
+ // Vite compiles modules one at a time
16
+ isolatedModules: true
17
+ };
18
+
19
+ /**
20
+ * Options that are strongly recommended, either because not having them is silly or
21
+ * because the align TypeScript's behaviour with Vite's, but which can be overwritten
22
+ * @type {Record<string, any>}
23
+ */
24
+ export const RECOMMENDED_OPTIONS = {
25
+ allowJs: true,
26
+ checkJs: true,
27
+ forceConsistentCasingInFileNames: true,
28
+ resolveJsonModule: true,
29
+ moduleDetection: 'force',
30
+ moduleResolution: 'bundler',
31
+ allowImportingTsExtensions: true,
32
+ module: 'esnext',
33
+ target: 'esnext',
34
+ skipLibCheck: true,
35
+ esModuleInterop: true,
36
+ noEmit: true // prevent tsconfig error "overwriting input files" - Vite handles the build and ignores this
37
+ };
38
+
39
+ /**
40
+ * Validates that a tsconfig contains `"extends": "<id>"`
41
+ * @param {any} options
42
+ * @param {string} id
43
+ */
44
+ export function extends_id(options, id) {
45
+ const o = options.extends;
46
+ return Array.isArray(o) ? o.includes(id) : o === id;
47
+ }
48
+
49
+ /**
50
+ * Makes paths relative to the output file
51
+ * @param {string} out
52
+ * @param {any} config
53
+ * @param {(input: any) => any} [transform] TODO get rid of this option
54
+ */
55
+ export function normalize_config(out, config, transform) {
56
+ const dir = path.dirname(out);
57
+ const relative = (/** @type {string} */ file) => posixify(path.relative(dir, file));
58
+
59
+ const paths =
60
+ config.compilerOptions?.paths &&
61
+ Object.fromEntries(
62
+ Object.entries(config.compilerOptions?.paths).map(([k, v]) => [k, v.map(relative)])
63
+ );
64
+
65
+ const normalized = {
66
+ ...config,
67
+ compilerOptions: {
68
+ ...config.compilerOptions,
69
+ paths,
70
+ rootDirs: config.compilerOptions?.rootDirs?.map(relative)
71
+ },
72
+ include: config.include?.map(relative),
73
+ exclude: config.exclude?.map(relative)
74
+ };
75
+
76
+ return transform?.(normalized) ?? normalized;
77
+ }
78
+
79
+ /**
80
+ * @param {any} resolved
81
+ * @param {any} options
82
+ */
83
+ export function validate_resolved_config(resolved, options) {
84
+ const warnings = [];
85
+
86
+ const join = (/** @type {string[]} */ array) =>
87
+ array
88
+ .map((v) => JSON.stringify(v))
89
+ .join(', ')
90
+ .replace(/, ([^,]*)$/, ' and $1');
91
+
92
+ const missing_types = options.types?.filter(
93
+ (/** @type {string} */ type) => !resolved.types?.includes(type)
94
+ );
95
+
96
+ if (missing_types.length > 0) {
97
+ warnings.push(`"types" was overwritten. It must include ${join(missing_types)}`);
98
+ }
99
+
100
+ if (options.paths) {
101
+ /** @type {Set<string>} */
102
+ const mismatch = new Set();
103
+
104
+ for (const [k, expected] of Object.entries(options.paths)) {
105
+ const actual = resolved.paths?.[k]?.map((/** @type {string} */ x) =>
106
+ path.resolve(/** @type {string} */ (resolved.pathsBasePath), x)
107
+ );
108
+
109
+ if (JSON.stringify(expected) !== JSON.stringify(actual)) {
110
+ mismatch.add(remove_trailing_slashstar(k));
111
+ }
112
+ }
113
+
114
+ if (mismatch.size > 0) {
115
+ const joined = join([...mismatch]);
116
+
117
+ warnings.push(`"paths" was overwritten. Imports from ${joined} may not typecheck`);
118
+ }
119
+ }
120
+
121
+ for (const key in ESSENTIAL_OPTIONS) {
122
+ if (resolved[key] !== options[key]) {
123
+ warnings.push(`"${key}" was overwritten. It should be ${JSON.stringify(options[key])}`);
124
+ }
125
+ }
126
+
127
+ return warnings;
128
+ }
129
+
130
+ /**
131
+ * @param {string} file
132
+ */
133
+ export function remove_trailing_slashstar(file) {
134
+ if (file.endsWith('/*')) {
135
+ return file.slice(0, -2);
136
+ } else {
137
+ return file;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * @param {string} root
143
+ */
144
+ export function get_subpath_imports(root) {
145
+ // Add all `#`-prefixed imports from package.json as path aliases
146
+ const imports = read_package_imports(root);
147
+
148
+ /** @type {Record<string, string>} */
149
+ const alias = {};
150
+
151
+ if (imports) {
152
+ for (const [key, raw_value] of Object.entries(imports)) {
153
+ if (!key.startsWith('#')) continue;
154
+ const value = normalize_import_value(raw_value);
155
+ if (value) {
156
+ alias[key] = value;
157
+ }
158
+ }
159
+ }
160
+
161
+ return alias;
162
+ }
@@ -609,7 +609,7 @@ function generate_params_type(params, outdir, config) {
609
609
  (param) =>
610
610
  `${/^\w+$/.test(param.name) ? param.name : `'${param.name}'`}${param.optional ? '?' : ''}: ${
611
611
  param.matcher
612
- ? `import('@sveltejs/kit').MatcherParam<(typeof import('${params_import}').params)[${JSON.stringify(param.matcher)}]>`
612
+ ? `Kit.MatcherParam<(typeof import('${params_import}').params)[${JSON.stringify(param.matcher)}]>`
613
613
  : 'string'
614
614
  }${param.optional ? ' | undefined' : ''}`
615
615
  )
@@ -717,6 +717,62 @@ export function tweak_types(content, is_server) {
717
717
  return _modified;
718
718
  }
719
719
 
720
+ /**
721
+ * @param {number} name_end position of the identifier the annotation is attached to
722
+ * @param {import('typescript').TypeNode} type_node
723
+ * @returns {string} the removed annotation's text
724
+ */
725
+ function strip_type_annotation(name_end, type_node) {
726
+ let a = type_node.pos;
727
+ const b = type_node.end;
728
+ while (is_whitespace(content[a])) a += 1;
729
+
730
+ const type = content.slice(a, b);
731
+ code.remove(name_end, type_node.end);
732
+ modified = true;
733
+ return type;
734
+ }
735
+
736
+ /**
737
+ * Types the first parameter of an untyped arrow/function expression.
738
+ * @param {import('typescript').Expression | undefined} rhs
739
+ * @param {string} type
740
+ * @returns {boolean} `false` if there was nothing to annotate
741
+ */
742
+ function annotate_first_param(rhs, type) {
743
+ if (
744
+ !rhs ||
745
+ !(ts.isArrowFunction(rhs) || ts.isFunctionExpression(rhs)) ||
746
+ !rhs.parameters.length
747
+ ) {
748
+ return false;
749
+ }
750
+
751
+ const arg = rhs.parameters[0];
752
+ const add_parens = content[arg.pos - 1] !== '(';
753
+
754
+ if (add_parens) code.prependRight(arg.pos, '(');
755
+
756
+ if (arg.type) return false;
757
+
758
+ code.appendLeft(arg.name.end, `: ${type}` + (add_parens ? ')' : ''));
759
+ return true;
760
+ }
761
+
762
+ /**
763
+ * @param {import('typescript').Expression | undefined} initializer
764
+ * @param {(prop: import('typescript').PropertyAssignment) => void} callback
765
+ */
766
+ function for_each_action(initializer, callback) {
767
+ if (initializer && ts.isObjectLiteralExpression(initializer)) {
768
+ for (const prop of initializer.properties) {
769
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
770
+ callback(prop);
771
+ }
772
+ }
773
+ }
774
+ }
775
+
720
776
  ast.forEachChild((node) => {
721
777
  if (ts.isFunctionDeclaration(node) && node.name?.text && node.name?.text === 'load') {
722
778
  // remove JSDoc comment above `export function load ...`
@@ -742,42 +798,13 @@ export function tweak_types(content, is_server) {
742
798
  // edge case — remove JSDoc comment above individual export
743
799
  replace_jsdoc_type_tags(declaration, declaration.initializer);
744
800
 
745
- // remove type from `export const load: Load ...`
746
801
  if (declaration.type) {
747
- let a = declaration.type.pos;
748
- const b = declaration.type.end;
749
- while (is_whitespace(content[a])) a += 1;
750
-
751
- const type = content.slice(a, b);
752
- code.remove(declaration.name.end, declaration.type.end);
802
+ const type = strip_type_annotation(declaration.name.end, declaration.type);
753
803
 
754
- const rhs = declaration.initializer;
755
-
756
- if (
757
- rhs &&
758
- (ts.isArrowFunction(rhs) || ts.isFunctionExpression(rhs)) &&
759
- rhs.parameters.length
760
- ) {
761
- const arg = rhs.parameters[0];
762
- const add_parens = content[arg.pos - 1] !== '(';
763
-
764
- if (add_parens) code.prependRight(arg.pos, '(');
765
-
766
- if (arg && !arg.type) {
767
- code.appendLeft(
768
- arg.name.end,
769
- `: Parameters<${type}>[0]` + (add_parens ? ')' : '')
770
- );
771
- } else {
772
- // prevent "type X is imported but not used" (isn't silenced by @ts-nocheck) when svelte-check runs
773
- code.append(`;null as any as ${type};`);
774
- }
775
- } else {
804
+ if (!annotate_first_param(declaration.initializer, `Parameters<${type}>[0]`)) {
776
805
  // prevent "type X is imported but not used" (isn't silenced by @ts-nocheck) when svelte-check runs
777
806
  code.append(`;null as any as ${type};`);
778
807
  }
779
-
780
- modified = true;
781
808
  }
782
809
  } else if (
783
810
  is_server &&
@@ -789,69 +816,34 @@ export function tweak_types(content, is_server) {
789
816
  const removed = replace_jsdoc_type_tags(node, declaration.initializer);
790
817
  // ... and move type to each individual action
791
818
  if (removed) {
792
- const rhs = declaration.initializer;
793
- if (ts.isObjectLiteralExpression(rhs)) {
794
- for (const prop of rhs.properties) {
795
- if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
796
- const rhs = prop.initializer;
797
- const replaced = replace_jsdoc_type_tags(prop, rhs);
798
- if (
799
- !replaced &&
800
- rhs &&
801
- (ts.isArrowFunction(rhs) || ts.isFunctionExpression(rhs)) &&
802
- rhs.parameters?.[0]
803
- ) {
804
- const name = ts.isIdentifier(rhs.parameters[0].name)
805
- ? rhs.parameters[0].name.text
806
- : 'event';
807
- code.prependRight(
808
- rhs.pos,
809
- `/** @param {import('./$types').RequestEvent} ${name} */ `
810
- );
811
- }
812
- }
819
+ for_each_action(declaration.initializer, (prop) => {
820
+ const rhs = prop.initializer;
821
+ const replaced = replace_jsdoc_type_tags(prop, rhs);
822
+ if (
823
+ !replaced &&
824
+ rhs &&
825
+ (ts.isArrowFunction(rhs) || ts.isFunctionExpression(rhs)) &&
826
+ rhs.parameters?.[0]
827
+ ) {
828
+ const name = ts.isIdentifier(rhs.parameters[0].name)
829
+ ? rhs.parameters[0].name.text
830
+ : 'event';
831
+ code.prependRight(
832
+ rhs.pos,
833
+ `/** @param {import('./$types').RequestEvent} ${name} */ `
834
+ );
813
835
  }
814
- }
836
+ });
815
837
  }
816
838
 
817
- // remove type from `export const actions: Actions ...`
818
839
  if (declaration.type) {
819
- let a = declaration.type.pos;
820
- const b = declaration.type.end;
821
- while (is_whitespace(content[a])) a += 1;
822
-
823
- const type = content.slice(a, b);
824
- code.remove(declaration.name.end, declaration.type.end);
840
+ const type = strip_type_annotation(declaration.name.end, declaration.type);
825
841
  code.append(`;null as any as ${type};`);
826
- modified = true;
827
842
 
828
843
  // ... and move type to each individual action
829
- const rhs = declaration.initializer;
830
- if (ts.isObjectLiteralExpression(rhs)) {
831
- for (const prop of rhs.properties) {
832
- if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
833
- const rhs = prop.initializer;
834
-
835
- if (
836
- rhs &&
837
- (ts.isArrowFunction(rhs) || ts.isFunctionExpression(rhs)) &&
838
- rhs.parameters.length
839
- ) {
840
- const arg = rhs.parameters[0];
841
- const add_parens = content[arg.pos - 1] !== '(';
842
-
843
- if (add_parens) code.prependRight(arg.pos, '(');
844
-
845
- if (arg && !arg.type) {
846
- code.appendLeft(
847
- arg.name.end,
848
- ": import('./$types').RequestEvent" + (add_parens ? ')' : '')
849
- );
850
- }
851
- }
852
- }
853
- }
854
- }
844
+ for_each_action(declaration.initializer, (prop) => {
845
+ annotate_first_param(prop.initializer, "import('./$types').RequestEvent");
846
+ });
855
847
  }
856
848
  }
857
849
  }