@sveltejs/kit 2.63.1 → 2.65.0

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 -5
  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 +47 -57
  6. package/src/exports/vite/build/utils.js +0 -8
  7. package/src/exports/vite/index.js +226 -178
  8. package/src/runtime/app/server/remote/command.js +0 -3
  9. package/src/runtime/app/server/remote/form.js +18 -13
  10. package/src/runtime/app/server/remote/prerender.js +28 -34
  11. package/src/runtime/app/server/remote/query.js +105 -94
  12. package/src/runtime/app/server/remote/requested.js +14 -10
  13. package/src/runtime/app/server/remote/shared.js +25 -18
  14. package/src/runtime/client/client.js +19 -13
  15. package/src/runtime/client/remote-functions/command.svelte.js +7 -32
  16. package/src/runtime/client/remote-functions/form.svelte.js +62 -82
  17. package/src/runtime/client/remote-functions/prerender.svelte.js +14 -6
  18. package/src/runtime/client/remote-functions/query/index.js +6 -14
  19. package/src/runtime/client/remote-functions/query/instance.svelte.js +20 -0
  20. package/src/runtime/client/remote-functions/query/proxy.js +3 -3
  21. package/src/runtime/client/remote-functions/query-batch.svelte.js +59 -68
  22. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +20 -6
  23. package/src/runtime/client/remote-functions/shared.svelte.js +76 -59
  24. package/src/runtime/server/page/render.js +20 -80
  25. package/src/runtime/server/page/server_routing.js +20 -15
  26. package/src/runtime/server/remote.js +296 -204
  27. package/src/runtime/server/respond.js +4 -2
  28. package/src/runtime/shared.js +83 -28
  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 +4 -4
  33. package/types/index.d.ts.map +1 -1
@@ -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
- };
@@ -21,9 +21,9 @@ import {
21
21
  get_global_name,
22
22
  handle_error_and_jsonify
23
23
  } from '../utils.js';
24
- import { create_remote_key } from '../../shared.js';
25
24
  import { get_status } from '../../../utils/error.js';
26
25
  import * as env from '__sveltekit/env';
26
+ import { collect_remote_data } from '../remote.js';
27
27
 
28
28
  // TODO rename this function/module
29
29
 
