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

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 (33) hide show
  1. package/package.json +2 -2
  2. package/src/core/postbuild/analyse.js +0 -8
  3. package/src/core/postbuild/prerender.js +2 -0
  4. package/src/exports/public.d.ts +1 -1
  5. package/src/exports/vite/build/build_server.js +43 -58
  6. package/src/exports/vite/build/remote.js +18 -11
  7. package/src/exports/vite/build/utils.js +0 -8
  8. package/src/exports/vite/index.js +220 -216
  9. package/src/runtime/app/server/remote/command.js +0 -3
  10. package/src/runtime/app/server/remote/form.js +18 -13
  11. package/src/runtime/app/server/remote/prerender.js +28 -34
  12. package/src/runtime/app/server/remote/query.js +105 -94
  13. package/src/runtime/app/server/remote/requested.js +14 -10
  14. package/src/runtime/app/server/remote/shared.js +25 -19
  15. package/src/runtime/client/client.js +25 -15
  16. package/src/runtime/client/remote-functions/command.svelte.js +5 -30
  17. package/src/runtime/client/remote-functions/form.svelte.js +62 -82
  18. package/src/runtime/client/remote-functions/prerender.svelte.js +14 -6
  19. package/src/runtime/client/remote-functions/query/index.js +6 -14
  20. package/src/runtime/client/remote-functions/query/instance.svelte.js +37 -8
  21. package/src/runtime/client/remote-functions/query/proxy.js +3 -3
  22. package/src/runtime/client/remote-functions/query-batch.svelte.js +59 -68
  23. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +21 -6
  24. package/src/runtime/client/remote-functions/shared.svelte.js +76 -59
  25. package/src/runtime/server/page/render.js +20 -80
  26. package/src/runtime/server/page/server_routing.js +20 -15
  27. package/src/runtime/server/remote.js +296 -204
  28. package/src/runtime/server/respond.js +4 -2
  29. package/src/types/global-private.d.ts +3 -3
  30. package/src/types/internal.d.ts +53 -34
  31. package/src/version.js +1 -1
  32. package/types/index.d.ts +6 -5
  33. package/types/index.d.ts.map +1 -1
@@ -1,12 +1,9 @@
1
1
  /** @import { RemoteQueryFunction } from '@sveltejs/kit' */
2
- /** @import { RemoteFunctionResponse } from 'types' */
3
2
  import { app_dir, base } from '$app/paths/internal/client';
4
- import { app, goto } from '../client.js';
5
- import { get_remote_request_headers, QUERY_FUNCTION_ID } from './shared.svelte.js';
3
+ import { goto } from '../client.js';
4
+ import { get_remote_request_headers, QUERY_FUNCTION_ID, remote_request } from './shared.svelte.js';
6
5
  import { QueryProxy } from './query/proxy.js';
7
- import * as devalue from 'devalue';
8
- import { HttpError, Redirect } from '@sveltejs/kit/internal';
9
- import { hydratable } from 'svelte';
6
+ import { HttpError } from '@sveltejs/kit/internal';
10
7
 
11
8
  /**
12
9
  * @param {string} id
@@ -18,84 +15,78 @@ export function query_batch(id) {
18
15
 
19
16
  /** @type {RemoteQueryFunction<any, any>} */
