@sveltejs/kit 1.0.0-next.476 → 1.0.0-next.479
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 +1 -1
- package/src/core/config/default-error.html +2 -2
- package/src/core/config/index.js +7 -2
- package/src/core/config/options.js +13 -1
- package/src/core/sync/sync.js +1 -1
- package/src/core/sync/write_client_manifest.js +13 -1
- package/src/exports/index.js +6 -15
- package/src/exports/node/index.js +28 -10
- package/src/exports/vite/build/build_server.js +23 -8
- package/src/exports/vite/dev/index.js +51 -29
- package/src/exports/vite/index.js +18 -12
- package/src/exports/vite/preview/index.js +5 -2
- package/src/exports/vite/utils.js +234 -157
- package/src/runtime/app/forms.js +15 -16
- package/src/runtime/client/ambient.d.ts +3 -1
- package/src/runtime/client/client.js +37 -26
- package/src/runtime/client/types.d.ts +2 -4
- package/src/runtime/control.js +10 -12
- package/src/runtime/server/data/index.js +5 -15
- package/src/runtime/server/index.js +34 -30
- package/src/runtime/server/page/actions.js +2 -2
- package/src/runtime/server/page/index.js +14 -18
- package/src/runtime/server/page/render.js +2 -15
- package/src/runtime/server/page/respond_with_error.js +10 -10
- package/src/runtime/server/page/types.d.ts +0 -6
- package/src/runtime/server/utils.js +19 -61
- package/src/utils/filesystem.js +29 -0
- package/types/ambient.d.ts +19 -12
- package/types/index.d.ts +28 -14
- package/types/internal.d.ts +20 -13
|
@@ -1,27 +1,230 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
1
|
import path from 'path';
|
|
3
2
|
import { loadConfigFromFile, loadEnv, normalizePath } from 'vite';
|
|
4
3
|
import { runtime_directory } from '../../core/utils.js';
|
|
5
4
|
import { posixify } from '../../utils/filesystem.js';
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'/@id/__x00__$env/static/private', // dev
|
|
11
|
-
'\0$env/static/private' // prod
|
|
12
|
-
]);
|
|
6
|
+
class IllegalModuleGuard {
|
|
7
|
+
/** @type {string} */
|
|
8
|
+
#lib_dir;
|
|
13
9
|
|
|
14
|
-
/** @
|
|
15
|
-
|
|
16
|
-
if (illegal_imports.has(id)) return true;
|
|
10
|
+
/** @type {string} */
|
|
11
|
+
#server_dir;
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
/** @type {string} */
|
|
14
|
+
#node_modules_dir = normalizePath(path.resolve(process.cwd(), 'node_modules'));
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
/** @type {string} */
|
|
17
|
+
#cwd = normalizePath(process.cwd());
|
|
23
18
|
|
|
24
|
-
|
|
19
|
+
/** @type {Set<string>} */
|
|
20
|
+
#illegal_imports = new Set([
|
|
21
|
+
'/@id/__x00__$env/dynamic/private', //dev
|
|
22
|
+
'\0$env/dynamic/private', // prod
|
|
23
|
+
'/@id/__x00__$env/static/private', // dev
|
|
24
|
+
'\0$env/static/private' // prod
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/** @type {Array<import('types').ImportNode>} */
|
|
28
|
+
#chain = [];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} lib_dir
|
|
32
|
+
*/
|
|
33
|
+
constructor(lib_dir) {
|
|
34
|
+
this.#lib_dir = normalizePath(lib_dir);
|
|
35
|
+
this.#server_dir = normalizePath(path.resolve(lib_dir, 'server'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {import('types').ImportNode} node
|
|
40
|
+
* @returns {void}
|
|
41
|
+
*/
|
|
42
|
+
assert_legal(node) {
|
|
43
|
+
this.#chain.push(node);
|
|
44
|
+
for (const child of node.children) {
|
|
45
|
+
if (this.#is_illegal(child.name)) {
|
|
46
|
+
this.#chain.push(child);
|
|
47
|
+
throw new Error(this.#format_illegal_import_chain(this.#chain));
|
|
48
|
+
}
|
|
49
|
+
this.assert_legal(child);
|
|
50
|
+
}
|
|
51
|
+
this.#chain.pop();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* `true` if the provided ID represents a server-only module, else `false`.
|
|
56
|
+
* @param {string} module_id
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
#is_illegal(module_id) {
|
|
60
|
+
if (this.#is_kit_illegal(module_id) || this.#is_user_illegal(module_id)) return true;
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* `true` if the provided ID represents a Kit-defined server-only module, else `false`.
|
|
66
|
+
* @param {string} module_id
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
*/
|
|
69
|
+
#is_kit_illegal(module_id) {
|
|
70
|
+
return this.#illegal_imports.has(module_id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* `true` if the provided ID represents a user-defined server-only module, else `false`.
|
|
75
|
+
* @param {string} module_id
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
#is_user_illegal(module_id) {
|
|
79
|
+
if (module_id.startsWith(this.#server_dir)) return true;
|
|
80
|
+
|
|
81
|
+
// files outside the project root are ignored
|
|
82
|
+
if (!module_id.startsWith(this.#cwd)) return false;
|
|
83
|
+
|
|
84
|
+
// so are files inside node_modules
|
|
85
|
+
if (module_id.startsWith(this.#node_modules_dir)) return false;
|
|
86
|
+
|
|
87
|
+
return /.*\.server\..+/.test(path.basename(module_id));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} str
|
|
92
|
+
* @param {number} times
|
|
93
|
+
*/
|
|
94
|
+
#repeat(str, times) {
|
|
95
|
+
return new Array(times + 1).join(str);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create a formatted error for an illegal import.
|
|
100
|
+
* @param {Array<{name: string, dynamic: boolean}>} stack
|
|
101
|
+
*/
|
|
102
|
+
#format_illegal_import_chain(stack) {
|
|
103
|
+
const dev_virtual_prefix = '/@id/__x00__';
|
|
104
|
+
const prod_virtual_prefix = '\0';
|
|
105
|
+
|
|
106
|
+
stack = stack.map((file) => {
|
|
107
|
+
if (file.name.startsWith(dev_virtual_prefix)) {
|
|
108
|
+
return { ...file, name: file.name.replace(dev_virtual_prefix, '') };
|
|
109
|
+
}
|
|
110
|
+
if (file.name.startsWith(prod_virtual_prefix)) {
|
|
111
|
+
return { ...file, name: file.name.replace(prod_virtual_prefix, '') };
|
|
112
|
+
}
|
|
113
|
+
if (file.name.startsWith(this.#lib_dir)) {
|
|
114
|
+
return { ...file, name: file.name.replace(this.#lib_dir, '$lib') };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { ...file, name: path.relative(process.cwd(), file.name) };
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const pyramid = stack
|
|
121
|
+
.map(
|
|
122
|
+
(file, i) =>
|
|
123
|
+
`${this.#repeat(' ', i * 2)}- ${file.name} ${
|
|
124
|
+
file.dynamic ? '(imported by parent dynamically)' : ''
|
|
125
|
+
}`
|
|
126
|
+
)
|
|
127
|
+
.join('\n');
|
|
128
|
+
|
|
129
|
+
return `Cannot import ${stack.at(-1)?.name} into client-side code:\n${pyramid}`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class RollupImportGraph {
|
|
134
|
+
/** @type {(id: string) => import('rollup').ModuleInfo | null} */
|
|
135
|
+
#node_getter;
|
|
136
|
+
|
|
137
|
+
/** @type {import('rollup').ModuleInfo} */
|
|
138
|
+
#module_info;
|
|
139
|
+
|
|
140
|
+
/** @type {string} */
|
|
141
|
+
name;
|
|
142
|
+
|
|
143
|
+
/** @type {boolean} */
|
|
144
|
+
dynamic;
|
|
145
|
+
|
|
146
|
+
/** @type {Set<string>} */
|
|
147
|
+
#seen;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {(id: string) => import('rollup').ModuleInfo | null} node_getter
|
|
151
|
+
* @param {import('rollup').ModuleInfo} node
|
|
152
|
+
* @param {boolean} dynamic
|
|
153
|
+
* @param {Set<string>} seen
|
|
154
|
+
*/
|
|
155
|
+
constructor(node_getter, node, dynamic = false, seen = new Set()) {
|
|
156
|
+
this.#node_getter = node_getter;
|
|
157
|
+
this.#module_info = node;
|
|
158
|
+
this.name = remove_query_from_path(normalizePath(node.id));
|
|
159
|
+
this.dynamic = dynamic;
|
|
160
|
+
this.#seen = seen;
|
|
161
|
+
void (/** @type {import('types').ImportNode} */ (this));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
get children() {
|
|
165
|
+
return this.#children();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
*#children() {
|
|
169
|
+
if (this.#seen.has(this.name)) return;
|
|
170
|
+
this.#seen.add(this.name);
|
|
171
|
+
for (const id of this.#module_info.importedIds) {
|
|
172
|
+
const child = this.#node_getter(id);
|
|
173
|
+
if (child === null) return;
|
|
174
|
+
yield new RollupImportGraph(this.#node_getter, child, false, this.#seen);
|
|
175
|
+
}
|
|
176
|
+
for (const id of this.#module_info.dynamicallyImportedIds) {
|
|
177
|
+
const child = this.#node_getter(id);
|
|
178
|
+
if (child === null) return;
|
|
179
|
+
yield new RollupImportGraph(this.#node_getter, child, true, this.#seen);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
class ViteImportGraph {
|
|
185
|
+
/** @type {Set<string>} */
|
|
186
|
+
#module_types;
|
|
187
|
+
|
|
188
|
+
/** @type {import('vite').ModuleNode} */
|
|
189
|
+
#module_info;
|
|
190
|
+
|
|
191
|
+
/** @type {string} */
|
|
192
|
+
name;
|
|
193
|
+
|
|
194
|
+
/** @type {Set<string>} */
|
|
195
|
+
#seen;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @param {Set<string>} module_types Module types to analyze, eg '.js', '.ts', etc.
|
|
199
|
+
* @param {import('vite').ModuleNode} node
|
|
200
|
+
* @param {Set<string>} seen
|
|
201
|
+
*/
|
|
202
|
+
constructor(module_types, node, seen = new Set()) {
|
|
203
|
+
this.#module_types = module_types;
|
|
204
|
+
this.#module_info = node;
|
|
205
|
+
this.name = remove_query_from_path(normalizePath(node.id ?? ''));
|
|
206
|
+
this.#seen = seen;
|
|
207
|
+
void (/** @type {import('types').ImportNode} */ (this));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
get dynamic() {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
get children() {
|
|
215
|
+
return this.#children();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
*#children() {
|
|
219
|
+
if (this.#seen.has(this.name)) return;
|
|
220
|
+
this.#seen.add(this.name);
|
|
221
|
+
for (const child of this.#module_info.importedModules) {
|
|
222
|
+
if (!this.#module_types.has(path.extname(this.name))) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
yield new ViteImportGraph(this.#module_types, child, this.#seen);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
25
228
|
}
|
|
26
229
|
|
|
27
230
|
/**
|
|
@@ -163,80 +366,6 @@ function escape_for_regexp(str) {
|
|
|
163
366
|
return str.replace(/[.*+?^${}()|[\]\\]/g, (match) => '\\' + match);
|
|
164
367
|
}
|
|
165
368
|
|
|
166
|
-
/**
|
|
167
|
-
* Given an entry point like [cwd]/src/hooks, returns a filename like [cwd]/src/hooks.js or [cwd]/src/hooks/index.js
|
|
168
|
-
* @param {string} entry
|
|
169
|
-
* @returns {string|null}
|
|
170
|
-
*/
|
|
171
|
-
export function resolve_entry(entry) {
|
|
172
|
-
if (fs.existsSync(entry)) {
|
|
173
|
-
const stats = fs.statSync(entry);
|
|
174
|
-
if (stats.isDirectory()) {
|
|
175
|
-
return resolve_entry(path.join(entry, 'index'));
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return entry;
|
|
179
|
-
} else {
|
|
180
|
-
const dir = path.dirname(entry);
|
|
181
|
-
|
|
182
|
-
if (fs.existsSync(dir)) {
|
|
183
|
-
const base = path.basename(entry);
|
|
184
|
-
const files = fs.readdirSync(dir);
|
|
185
|
-
|
|
186
|
-
const found = files.find((file) => file.replace(/\.[^.]+$/, '') === base);
|
|
187
|
-
|
|
188
|
-
if (found) return path.join(dir, found);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* @param {string} str
|
|
197
|
-
* @param {number} times
|
|
198
|
-
*/
|
|
199
|
-
function repeat(str, times) {
|
|
200
|
-
return new Array(times + 1).join(str);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Create a formatted error for an illegal import.
|
|
205
|
-
* @param {Array<{name: string, dynamic: boolean}>} stack
|
|
206
|
-
* @param {string} lib_dir
|
|
207
|
-
*/
|
|
208
|
-
function format_illegal_import_chain(stack, lib_dir) {
|
|
209
|
-
const dev_virtual_prefix = '/@id/__x00__';
|
|
210
|
-
const prod_virtual_prefix = '\0';
|
|
211
|
-
|
|
212
|
-
stack = stack.map((file) => {
|
|
213
|
-
if (file.name.startsWith(dev_virtual_prefix)) {
|
|
214
|
-
return { ...file, name: file.name.replace(dev_virtual_prefix, '') };
|
|
215
|
-
}
|
|
216
|
-
if (file.name.startsWith(prod_virtual_prefix)) {
|
|
217
|
-
return { ...file, name: file.name.replace(prod_virtual_prefix, '') };
|
|
218
|
-
}
|
|
219
|
-
if (file.name.startsWith(lib_dir)) {
|
|
220
|
-
return { ...file, name: file.name.replace(lib_dir, '$lib') };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return { ...file, name: path.relative(process.cwd(), file.name) };
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
const pyramid = stack
|
|
227
|
-
.map(
|
|
228
|
-
(file, i) =>
|
|
229
|
-
`${repeat(' ', i * 2)}- ${file.name} ${
|
|
230
|
-
file.dynamic ? '(imported by parent dynamically)' : ''
|
|
231
|
-
}`
|
|
232
|
-
)
|
|
233
|
-
.join('\n');
|
|
234
|
-
|
|
235
|
-
return `Cannot import ${stack.at(-1)?.name} into client-side code:\n${pyramid}`;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const node_modules_dir = path.resolve(process.cwd(), 'node_modules');
|
|
239
|
-
|
|
240
369
|
/**
|
|
241
370
|
* Load environment variables from process.env and .env files
|
|
242
371
|
* @param {import('types').ValidatedKitConfig['env']} env_config
|
|
@@ -251,16 +380,6 @@ export function get_env(env_config, mode) {
|
|
|
251
380
|
};
|
|
252
381
|
}
|
|
253
382
|
|
|
254
|
-
/**
|
|
255
|
-
* @param {(id: string) => import('rollup').ModuleInfo | null} node_getter
|
|
256
|
-
* @param {import('rollup').ModuleInfo} node
|
|
257
|
-
* @param {string} lib_dir
|
|
258
|
-
*/
|
|
259
|
-
export function prevent_illegal_rollup_imports(node_getter, node, lib_dir) {
|
|
260
|
-
const chain = find_illegal_rollup_imports(node_getter, node, false);
|
|
261
|
-
if (chain) throw new Error(format_illegal_import_chain(chain, lib_dir));
|
|
262
|
-
}
|
|
263
|
-
|
|
264
383
|
const query_pattern = /\?.*$/s;
|
|
265
384
|
|
|
266
385
|
/** @param {string} path */
|
|
@@ -268,37 +387,6 @@ function remove_query_from_path(path) {
|
|
|
268
387
|
return path.replace(query_pattern, '');
|
|
269
388
|
}
|
|
270
389
|
|
|
271
|
-
/**
|
|
272
|
-
* @param {(id: string) => import('rollup').ModuleInfo | null} node_getter
|
|
273
|
-
* @param {import('rollup').ModuleInfo} node
|
|
274
|
-
* @param {boolean} dynamic
|
|
275
|
-
* @param {Set<string>} seen
|
|
276
|
-
* @returns {Array<import('types').ImportNode> | null}
|
|
277
|
-
*/
|
|
278
|
-
const find_illegal_rollup_imports = (node_getter, node, dynamic, seen = new Set()) => {
|
|
279
|
-
const name = remove_query_from_path(normalizePath(node.id));
|
|
280
|
-
if (seen.has(name)) return null;
|
|
281
|
-
seen.add(name);
|
|
282
|
-
|
|
283
|
-
if (is_illegal(name)) {
|
|
284
|
-
return [{ name, dynamic }];
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
for (const id of node.importedIds) {
|
|
288
|
-
const child = node_getter(id);
|
|
289
|
-
const chain = child && find_illegal_rollup_imports(node_getter, child, false, seen);
|
|
290
|
-
if (chain) return [{ name, dynamic }, ...chain];
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
for (const id of node.dynamicallyImportedIds) {
|
|
294
|
-
const child = node_getter(id);
|
|
295
|
-
const chain = child && find_illegal_rollup_imports(node_getter, child, true, seen);
|
|
296
|
-
if (chain) return [{ name, dynamic }, ...chain];
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return null;
|
|
300
|
-
};
|
|
301
|
-
|
|
302
390
|
/**
|
|
303
391
|
* Vite does some weird things with import trees in dev
|
|
304
392
|
* for example, a Tailwind app.css will appear to import
|
|
@@ -308,6 +396,7 @@ const find_illegal_rollup_imports = (node_getter, node, dynamic, seen = new Set(
|
|
|
308
396
|
*/
|
|
309
397
|
const get_module_types = (config_module_types) => {
|
|
310
398
|
return new Set([
|
|
399
|
+
'',
|
|
311
400
|
'.ts',
|
|
312
401
|
'.js',
|
|
313
402
|
'.svelte',
|
|
@@ -324,38 +413,26 @@ const get_module_types = (config_module_types) => {
|
|
|
324
413
|
|
|
325
414
|
/**
|
|
326
415
|
* Throw an error if a private module is imported from a client-side node.
|
|
327
|
-
* @param {import('
|
|
416
|
+
* @param {(id: string) => import('rollup').ModuleInfo | null} node_getter
|
|
417
|
+
* @param {import('rollup').ModuleInfo} node
|
|
328
418
|
* @param {string} lib_dir
|
|
329
|
-
* @
|
|
419
|
+
* @returns {void}
|
|
330
420
|
*/
|
|
331
|
-
export function
|
|
332
|
-
const
|
|
333
|
-
|
|
421
|
+
export function prevent_illegal_rollup_imports(node_getter, node, lib_dir) {
|
|
422
|
+
const graph = new RollupImportGraph(node_getter, node);
|
|
423
|
+
const guard = new IllegalModuleGuard(lib_dir);
|
|
424
|
+
guard.assert_legal(graph);
|
|
334
425
|
}
|
|
335
426
|
|
|
336
427
|
/**
|
|
428
|
+
* Throw an error if a private module is imported from a client-side node.
|
|
337
429
|
* @param {import('vite').ModuleNode} node
|
|
338
|
-
* @param {
|
|
339
|
-
* @param {
|
|
340
|
-
* @returns {
|
|
430
|
+
* @param {string} lib_dir
|
|
431
|
+
* @param {Iterable<string>} module_types File extensions to analyze in addition to the defaults: `.ts`, `.js`, etc.
|
|
432
|
+
* @returns {void}
|
|
341
433
|
*/
|
|
342
|
-
function
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
if (path.extname(name) !== '' && (seen.has(name) || !module_types.has(path.extname(name)))) {
|
|
347
|
-
return null;
|
|
348
|
-
}
|
|
349
|
-
seen.add(name);
|
|
350
|
-
|
|
351
|
-
if (is_illegal(name)) {
|
|
352
|
-
return [{ name, dynamic: false }];
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
for (const child of node.importedModules) {
|
|
356
|
-
const chain = child && find_illegal_vite_imports(child, module_types, seen);
|
|
357
|
-
if (chain) return [{ name, dynamic: false }, ...chain];
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return null;
|
|
434
|
+
export function prevent_illegal_vite_imports(node, lib_dir, module_types) {
|
|
435
|
+
const graph = new ViteImportGraph(get_module_types(module_types), node);
|
|
436
|
+
const guard = new IllegalModuleGuard(lib_dir);
|
|
437
|
+
guard.assert_legal(graph);
|
|
361
438
|
}
|
package/src/runtime/app/forms.js
CHANGED
|
@@ -16,46 +16,43 @@ const ssr = import.meta.env.SSR;
|
|
|
16
16
|
export const applyAction = ssr ? guard('applyAction') : client.apply_action;
|
|
17
17
|
|
|
18
18
|
/** @type {import('$app/forms').enhance} */
|
|
19
|
-
export function enhance(
|
|
19
|
+
export function enhance(form, submit = () => {}) {
|
|
20
20
|
/**
|
|
21
21
|
* @param {{
|
|
22
|
-
*
|
|
23
|
-
* form: HTMLFormElement;
|
|
22
|
+
* action: string;
|
|
24
23
|
* result: import('types').ActionResult;
|
|
25
24
|
* }} opts
|
|
26
25
|
*/
|
|
27
|
-
const fallback_callback = async ({
|
|
26
|
+
const fallback_callback = async ({ action, result }) => {
|
|
28
27
|
if (result.type === 'success') {
|
|
29
28
|
await invalidateAll();
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
const action = element.formAction ?? form.action;
|
|
33
|
-
|
|
34
31
|
if (location.origin + location.pathname === action.split('?')[0]) {
|
|
35
32
|
applyAction(result);
|
|
36
33
|
}
|
|
37
34
|
};
|
|
38
35
|
|
|
39
|
-
const form =
|
|
40
|
-
element instanceof HTMLFormElement ? element : /** @type {HTMLFormElement} */ (element.form);
|
|
41
|
-
if (!form) throw new Error('Element is not associated with a form');
|
|
42
|
-
|
|
43
36
|
/** @param {SubmitEvent} event */
|
|
44
37
|
async function handle_submit(event) {
|
|
45
38
|
event.preventDefault();
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
// We can't do submitter.formAction directly because that property is always set
|
|
41
|
+
const action = event.submitter?.hasAttribute('formaction')
|
|
42
|
+
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formAction
|
|
43
|
+
: form.action;
|
|
49
44
|
const data = new FormData(form);
|
|
45
|
+
const controller = new AbortController();
|
|
50
46
|
|
|
51
47
|
let cancelled = false;
|
|
52
48
|
const cancel = () => (cancelled = true);
|
|
53
49
|
|
|
54
50
|
const callback =
|
|
55
51
|
submit({
|
|
56
|
-
|
|
57
|
-
data,
|
|
52
|
+
action,
|
|
58
53
|
cancel,
|
|
54
|
+
controller,
|
|
55
|
+
data,
|
|
59
56
|
form
|
|
60
57
|
}) ?? fallback_callback;
|
|
61
58
|
if (cancelled) return;
|
|
@@ -69,16 +66,18 @@ export function enhance(element, submit = () => {}) {
|
|
|
69
66
|
headers: {
|
|
70
67
|
accept: 'application/json'
|
|
71
68
|
},
|
|
72
|
-
body: data
|
|
69
|
+
body: data,
|
|
70
|
+
signal: controller.signal
|
|
73
71
|
});
|
|
74
72
|
|
|
75
73
|
result = await response.json();
|
|
76
74
|
} catch (error) {
|
|
75
|
+
if (/** @type {any} */ (error)?.name === 'AbortError') return;
|
|
77
76
|
result = { type: 'error', error };
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
callback({
|
|
81
|
-
|
|
80
|
+
action,
|
|
82
81
|
data,
|
|
83
82
|
form,
|
|
84
83
|
// @ts-expect-error generic constraints stuff we don't care about
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
declare module '__GENERATED__/client-manifest.js' {
|
|
2
|
-
import { CSRPageNodeLoader, ParamMatcher } from 'types';
|
|
2
|
+
import { CSRPageNodeLoader, ClientHooks, ParamMatcher } from 'types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A list of all the error/layout/page nodes used in the app
|
|
@@ -21,4 +21,6 @@ declare module '__GENERATED__/client-manifest.js' {
|
|
|
21
21
|
export const dictionary: Record<string, [leaf: number, layouts: number[], errors?: number[]]>;
|
|
22
22
|
|
|
23
23
|
export const matchers: Record<string, ParamMatcher>;
|
|
24
|
+
|
|
25
|
+
export const hooks: ClientHooks;
|
|
24
26
|
}
|
|
@@ -4,10 +4,9 @@ import { make_trackable, decode_params, normalize_path } from '../../utils/url.j
|
|
|
4
4
|
import { find_anchor, get_base_uri, scroll_state } from './utils.js';
|
|
5
5
|
import { lock_fetch, unlock_fetch, initial_fetch, subsequent_fetch } from './fetcher.js';
|
|
6
6
|
import { parse } from './parse.js';
|
|
7
|
-
import { error } from '../../exports/index.js';
|
|
8
7
|
|
|
9
8
|
import Root from '__GENERATED__/root.svelte';
|
|
10
|
-
import { nodes, server_loads, dictionary, matchers } from '__GENERATED__/client-manifest.js';
|
|
9
|
+
import { nodes, server_loads, dictionary, matchers, hooks } from '__GENERATED__/client-manifest.js';
|
|
11
10
|
import { HttpError, Redirect } from '../control.js';
|
|
12
11
|
import { stores } from './singletons.js';
|
|
13
12
|
import { DATA_SUFFIX } from '../../constants.js';
|
|
@@ -386,7 +385,7 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
386
385
|
* params: Record<string, string>;
|
|
387
386
|
* branch: Array<import('./types').BranchNode | undefined>;
|
|
388
387
|
* status: number;
|
|
389
|
-
* error:
|
|
388
|
+
* error: App.PageError | null;
|
|
390
389
|
* route: import('types').CSRRoute | null;
|
|
391
390
|
* form?: Record<string, any> | null;
|
|
392
391
|
* }} opts
|
|
@@ -756,12 +755,8 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
756
755
|
parent_changed = true;
|
|
757
756
|
|
|
758
757
|
if (server_data_node?.type === 'error') {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
throw error(server_data_node.httperror.status, server_data_node.httperror.message);
|
|
762
|
-
} else {
|
|
763
|
-
throw server_data_node.error;
|
|
764
|
-
}
|
|
758
|
+
// rethrow and catch below
|
|
759
|
+
throw server_data_node;
|
|
765
760
|
}
|
|
766
761
|
|
|
767
762
|
return load_node({
|
|
@@ -790,17 +785,29 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
790
785
|
if (loaders[i]) {
|
|
791
786
|
try {
|
|
792
787
|
branch.push(await branch_promises[i]);
|
|
793
|
-
} catch (
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
if (error instanceof Redirect) {
|
|
788
|
+
} catch (err) {
|
|
789
|
+
if (err instanceof Redirect) {
|
|
797
790
|
return {
|
|
798
791
|
type: 'redirect',
|
|
799
|
-
location:
|
|
792
|
+
location: err.location
|
|
800
793
|
};
|
|
801
794
|
}
|
|
802
795
|
|
|
803
|
-
|
|
796
|
+
let status = 500;
|
|
797
|
+
/** @type {App.PageError} */
|
|
798
|
+
let error;
|
|
799
|
+
|
|
800
|
+
if (server_data_nodes?.includes(/** @type {import('types').ServerErrorNode} */ (err))) {
|
|
801
|
+
// this is the server error rethrown above, reconstruct but don't invoke
|
|
802
|
+
// the client error handler; it should've already been handled on the server
|
|
803
|
+
status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
|
|
804
|
+
error = /** @type {import('types').ServerErrorNode} */ (err).error;
|
|
805
|
+
} else if (err instanceof HttpError) {
|
|
806
|
+
status = err.status;
|
|
807
|
+
error = err.body;
|
|
808
|
+
} else {
|
|
809
|
+
error = handle_error(err, { params, url, routeId: route.id });
|
|
810
|
+
}
|
|
804
811
|
|
|
805
812
|
while (i--) {
|
|
806
813
|
if (errors[i]) {
|
|
@@ -920,7 +927,10 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
920
927
|
params,
|
|
921
928
|
branch: [root_layout, root_error],
|
|
922
929
|
status,
|
|
923
|
-
error
|
|
930
|
+
error:
|
|
931
|
+
error instanceof HttpError
|
|
932
|
+
? error.body
|
|
933
|
+
: handle_error(error, { url, params, routeId: null }),
|
|
924
934
|
route: null
|
|
925
935
|
});
|
|
926
936
|
}
|
|
@@ -1395,7 +1405,7 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
1395
1405
|
|
|
1396
1406
|
_hydrate: async ({
|
|
1397
1407
|
status,
|
|
1398
|
-
error
|
|
1408
|
+
error,
|
|
1399
1409
|
node_ids,
|
|
1400
1410
|
params,
|
|
1401
1411
|
routeId,
|
|
@@ -1432,15 +1442,7 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
1432
1442
|
params,
|
|
1433
1443
|
branch: await Promise.all(branch_promises),
|
|
1434
1444
|
status,
|
|
1435
|
-
error
|
|
1436
|
-
?.__is_http_error
|
|
1437
|
-
? new HttpError(
|
|
1438
|
-
/** @type {import('../server/page/types').SerializedHttpError} */ (
|
|
1439
|
-
original_error
|
|
1440
|
-
).status,
|
|
1441
|
-
original_error.message
|
|
1442
|
-
)
|
|
1443
|
-
: original_error,
|
|
1445
|
+
error,
|
|
1444
1446
|
form,
|
|
1445
1447
|
route: routes.find((route) => route.id === routeId) ?? null
|
|
1446
1448
|
});
|
|
@@ -1498,6 +1500,15 @@ async function load_data(url, invalid) {
|
|
|
1498
1500
|
return server_data;
|
|
1499
1501
|
}
|
|
1500
1502
|
|
|
1503
|
+
/**
|
|
1504
|
+
* @param {unknown} error
|
|
1505
|
+
* @param {import('types').NavigationEvent} event
|
|
1506
|
+
* @returns {App.PageError}
|
|
1507
|
+
*/
|
|
1508
|
+
function handle_error(error, event) {
|
|
1509
|
+
return hooks.handleError({ error, event }) ?? /** @type {any} */ ({ message: 'Internal Error' });
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1501
1512
|
// TODO remove for 1.0
|
|
1502
1513
|
const properties = [
|
|
1503
1514
|
'hash',
|
|
@@ -9,8 +9,6 @@ import {
|
|
|
9
9
|
prefetchRoutes
|
|
10
10
|
} from '$app/navigation';
|
|
11
11
|
import { CSRPageNode, CSRPageNodeLoader, CSRRoute, Uses } from 'types';
|
|
12
|
-
import { HttpError } from '../control.js';
|
|
13
|
-
import { SerializedHttpError } from '../server/page/types.js';
|
|
14
12
|
|
|
15
13
|
export interface Client {
|
|
16
14
|
// public API, exposed via $app/navigation
|
|
@@ -27,7 +25,7 @@ export interface Client {
|
|
|
27
25
|
// private API
|
|
28
26
|
_hydrate: (opts: {
|
|
29
27
|
status: number;
|
|
30
|
-
error:
|
|
28
|
+
error: App.PageError;
|
|
31
29
|
node_ids: number[];
|
|
32
30
|
params: Record<string, string>;
|
|
33
31
|
routeId: string | null;
|
|
@@ -79,7 +77,7 @@ export interface DataNode {
|
|
|
79
77
|
|
|
80
78
|
export interface NavigationState {
|
|
81
79
|
branch: Array<BranchNode | undefined>;
|
|
82
|
-
error:
|
|
80
|
+
error: App.PageError | null;
|
|
83
81
|
params: Record<string, string>;
|
|
84
82
|
route: CSRRoute | null;
|
|
85
83
|
session_id: number;
|