@@ -79,9 +79,9 @@ export async function render_response({
79
79
 
80
80
  const { client } = manifest._;
81
81
 
82
- const modulepreloads = new Set(client.imports);
83
- const stylesheets = new Set(client.stylesheets);
84
- const fonts = new Set(client.fonts);
82
+ const modulepreloads = new Set(client?.imports);
83
+ const stylesheets = new Set(client?.stylesheets);
84
+ const fonts = new Set(client?.fonts);
85
85
 
86
86
  /**
87
87
  * The value of the Link header that is added to the response when not prerendering
@@ -282,7 +282,7 @@ export async function render_response({
282
282
  for (const url of node.stylesheets) stylesheets.add(url);
283
283
  for (const url of node.fonts) fonts.add(url);
284
284
 
285
- if (node.inline_styles && !client.inline) {
285
+ if (node.inline_styles && !client?.inline) {
286
286
  Object.entries(await node.inline_styles()).forEach(([filename, css]) => {
287
287
  if (typeof css === 'string') {
288
288
  inline_styles.set(filename, css);
@@ -308,7 +308,7 @@ export async function render_response({
308
308
  return `${assets}/${path}`;
309
309
  };
310
310
 
311
- const style = client.inline
311
+ const style = client?.inline
312
312
  ? client.inline?.style
313
313
  : Array.from(inline_styles.values()).join('\n');
314
314
 
@@ -365,8 +365,8 @@ export async function render_response({
365
365
  .join('\n\t\t\t')}`;
366
366
  }
367
367
 
368
- if (page_config.csr) {
369
- const route = manifest._.client.routes?.find((r) => r.id === event.route.id) ?? null;
368
+ if (page_config.csr && client) {
369
+ const route = client.routes?.find((r) => r.id === event.route.id) ?? null;
370
370
 
371
371
  if (client.uses_env_dynamic_public && state.prerendering) {
372
372
  modulepreloads.add(`${paths.app_dir}/env.js`);
@@ -390,12 +390,12 @@ export async function render_response({
390
390
  }
391
391
 
392
392
  // prerender a `/path/to/page/__route.js` module
393
- if (manifest._.client.routes && state.prerendering && !state.prerendering.fallback) {
393
+ if (client.routes && state.prerendering && !state.prerendering.fallback) {
394
394
  const pathname = add_resolution_suffix(event.url.pathname);
395
395
 
396
396
  state.prerendering.dependencies.set(
397
397
  pathname,
398
- create_server_routing_response(route, event.params, new URL(pathname, event.url), manifest)
398
+ create_server_routing_response(route, event.params, new URL(pathname, event.url), client)
399
399
  );
400
400
  }
401
401
 
@@ -500,9 +500,9 @@ export async function render_response({
500
500
  hydrate.push(`status: ${status}`);
501
501
  }
502
502
 
503
- if (manifest._.client.routes) {
503
+ if (client.routes) {
504
504
  if (route) {
505
- const stringified = generate_route_object(route, event.url, manifest).replaceAll(
505
+ const stringified = generate_route_object(route, event.url, client).replaceAll(
506
506
  '\n',
507
507
  '\n\t\t\t\t\t\t\t'
508
508
  ); // make output after it's put together with the rest more readable
@@ -516,87 +516,27 @@ export async function render_response({
516
516
  args.push(`{\n${indent}\t${hydrate.join(`,\n${indent}\t`)}\n${indent}}`);
517
517
  }
518
518
 
519
- const { remote } = event_state;
520
-
521
- let serialized_query_data = '';
522
- let serialized_prerender_data = '';
523
-
524
- if (remote.data) {
525
- /** @type {Record<string, any>} */
526
- const query = {};
527
-
528
- /** @type {Record<string, any>} */
529
- const prerender = {};
530
-
531
- for (const [internals, cache] of remote.data) {
532
- // remote functions without an `id` aren't exported, and thus
533
- // cannot be called from the client
534
- if (!internals.id) continue;
535
-
536
- for (const key in cache) {
537
- const entry = cache[key];
538
-
539
- if (!entry.serialize) continue;
540
-
541
- const remote_key = create_remote_key(internals.id, key);
542
-
543
- const store = internals.type === 'prerender' ? prerender : query;
544
-
545
- if (
546
- event_state.remote.refreshes?.has(remote_key) ||
547
- event_state.remote.reconnects?.has(remote_key)
548
- ) {
549
- // This entry was refreshed/set by a command or form action.
550
- // Always await it so the mutation result is serialized.
551
- store[remote_key] = await entry.data;
552
- } else {
553
- // Don't block the response on pending remote data - if a query
554
- // hasn't settled yet, it wasn't awaited in the template (or is behind a pending boundary).
555
- const result = await Promise.race([
556
- Promise.resolve(entry.data).then(
557
- (v) => /** @type {const} */ ({ settled: true, value: v }),
558
- (e) => /** @type {const} */ ({ settled: true, error: e })
559
- ),
560
- new Promise((resolve) => {
561
- queueMicrotask(() => resolve(/** @type {const} */ ({ settled: false })));
562
- })
563
- ]);
564
-
565
- if (result.settled) {
566
- if ('error' in result) throw result.error;
567
- store[remote_key] = result.value;
568
- }
569
- }
570
- }
571
- }
572
-
573
- const replacer = create_replacer(options.hooks.transport);
574
-
575
- if (Object.keys(query).length > 0) {
576
- serialized_query_data = `${global}.query = ${devalue.uneval(query, replacer)};\n\n\t\t\t\t\t\t`;
577
- }
578
-
579
- if (Object.keys(prerender).length > 0) {
580
- serialized_prerender_data = `${global}.prerender = ${devalue.uneval(prerender, replacer)};\n\n\t\t\t\t\t\t`;
581
- }
582
- }
519
+ const remote_data = await collect_remote_data({}, event, event_state, options);
583
520
 
584
- const serialized_remote_data = `${serialized_query_data}${serialized_prerender_data}`;
521
+ const serialized_data =
522
+ Object.keys(remote_data).length > 0
523
+ ? `${global}.data = ${devalue.uneval(remote_data, create_replacer(options.hooks.transport))};\n\n\t\t\t\t\t\t`
524
+ : '';
585
525
 
586
526
  // `client.app` is a proxy for `bundleStrategy === 'split'`
587
527
  const boot = client.inline
588
528
  ? `${client.inline.script}
589
529
 
590
- ${serialized_remote_data}${global}.app.start(${args.join(', ')});`
530
+ ${serialized_data}${global}.app.start(${args.join(', ')});`
591
531
  : client.app
592
532
  ? `Promise.all([
593
533
  import(${s(prefixed(client.start))}),
594
534
  import(${s(prefixed(client.app))})
595
535
  ]).then(([kit, app]) => {
596
- ${serialized_remote_data}kit.start(app, ${args.join(', ')});
536
+ ${serialized_data}kit.start(app, ${args.join(', ')});
597
537
  });`
598
538
  : `import(${s(prefixed(client.start))}).then((app) => {
599
- ${serialized_remote_data}app.start(${args.join(', ')})
539
+ ${serialized_data}app.start(${args.join(', ')})
600
540
  });`;
601
541
 
602
542
  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
  }