20
17
  const wrapper = (arg) => {
21
- return new QueryProxy(id, arg, async (key, payload) => {
22
- const serialized = await hydratable(key, () => {
23
- return new Promise((resolve, reject) => {
24
- // create_remote_function caches identical calls, but in case a refresh to the same query is called multiple times this function
25
- // is invoked multiple times with the same payload, so we need to deduplicate here
26
- const entry = batching.get(payload) ?? [];
27
- entry.push({ resolve, reject });
28
- batching.set(payload, entry);
18
+ return new QueryProxy(id, arg, async (payload) => {
19
+ return await new Promise((resolve, reject) => {
20
+ // create_remote_function caches identical calls, but in case a refresh to the same query is called multiple times this function
21
+ // is invoked multiple times with the same payload, so we need to deduplicate here
22
+ const entry = batching.get(payload) ?? [];
23
+ entry.push({ resolve, reject });
24
+ batching.set(payload, entry);
29
25
 
30
- if (batching.size > 1) return;
26
+ if (batching.size > 1) return;
31
27
 
32
- // Do this here, after await Svelte' reactivity context is gone.
33
- // TODO is it possible to have batches of the same key
34
- // but in different forks/async contexts and in the same macrotask?
35
- // If so this would potentially be buggy
36
- const headers = {
37
- 'Content-Type': 'application/json',
38
- ...get_remote_request_headers()
39
- };
28
+ // Do this here, after await Svelte' reactivity context is gone.
29
+ // TODO is it possible to have batches of the same key
30
+ // but in different forks/async contexts and in the same macrotask?
31
+ // If so this would potentially be buggy
32
+ const headers = {
33
+ 'Content-Type': 'application/json',
34
+ ...get_remote_request_headers()
35
+ };
40
36
 
41
- // Wait for the next macrotask - don't use microtask as Svelte runtime uses these to collect changes and flush them,
42
- // and flushes could reveal more queries that should be batched.
43
- setTimeout(async () => {
44
- const batched = batching;
45
- batching = new Map();
37
+ // Wait for the next macrotask - don't use microtask as Svelte runtime uses these to collect changes and flush them,
38
+ // and flushes could reveal more queries that should be batched.
39
+ setTimeout(async () => {
40
+ const batched = batching;
41
+ batching = new Map();
46
42
 
47
- try {
48
- const response = await fetch(`${base}/${app_dir}/remote/${id}`, {
49
- method: 'POST',
50
- body: JSON.stringify({
51
- payloads: Array.from(batched.keys())
52
- }),
53
- headers
54
- });
43
+ try {
44
+ const response = await remote_request(`${base}/${app_dir}/remote/${id}`, {
45
+ method: 'POST',
46
+ body: JSON.stringify({
47
+ payloads: Array.from(batched.keys())
48
+ }),
49
+ headers
50
+ });
55
51
 
56
- if (!response.ok) {
57
- throw new Error('Failed to execute batch query');
58
- }
52
+ if (response.redirect) {
53
+ await goto(response.redirect);
59
54
 
60
- const result = /** @type {RemoteFunctionResponse} */ (await response.json());
61
- if (result.type === 'error') {
62
- throw new HttpError(result.status ?? 500, result.error);
55
+ // settle all batched promises (with `undefined`, like a redirect
56
+ // from a non-batched query) so that callers don't hang forever
57
+ for (const resolvers of batched.values()) {
58
+ for (const { resolve } of resolvers) {
59
+ resolve(undefined);
60
+ }
63
61
  }
64
62
 
65
- if (result.type === 'redirect') {
66
- await goto(result.location);
67
- throw new Redirect(307, result.location);
68
- }
63
+ return;
64
+ }
69
65
 
70
- const results = devalue.parse(result.result, app.decoders);
66
+ const results = response._;
67
+ let i = 0;
71
68
 
72
- // Resolve individual queries
73
- // Maps guarantee insertion order so we can do it like this
74
- let i = 0;
69
+ for (const resolvers of batched.values()) {
70
+ const result = results[i];
75
71
 
76
- for (const resolvers of batched.values()) {
77
- for (const { resolve, reject } of resolvers) {
78
- if (results[i].type === 'error') {
79
- reject(new HttpError(results[i].status, results[i].error));
80
- } else {
81
- resolve(results[i].data);
82
- }
72
+ for (const { resolve, reject } of resolvers) {
73
+ if (result.type === 'error') {
74
+ reject(new HttpError(result.status, result.error));
75
+ } else {
76
+ resolve(result.data);
83
77
  }
84
- i++;
85
78
  }
86
- } catch (error) {
87
- // Reject all queries in the batch
88
- for (const resolver of batched.values()) {
89
- for (const { reject } of resolver) {
90
- reject(error);
91
- }
79
+ i++;
80
+ }
81
+ } catch (e) {
82
+ for (const resolvers of batched.values()) {
83
+ for (const { reject } of resolvers) {
84
+ reject(e);
92
85
  }
93
86
  }
94
- }, 0);
95
- });
87
+ }
88
+ }, 0);
96
89
  });
97
-
98
- return devalue.parse(serialized, app.decoders);
99
90
  });
100
91
  };
101
92
 
@@ -1,9 +1,8 @@
1
- import { app } from '../../client.js';
2
- import * as devalue from 'devalue';
1
+ import { query_responses } from '../../client.js';
3
2
  import { HttpError, Redirect } from '@sveltejs/kit/internal';
4
3
  import { noop, once } from '../../../../utils/functions.js';
5
4
  import { SharedIterator } from '../../../../utils/shared-iterator.js';
6
- import { hydratable, tick } from 'svelte';
5
+ import { tick } from 'svelte';
7
6
  import { create_live_iterator } from './iterator.js';
8
7
 
9
8
  /**
@@ -84,9 +83,25 @@ export class LiveQuery {
84
83
  this.#resolve_first = resolve;
85
84
  this.#reject_first = reject;
86
85
 
87
- const serialized = hydratable(key, () => undefined);
88
- if (serialized !== undefined) {
89
- this.set(devalue.parse(serialized, app.decoders));
86
+ if (Object.hasOwn(query_responses, key)) {
87
+ const node = query_responses[key];
88
+ delete query_responses[key];
89
+
90
+ if (node.e) {
91
+ // the query failed during SSR — seed the failed state (mirroring `fail()`,
92
+ // minus its terminal `#done`), so the main loop still connects as usual
93
+ // and the query can recover
94
+ const error = new HttpError(node.e[0] ?? 500, node.e[1]);
95
+ this.#loading = false;
96
+ this.#error = error;
97
+
98
+ promise.catch(noop);
99
+ this.#reject_first?.(error);
100
+ this.#resolve_first = null;
101
+ this.#reject_first = null;
102
+ } else {
103
+ this.set(node.v);
104
+ }
90
105
  }
91
106
  }
92
107
 
@@ -1,7 +1,8 @@
1
- /** @import { RemoteFunctionResponse, RemoteSingleflightMap, RemoteSingleflightEntry } from 'types' */
1
+ /** @import { RemoteFunctionResponse, RemoteFunctionData, RemoteFunctionDataNode } from 'types' */
2
2
  /** @import { RemoteQueryUpdate } from '@sveltejs/kit' */
3
+ /** @import { CacheEntry } from './cache.svelte.js' */
3
4
  import * as devalue from 'devalue';
4
- import { app, goto, live_query_map, query_map } from '../client.js';
5
+ import { app, goto, live_query_map, query_map, query_responses } from '../client.js';
5
6
  import { HttpError, Redirect } from '@sveltejs/kit/internal';
6
7
  import { untrack } from 'svelte';
7
8
  import { create_remote_key, split_remote_key } from '../../shared.js';
@@ -77,6 +78,20 @@ export function pin_while_resolving(cache_map, cache, id, payload, then) {
77
78
  /**
78
79
  * @returns {{ 'x-sveltekit-pathname': string, 'x-sveltekit-search': string }}
79
80
  */
81
+ /**
82
+ * Unwraps a `RemoteFunctionDataNode` that was serialized during SSR,
83
+ * rethrowing serialized errors so the consuming resource ends up
84
+ * in the same failed state it had on the server
85
+ * @param {RemoteFunctionDataNode} node
86
+ */
87
+ export function unwrap_node(node) {
88
+ if (node.e) {
89
+ throw new HttpError(node.e[0] ?? 500, node.e[1]);
90
+ }
91
+
92
+ return node.v;
93
+ }
94
+
80
95
  export function get_remote_request_headers() {
81
96
  // This will be the correct value of the current or soon-current url,
82
97
  // even in forks because it's state-based - therefore not using window.location.
@@ -93,15 +108,10 @@ export function get_remote_request_headers() {
93
108
 
94
109
  /**
95
110
  * @param {string} url
96
- * @param {HeadersInit} headers
111
+ * @param {RequestInit} [init]
97
112
  */
98
- export async function remote_request(url, headers) {
99
- const response = await fetch(url, {
100
- headers: {
101
- 'Content-Type': 'application/json',
102
- ...headers
103
- }
104
- });
113
+ export async function remote_request(url, init) {
114
+ const response = await fetch(url, init);
105
115
 
106
116
  if (!response.ok) {
107
117
  throw new HttpError(500, 'Failed to execute remote function');
@@ -109,9 +119,63 @@ export async function remote_request(url, headers) {
109
119
 
110
120
  const result = /** @type {RemoteFunctionResponse} */ (await response.json());
111
121
 
112
- const resolved = await handle_side_channel_response(result);
122
+ if (result.type === 'error') {
123
+ throw new HttpError(result.status ?? 500, result.error);
124
+ }
125
+
126
+ const data = /** @type {RemoteFunctionData} */ (
127
+ result.data ? devalue.parse(result.data, app.decoders) : {}
128
+ );
129
+
130
+ /**
131
+ *
132
+ * @param {string} key
133
+ * @param {CacheEntry<any> | undefined} entry
134
+ * @param {any} result
135
+ */
136
+ function refresh(key, entry, result) {
137
+ if (entry?.resource) {
138
+ if (result.e) {
139
+ entry.resource.fail(new HttpError(result.e[0] ?? 500, result.e[1]));
140
+ } else {
141
+ entry.resource.set(result.v);
142
+ }
143
+ } else if (!result.e) {
144
+ // `query_responses` stores `{ v }`/`{ e }` nodes, not raw values.
145
+ // Errors are deliberately dropped here: they are responses to a specific
146
+ // refresh, not durable state a future resource should initialize with
147
+ query_responses[key] = result;
148
+ }
149
+ }
150
+
151
+ // update queries with refreshed data
152
+ if (data.q) {
153
+ for (const key in data.q) {
154
+ const parts = split_remote_key(key);
155
+ const entry = query_map.get(parts.id)?.get(parts.payload);
156
+
157
+ refresh(key, entry, data.q[key]);
158
+ }
159
+ }
160
+
161
+ // reconnect live queries
162
+ if (data.l) {
163
+ for (const key in data.l) {
164
+ const parts = split_remote_key(key);
165
+ const entry = live_query_map.get(parts.id)?.get(parts.payload);
166
+
167
+ refresh(key, entry, data.l[key]);
113
168
 
114
- return resolved.result;
169
+ // `fail()` is terminal, so only reconnect on the success path —
170
+ // reconnecting after a hard failure would wipe the error state and
171
+ // restart the stream (see commit 63a3e83 regression).
172
+ if (!data.l[key].e) {
173
+ void entry?.resource.reconnect();
174
+ }
175
+ }
176
+ }
177
+
178
+ return data;
115
179
  }
116
180
 
117
181
  /**
@@ -206,50 +270,3 @@ export function categorize_updates(updates) {
206
270
 
207
271
  return { overrides, refreshes };
208
272
  }
209
-
210
- /**
211
- * @template TResource
212
- * @param {string} stringified_singleflight
213
- * @param {Map<string, Map<string, { resource: TResource }>>} map
214
- * @param {(resource: TResource, value: RemoteSingleflightEntry) => void} callback
215
- */
216
- function apply_singleflight(stringified_singleflight, map, callback) {
217
- const singleflight = /** @type {RemoteSingleflightMap} */ (
218
- devalue.parse(stringified_singleflight, app.decoders)
219
- );
220
-
221
- for (const [key, value] of Object.entries(singleflight)) {
222
- const parts = split_remote_key(key);
223
- const entry = map.get(parts.id)?.get(parts.payload);
224
- if (entry?.resource) {
225
- callback(entry.resource, value);
226
- }
227
- }
228
- }
229
-
230
- /**
231
- * Apply refresh data from the server to the relevant queries
232
- *
233
- * @param {string} stringified_refreshes
234
- */
235
- export const apply_refreshes = (stringified_refreshes) => {
236
- apply_singleflight(stringified_refreshes, query_map, (resource, value) => {
237
- if (value.type === 'result') {
238
- resource?.set(value.data);
239
- } else {
240
- resource?.fail(new HttpError(value.status ?? 500, value.error));
241
- }
242
- });
243
- };
244
-
245
- /** @param {string} stringified_reconnects */
246
- export const apply_reconnections = (stringified_reconnects) => {
247
- apply_singleflight(stringified_reconnects, live_query_map, (resource, value) => {
248
- if (value.type === 'result') {
249
- resource?.set(value.data);
250
- void resource?.reconnect();
251
- } else {
252
- resource?.fail(new HttpError(value.status ?? 500, value.error));
253
- }
254
- });
255
- };
@@ -20,9 +20,9 @@ import {
20
20
  get_global_name,
21
21
  handle_error_and_jsonify
22
22
  } from '../utils.js';
23
- import { create_remote_key } from '../../shared.js';
24
23
  import { get_status } from '../../../utils/error.js';
25
24
  import * as env from '__sveltekit/env';
25
+ import { collect_remote_data } from '../remote.js';
26
26
 
27
27
  // TODO rename this function/module
28
28
 
@@ -78,9 +78,9 @@ export async function render_response({
78
78
 
79
79
  const { client } = manifest._;
80
80
 
81
- const modulepreloads = new Set(client.imports);
82
- const stylesheets = new Set(client.stylesheets);
83
- const fonts = new Set(client.fonts);
81
+ const modulepreloads = new Set(client?.imports);
82
+ const stylesheets = new Set(client?.stylesheets);
83
+ const fonts = new Set(client?.fonts);
84
84
 
85
85
  /**
86
86
  * The value of the Link header that is added to the response when not prerendering
@@ -281,7 +281,7 @@ export async function render_response({
281
281
  for (const url of node.stylesheets) stylesheets.add(url);
282
282
  for (const url of node.fonts) fonts.add(url);
283
283
 
284
- if (node.inline_styles && !client.inline) {
284
+ if (node.inline_styles && !client?.inline) {
285
285
  Object.entries(await node.inline_styles()).forEach(([filename, css]) => {
286
286
  if (typeof css === 'string') {
287
287
  inline_styles.set(filename, css);
@@ -307,7 +307,7 @@ export async function render_response({
307
307
  return `${assets}/${path}`;
308
308
  };
309
309
 
310
- const style = client.inline
310
+ const style = client?.inline
311
311
  ? client.inline?.style
312
312
  : Array.from(inline_styles.values()).join('\n');
313
313
 
@@ -371,8 +371,8 @@ export async function render_response({
371
371
  .join('\n\t\t\t')}`;
372
372
  }
373
373
 
374
- if (page_config.csr) {
375
- const route = manifest._.client.routes?.find((r) => r.id === event.route.id) ?? null;
374
+ if (page_config.csr && client) {
375
+ const route = client.routes?.find((r) => r.id === event.route.id) ?? null;
376
376
 
377
377
  if (client.uses_env_dynamic_public && state.prerendering) {
378
378
  modulepreloads.add(`${paths.app_dir}/env.js`);
@@ -400,12 +400,12 @@ export async function render_response({
400
400
  }
401
401
 
402
402
  // prerender a `/path/to/page/__route.js` module
403
- if (manifest._.client.routes && state.prerendering && !state.prerendering.fallback) {
403
+ if (client.routes && state.prerendering && !state.prerendering.fallback) {
404
404
  const pathname = add_resolution_suffix(event.url.pathname);
405
405
 
406
406
  state.prerendering.dependencies.set(
407
407
  pathname,
408
- create_server_routing_response(route, event.params, new URL(pathname, event.url), manifest)
408
+ create_server_routing_response(route, event.params, new URL(pathname, event.url), client)
409
409
  );
410
410
  }
411
411
 
@@ -505,9 +505,9 @@ export async function render_response({
505
505
  hydrate.push(`status: ${status}`);
506
506
  }
507
507
 
508
- if (manifest._.client.routes) {
508
+ if (client.routes) {
509
509
  if (route) {
510
- const stringified = generate_route_object(route, event.url, manifest).replaceAll(
510
+ const stringified = generate_route_object(route, event.url, client).replaceAll(
511
511
  '\n',
512
512
  '\n\t\t\t\t\t\t\t'
513
513
  ); // make output after it's put together with the rest more readable
@@ -521,87 +521,27 @@ export async function render_response({
521
521
  args.push(`{\n${indent}\t${hydrate.join(`,\n${indent}\t`)}\n${indent}}`);
522
522
  }
523
523
 
524
- const { remote } = event_state;
525
-
526
- let serialized_query_data = '';
527
- let serialized_prerender_data = '';
528
-
529
- if (remote.data) {
530
- /** @type {Record<string, any>} */
531
- const query = {};
532
-
533
- /** @type {Record<string, any>} */
534
- const prerender = {};
535
-
536
- for (const [internals, cache] of remote.data) {
537
- // remote functions without an `id` aren't exported, and thus
538
- // cannot be called from the client
539
- if (!internals.id) continue;
540
-
541
- for (const key in cache) {
542
- const entry = cache[key];
543
-
544
- if (!entry.serialize) continue;
545
-
546
- const remote_key = create_remote_key(internals.id, key);
547
-
548
- const store = internals.type === 'prerender' ? prerender : query;
549
-
550
- if (
551
- event_state.remote.refreshes?.has(remote_key) ||
552
- event_state.remote.reconnects?.has(remote_key)
553
- ) {
554
- // This entry was refreshed/set by a command or form action.
555
- // Always await it so the mutation result is serialized.
556
- store[remote_key] = await entry.data;
557
- } else {
558
- // Don't block the response on pending remote data - if a query
559
- // hasn't settled yet, it wasn't awaited in the template (or is behind a pending boundary).
560
- const result = await Promise.race([
561
- Promise.resolve(entry.data).then(
562
- (v) => /** @type {const} */ ({ settled: true, value: v }),
563
- (e) => /** @type {const} */ ({ settled: true, error: e })
564
- ),
565
- new Promise((resolve) => {
566
- queueMicrotask(() => resolve(/** @type {const} */ ({ settled: false })));
567
- })
568
- ]);
569
-
570
- if (result.settled) {
571
- if ('error' in result) throw result.error;
572
- store[remote_key] = result.value;
573
- }
574
- }
575
- }
576
- }
577
-
578
- const replacer = create_replacer(options.hooks.transport);
579
-
580
- if (Object.keys(query).length > 0) {
581
- serialized_query_data = `${global}.query = ${devalue.uneval(query, replacer)};\n\n\t\t\t\t\t\t`;
582
- }
583
-
584
- if (Object.keys(prerender).length > 0) {
585
- serialized_prerender_data = `${global}.prerender = ${devalue.uneval(prerender, replacer)};\n\n\t\t\t\t\t\t`;
586
- }
587
- }
524
+ const remote_data = await collect_remote_data({}, event, event_state, options);
588
525
 
589
- const serialized_remote_data = `${serialized_query_data}${serialized_prerender_data}`;
526
+ const serialized_data =
527
+ Object.keys(remote_data).length > 0
528
+ ? `${global}.data = ${devalue.uneval(remote_data, create_replacer(options.hooks.transport))};\n\n\t\t\t\t\t\t`
529
+ : '';
590
530
 
591
531
  // `client.app` is a proxy for `bundleStrategy === 'split'`
592
532
  const boot = client.inline
593
533
  ? `${client.inline.script}
594
534
 
595
- ${serialized_remote_data}${global}.app.start(${args.join(', ')});`
535
+ ${serialized_data}${global}.app.start(${args.join(', ')});`
596
536
  : client.app
597
537
  ? `Promise.all([
598
538
  import(${s(prefixed(client.start))}),
599
539
  import(${s(prefixed(client.app))})
600
540
  ]).then(([kit, app]) => {
601
- ${serialized_remote_data}kit.start(app, ${args.join(', ')});
541
+ ${serialized_data}kit.start(app, ${args.join(', ')});
602
542
  });`
603
543
  : `import(${s(prefixed(client.start))}).then((app) => {
604
- ${serialized_remote_data}app.start(${args.join(', ')})
544
+ ${serialized_data}app.start(${args.join(', ')})
605
545
  });`;
606
546
 
607
547
  if (load_env_eagerly) {
@@ -1,3 +1,4 @@
1
+ /** @import { SSRManifest } from '@sveltejs/kit' */
1
2
  import { base, assets, relative } from '$app/paths/internal/server';
2
3
  import { text } from '@sveltejs/kit';
3
4
  import { s } from '../../../utils/misc.js';
@@ -7,15 +8,15 @@ import { get_relative_path } from '../../utils.js';
7
8
  /**
8
9
  * @param {import('types').SSRClientRoute} route
9
10
  * @param {URL} url
10
- * @param {import('@sveltejs/kit').SSRManifest} manifest
11
+ * @param {NonNullable<SSRManifest['_']['client']>} client
11
12
  * @returns {string}
12
13
  */
13
- export function generate_route_object(route, url, manifest) {
14
+ export function generate_route_object(route, url, client) {
14
15
  const { errors, layouts, leaf } = route;
15
16
 
16
17
  const nodes = [...errors, ...layouts.map((l) => l?.[1]), leaf[1]]
17
18
  .filter((n) => typeof n === 'number')
18
- .map((n) => `'${n}': () => ${create_client_import(manifest._.client.nodes?.[n], url)}`)
19
+ .map((n) => `'${n}': () => ${create_client_import(client.nodes?.[n], url)}`)
19
20
  .join(',\n\t\t');
20
21
 
21
22
  // stringified version of
@@ -60,36 +61,40 @@ function create_client_import(import_path, url) {
60
61
  /**
61
62
  * @param {string} resolved_path
62
63
  * @param {URL} url
63
- * @param {import('@sveltejs/kit').SSRManifest} manifest
64
+ * @param {SSRManifest} manifest
64
65
  * @returns {Promise<Response>}
65
66
  */
66
67
  export async function resolve_route(resolved_path, url, manifest) {
67
- if (!manifest._.client.routes) {
68
+ if (!manifest._.client?.routes) {
68
69
  return text('Server-side route resolution disabled', { status: 400 });
69
70
  }
70
71
 
71
72
  const matchers = await manifest._.matchers();
72
73
  const result = find_route(resolved_path, manifest._.client.routes, matchers);
73
74
 
74
- return create_server_routing_response(result?.route ?? null, result?.params ?? {}, url, manifest)
75
- .response;
75
+ return create_server_routing_response(
76
+ result?.route ?? null,
77
+ result?.params ?? {},
78
+ url,
79
+ manifest._.client
80
+ ).response;
76
81
  }
77
82
 
78
83
  /**
79
84
  * @param {import('types').SSRClientRoute | null} route
80
85
  * @param {Partial<Record<string, string>>} params
81
86
  * @param {URL} url
82
- * @param {import('@sveltejs/kit').SSRManifest} manifest
87
+ * @param {NonNullable<SSRManifest['_']['client']>} client
83
88
  * @returns {{response: Response, body: string}}
84
89
  */
85
- export function create_server_routing_response(route, params, url, manifest) {
90
+ export function create_server_routing_response(route, params, url, client) {
86
91
  const headers = new Headers({
87
92
  'content-type': 'application/javascript; charset=utf-8'
88
93
  });
89
94
 
90
95
  if (route) {
91
- const csr_route = generate_route_object(route, url, manifest);
92
- const body = `${create_css_import(route, url, manifest)}\nexport const route = ${csr_route}; export const params = ${JSON.stringify(params)};`;
96
+ const csr_route = generate_route_object(route, url, client);
97
+ const body = `${create_css_import(route, url, client)}\nexport const route = ${csr_route}; export const params = ${JSON.stringify(params)};`;
93
98
 
94
99
  return { response: text(body, { headers }), body };
95
100
  } else {
@@ -105,17 +110,17 @@ export function create_server_routing_response(route, params, url, manifest) {
105
110
  *
106
111
  * @param {import('types').SSRClientRoute} route
107
112
  * @param {URL} url
108
- * @param {import('@sveltejs/kit').SSRManifest} manifest
113
+ * @param {NonNullable<SSRManifest['_']['client']>} client
109
114
  * @returns {string}
110
115
  */
111
- function create_css_import(route, url, manifest) {
116
+ function create_css_import(route, url, client) {
112
117
  const { errors, layouts, leaf } = route;
113
118
 
114
119
  let css = '';
115
120
 
116
121
  for (const node of [...errors, ...layouts.map((l) => l?.[1]), leaf[1]]) {
117
122
  if (typeof node !== 'number') continue;
118
- const node_css = manifest._.client.css?.[node];
123
+ const node_css = client.css?.[node];
119
124
  for (const css_path of node_css ?? []) {
120
125
  css += `'${assets || base}/${css_path}',`;
121
126
  }
@@ -123,5 +128,5 @@ function create_css_import(route, url, manifest) {
123
128
 
124
129
  if (!css) return '';
125
130
 
126
- return `${create_client_import(/** @type {string} */ (manifest._.client.start), url)}.then(x => x.load_css([${css}]));`;
131
+ return `${create_client_import(client.start, url)}.then(x => x.load_css([${css}]));`;
127
132
  }