@sveltejs/kit 2.60.0 → 2.61.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 (45) hide show
  1. package/package.json +10 -11
  2. package/src/core/postbuild/analyse.js +1 -3
  3. package/src/core/sync/create_manifest_data/conflict.js +72 -0
  4. package/src/core/sync/create_manifest_data/index.js +1 -65
  5. package/src/core/sync/write_non_ambient.js +2 -2
  6. package/src/core/sync/write_types/index.js +1 -1
  7. package/src/exports/public.d.ts +23 -30
  8. package/src/exports/vite/build/build_server.js +36 -13
  9. package/src/exports/vite/dev/index.js +4 -2
  10. package/src/exports/vite/index.js +18 -16
  11. package/src/runtime/app/server/index.js +1 -2
  12. package/src/runtime/app/server/remote/form.js +10 -0
  13. package/src/runtime/app/server/remote/query.js +111 -44
  14. package/src/runtime/client/client.js +13 -8
  15. package/src/runtime/client/remote-functions/cache.svelte.js +157 -0
  16. package/src/runtime/client/remote-functions/form.svelte.js +235 -196
  17. package/src/runtime/client/remote-functions/index.js +2 -2
  18. package/src/runtime/client/remote-functions/prerender.svelte.js +1 -2
  19. package/src/runtime/client/remote-functions/query/cache.js +4 -0
  20. package/src/runtime/client/remote-functions/query/index.js +48 -0
  21. package/src/runtime/client/remote-functions/query/instance.svelte.js +249 -0
  22. package/src/runtime/client/remote-functions/query/proxy.js +156 -0
  23. package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
  24. package/src/runtime/client/remote-functions/query-live/cache.js +4 -0
  25. package/src/runtime/client/remote-functions/query-live/index.js +31 -0
  26. package/src/runtime/client/remote-functions/{query-live.svelte.js → query-live/instance.svelte.js} +61 -310
  27. package/src/runtime/client/remote-functions/query-live/iterator.js +91 -0
  28. package/src/runtime/client/remote-functions/query-live/proxy.js +144 -0
  29. package/src/runtime/client/remote-functions/shared.svelte.js +53 -6
  30. package/src/runtime/client/utils.js +1 -1
  31. package/src/runtime/form-utils.js +7 -16
  32. package/src/runtime/server/index.js +2 -3
  33. package/src/runtime/server/page/actions.js +2 -9
  34. package/src/runtime/server/page/csp.js +3 -4
  35. package/src/runtime/server/page/render.js +13 -14
  36. package/src/runtime/server/respond.js +61 -38
  37. package/src/runtime/server/utils.js +23 -3
  38. package/src/types/global-private.d.ts +5 -0
  39. package/src/types/internal.d.ts +45 -8
  40. package/src/utils/routing.js +3 -1
  41. package/src/utils/shared-iterator.js +213 -0
  42. package/src/version.js +1 -1
  43. package/types/index.d.ts +28 -32
  44. package/types/index.d.ts.map +1 -1
  45. package/src/runtime/client/remote-functions/query.svelte.js +0 -512
@@ -6,7 +6,6 @@ import { HttpError, Redirect } from '@sveltejs/kit/internal';
6
6
  import { untrack } from 'svelte';
7
7
  import { create_remote_key, split_remote_key } from '../../shared.js';
8
8
  import { navigating, page } from '../state.svelte.js';
9
- import { noop } from '../../../utils/functions.js';
10
9
 
11
10
  /** Indicates a query function, as opposed to a query instance */
12
11
  export const QUERY_FUNCTION_ID = Symbol('sveltekit.query_function_id');
@@ -16,17 +15,65 @@ export const QUERY_OVERRIDE_KEY = Symbol('sveltekit.query_override_key');
16
15
  export const QUERY_RESOURCE_KEY = Symbol('sveltekit.query_resource_key');
17
16
 
18
17
  /**
19
- * @returns {boolean} Returns `true` if we are in an effect
18
+ * If we're inside a reactive context, pin a cache entry for as long as the
19
+ * surrounding effect is alive. Without this, a transiently-referenced
20
+ * `QueryProxy`/`LiveQueryProxy` (e.g. one produced by `{await fn()}` in a
21
+ * template or by `$derived(await fn())`) would be eligible for GC as soon as
22
+ * the awaited value has been read, after which the FinalizationRegistry
23
+ * would evict the cache entry — even though the consuming effect is still
24
+ * alive and may rely on the entry being refreshed (e.g. via `refreshAll()`
25
+ * or a server-initiated single-flight refresh).
26
+ *
27
+ * @template TResource
28
+ * @param {Map<string, Map<string, { resource: TResource }>>} cache_map
29
+ * @param {{ manual_ref: (entry: any, id: string, payload: string) => () => void }} cache
30
+ * @param {string} id
31
+ * @param {string} payload
20
32
  */
21
- export function is_in_effect() {
33
+ export function pin_in_effect(cache_map, cache, id, payload) {
22
34
  try {
23
- $effect.pre(noop);
24
- return true;
35
+ $effect.pre(() => {
36
+ const entry = cache_map.get(id)?.get(payload);
37
+ if (!entry) return;
38
+ return cache.manual_ref(entry, id, payload);
39
+ });
25
40
  } catch {
26
- return false;
41
+ // not in an effect context — nothing to pin
27
42
  }
28
43
  }
29
44
 
45
+ /**
46
+ * Wrap a proxy's `then`/`catch`/`finally` function so that the underlying
47
+ * cache entry stays pinned for the lifetime of the awaited promise. Without
48
+ * this, a proxy awaited outside any effect (e.g. in an event handler) could
49
+ * be GC'd between the `.then` getter returning the thenable and the
50
+ * underlying promise settling, causing the FinalizationRegistry to evict the
51
+ * cache entry mid-flight and the awaited value to resolve to Svelte's
52
+ * `UNINITIALIZED` sentinel from a torn-down `$derived`.
53
+ *
54
+ * @template TResource
55
+ * @template {(...args: any[]) => Promise<any>} TThen
56
+ * @param {Map<string, Map<string, { resource: TResource }>>} cache_map
57
+ * @param {{ manual_ref: (entry: any, id: string, payload: string) => () => void }} cache
58
+ * @param {string} id
59
+ * @param {string} payload
60
+ * @param {TThen} then
61
+ * @returns {TThen}
62
+ */
63
+ export function pin_while_resolving(cache_map, cache, id, payload, then) {
64
+ return /** @type {TThen} */ (
65
+ (...a) => {
66
+ const entry = cache_map.get(id)?.get(payload);
67
+ const release = entry ? cache.manual_ref(entry, id, payload) : undefined;
68
+ const promise = then(...a);
69
+ if (release) {
70
+ promise.then(release, release);
71
+ }
72
+ return promise;
73
+ }
74
+ );
75
+ }
76
+
30
77
  /**
31
78
  * @returns {{ 'x-sveltekit-pathname': string, 'x-sveltekit-search': string }}
32
79
  */
@@ -249,7 +249,7 @@ export const updated_listener = {
249
249
  export function create_updated_store() {
250
250
  const { set, subscribe } = writable(false);
251
251
 
252
- if (DEV || !BROWSER) {
252
+ if (__SVELTEKIT_DEV__ || !BROWSER) {
253
253
  return {
254
254
  subscribe,
255
255
  // eslint-disable-next-line @typescript-eslint/require-await
@@ -145,10 +145,6 @@ export async function deserialize_binary_form(request) {
145
145
  if (!request.body) {
146
146
  throw deserialize_error('no body');
147
147
  }
148
- const content_length = parseInt(request.headers.get('content-length') ?? '');
149
- if (Number.isNaN(content_length)) {
150
- throw deserialize_error('invalid Content-Length header');
151
- }
152
148
 
153
149
  const reader = request.body.getReader();
154
150
 
@@ -228,16 +224,11 @@ export async function deserialize_binary_form(request) {
228
224
  }
229
225
  const header_view = new DataView(header.buffer, header.byteOffset, header.byteLength);
230
226
  const data_length = header_view.getUint32(1, true);
231
-
232
- if (HEADER_BYTES + data_length > content_length) {
233
- throw deserialize_error('data overflow');
234
- }
235
-
236
227
  const file_offsets_length = header_view.getUint16(5, true);
237
228
 
238
- if (HEADER_BYTES + data_length + file_offsets_length > content_length) {
239
- throw deserialize_error('file offset table overflow');
240
- }
229
+ // Validation uses embedded binary header fields (data_length, file_offsets_length)
230
+ // rather than Content-Length, which proxies/middleboxes may strip or corrupt.
231
+ // See: https://github.com/sveltejs/kit/issues/15299
241
232
 
242
233
  // Read the form data
243
234
  const data_buffer = await get_buffer(HEADER_BYTES, data_length);
@@ -289,9 +280,6 @@ export async function deserialize_binary_form(request) {
289
280
  file_offsets[index] = undefined;
290
281
 
291
282
  offset += files_start_offset;
292
- if (offset + size > content_length) {
293
- throw deserialize_error('file data overflow');
294
- }
295
283
 
296
284
  file_spans.push({ offset, size });
297
285
 
@@ -716,8 +704,11 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
716
704
  }
717
705
  }
718
706
 
707
+ const value =
708
+ typeof input_value === 'boolean' ? (input_value ? 'on' : 'off') : input_value;
709
+
719
710
  return Object.defineProperties(base_props, {
720
- value: { value: input_value, enumerable: true }
711
+ value: { value, enumerable: true }
721
712
  });
722
713
  }
723
714
 
@@ -5,7 +5,6 @@ import { IN_WEBCONTAINER } from './constants.js';
5
5
  import { respond } from './respond.js';
6
6
  import { set_private_env, set_public_env } from '../shared-server.js';
7
7
  import { options, get_hooks } from '__SERVER__/internal.js';
8
- import { DEV } from 'esm-env';
9
8
  import { filter_env } from '../../utils/env.js';
10
9
  import { format_server_error } from './utils.js';
11
10
  import { set_read_implementation, set_manifest } from '__sveltekit/server';
@@ -102,7 +101,7 @@ export class Server {
102
101
  set_read_implementation(wrapped_read);
103
102
  }
104
103
 
105
- // During DEV and for some adapters this function might be called in quick succession,
104
+ // During dev and for some adapters this function might be called in quick succession,
106
105
  // so we need to make sure we're not invoking this logic (most notably the init hook) multiple times
107
106
  await (init_promise ??= (async () => {
108
107
  try {
@@ -141,7 +140,7 @@ export class Server {
141
140
  await module.init();
142
141
  }
143
142
  } catch (e) {
144
- if (DEV) {
143
+ if (__SVELTEKIT_DEV__) {
145
144
  this.#options.hooks = {
146
145
  handle: () => {
147
146
  throw e;
@@ -7,7 +7,7 @@ import { HttpError, Redirect, ActionFailure, SvelteKitError } from '@sveltejs/ki
7
7
  import { with_request_store, merge_tracing } from '@sveltejs/kit/internal/server';
8
8
  import { get_status, normalize_error } from '../../../utils/error.js';
9
9
  import { is_form_content_type, negotiate } from '../../../utils/http.js';
10
- import { handle_error_and_jsonify } from '../utils.js';
10
+ import { create_replacer, handle_error_and_jsonify } from '../utils.js';
11
11
  import { record_span } from '../../telemetry/record_span.js';
12
12
 
13
13
  /** @param {RequestEvent} event */
@@ -301,14 +301,7 @@ function validate_action_return(data) {
301
301
  * @param {ServerHooks['transport']} transport
302
302
  */
303
303
  export function uneval_action_response(data, route_id, transport) {
304
- const replacer = (/** @type {any} */ thing) => {
305
- for (const key in transport) {
306
- const encoded = transport[key].encode(thing);
307
- if (encoded) {
308
- return `app.decode('${key}', ${devalue.uneval(encoded, replacer)})`;
309
- }
310
- }
311
- };
304
+ const replacer = create_replacer(transport);
312
305
 
313
306
  return try_serialize(data, (value) => devalue.uneval(value, replacer), route_id);
314
307
  }
@@ -1,4 +1,3 @@
1
- import { DEV } from 'esm-env';
2
1
  import { escape_html } from '../../../utils/escape.js';
3
2
  import { sha256 } from './crypto.js';
4
3
 
@@ -87,7 +86,7 @@ class BaseProvider {
87
86
  */
88
87
  constructor(use_hashes, directives, nonce) {
89
88
  this.#use_hashes = use_hashes;
90
- this.#directives = DEV ? { ...directives } : directives; // clone in dev so we can safely mutate
89
+ this.#directives = __SVELTEKIT_DEV__ ? { ...directives } : directives; // clone in dev so we can safely mutate
91
90
 
92
91
  const d = this.#directives;
93
92
 
@@ -103,7 +102,7 @@ class BaseProvider {
103
102
  const style_src_attr = d['style-src-attr'];
104
103
  const style_src_elem = d['style-src-elem'];
105
104
 
106
- if (DEV) {
105
+ if (__SVELTEKIT_DEV__) {
107
106
  // remove strict-dynamic in dev...
108
107
  // TODO reinstate this if we can figure out how to make strict-dynamic work
109
108
  // if (d['default-src']) {
@@ -164,7 +163,7 @@ class BaseProvider {
164
163
 
165
164
  this.#script_needs_csp = this.#script_src_needs_csp || this.#script_src_elem_needs_csp;
166
165
  this.#style_needs_csp =
167
- !DEV &&
166
+ !__SVELTEKIT_DEV__ &&
168
167
  (this.#style_src_needs_csp ||
169
168
  this.#style_src_attr_needs_csp ||
170
169
  this.#style_src_elem_needs_csp);
@@ -15,7 +15,12 @@ import { create_server_routing_response, generate_route_object } from './server_
15
15
  import { add_resolution_suffix } from '../../pathname.js';
16
16
  import { try_get_request_store, with_request_store } from '@sveltejs/kit/internal/server';
17
17
  import { text_encoder } from '../../utils.js';
18
- import { count_non_ssi_comments, get_global_name, handle_error_and_jsonify } from '../utils.js';
18
+ import {
19
+ count_non_ssi_comments,
20
+ create_replacer,
21
+ get_global_name,
22
+ handle_error_and_jsonify
23
+ } from '../utils.js';
19
24
  import { create_remote_key } from '../../shared.js';
20
25
  import { get_status } from '../../../utils/error.js';
21
26
 
@@ -302,13 +307,15 @@ export async function render_response({
302
307
  return `${assets}/${path}`;
303
308
  };
304
309
 
305
- // inline styles can come from `bundleStrategy: 'inline'` or `inlineStyleThreshold`
306
310
  const style = client.inline
307
311
  ? client.inline?.style
308
312
  : Array.from(inline_styles.values()).join('\n');
309
313
 
310
314
  if (style) {
311
- const attributes = DEV ? ['data-sveltekit'] : [];
315
+ // We always inline all styles to avoid FOUC during development.
316
+ // Once that's accomplished, we find and remove the style node using the
317
+ // `data-sveltekit` attribute once CSR kicks in
318
+ const attributes = __SVELTEKIT_DEV__ ? ['data-sveltekit'] : [];
312
319
  if (csp.style_needs_nonce) attributes.push(`nonce="${csp.nonce}"`);
313
320
  csp.add_style(style);
314
321
  head.add_style(style, attributes);
@@ -557,15 +564,7 @@ export async function render_response({
557
564
  }
558
565
  }
559
566
 
560
- // TODO this is repeated in a few places — dedupe it
561
- const replacer = (/** @type {any} */ thing) => {
562
- for (const key in options.hooks.transport) {
563
- const encoded = options.hooks.transport[key].encode(thing);
564
- if (encoded) {
565
- return `app.decode('${key}', ${devalue.uneval(encoded, replacer)})`;
566
- }
567
- }
568
- };
567
+ const replacer = create_replacer(options.hooks.transport);
569
568
 
570
569
  if (Object.keys(query).length > 0) {
571
570
  serialized_query_data = `${global}.query = ${devalue.uneval(query, replacer)};\n\n\t\t\t\t\t\t`;
@@ -605,10 +604,10 @@ export async function render_response({
605
604
  }
606
605
 
607
606
  if (options.service_worker) {
608
- let opts = DEV ? ", { type: 'module' }" : '';
607
+ let opts = __SVELTEKIT_DEV__ ? ", { type: 'module' }" : '';
609
608
  if (options.service_worker_options != null) {
610
609
  const service_worker_options = { ...options.service_worker_options };
611
- if (DEV) {
610
+ if (__SVELTEKIT_DEV__) {
612
611
  service_worker_options.type = 'module';
613
612
  }
614
613
  opts = `, ${s(service_worker_options)}`;
@@ -40,8 +40,6 @@ import { get_remote_id, handle_remote_call } from './remote.js';
40
40
  import { record_span } from '../telemetry/record_span.js';
41
41
  import { otel } from '../telemetry/otel.js';
42
42
 
43
- /* global __SVELTEKIT_ADAPTER_NAME__ */
44
-
45
43
  /** @type {import('types').RequiredResolveOptions['transformPageChunk']} */
46
44
  const default_transform = ({ html }) => html;
47
45
 
@@ -150,11 +148,11 @@ export async function internal_respond(request, options, manifest, state) {
150
148
  remote: {
151
149
  data: null,
152
150
  forms: null,
153
- /** A map of remote function key to corresponding single-flight-mutation promise */
154
151
  refreshes: null,
152
+ requested: null,
155
153
  reconnects: null,
156
- /** A map of remote function ID to payloads requested for refreshing by the client */
157
- requested: null
154
+ batches: null,
155
+ live_iterators: null
158
156
  },
159
157
  is_in_remote_function: false,
160
158
  is_in_render: false,
@@ -229,6 +227,7 @@ export async function internal_respond(request, options, manifest, state) {
229
227
  });
230
228
  }
231
229
 
230
+ /** @type {string | null} */
232
231
  let resolved_path = url.pathname;
233
232
 
234
233
  if (!remote_id) {
@@ -250,10 +249,24 @@ export async function internal_respond(request, options, manifest, state) {
250
249
  }
251
250
  }
252
251
 
252
+ /** @type {import('types').RequiredResolveOptions} */
253
+ let resolve_opts = {
254
+ transformPageChunk: default_transform,
255
+ filterSerializedResponseHeaders: default_filter,
256
+ preload: default_preload
257
+ };
258
+
259
+ /** @type {import('types').TrailingSlash} */
260
+ let trailing_slash = 'never';
261
+
262
+ /** @type {PageNodes | undefined} */
263
+ let page_nodes;
264
+
253
265
  try {
254
266
  resolved_path = decode_pathname(resolved_path);
255
267
  } catch {
256
- return text('Malformed URI', { status: 400 });
268
+ resolved_path = null;
269
+ return await handle();
257
270
  }
258
271
 
259
272
  // try to serve the rerouted prerendered resource if it exists
@@ -327,19 +340,8 @@ export async function internal_respond(request, options, manifest, state) {
327
340
  }
328
341
  }
329
342
 
330
- /** @type {import('types').RequiredResolveOptions} */
331
- let resolve_opts = {
332
- transformPageChunk: default_transform,
333
- filterSerializedResponseHeaders: default_filter,
334
- preload: default_preload
335
- };
336
-
337
- /** @type {import('types').TrailingSlash} */
338
- let trailing_slash = 'never';
339
-
340
343
  try {
341
- /** @type {PageNodes | undefined} */
342
- const page_nodes = route?.page
344
+ page_nodes = route?.page
343
345
  ? new PageNodes(await load_page_nodes(route.page, manifest))
344
346
  : undefined;
345
347
 
@@ -394,16 +396,36 @@ export async function internal_respond(request, options, manifest, state) {
394
396
  prerender = page_nodes.prerender();
395
397
  }
396
398
 
397
- if (state.before_handle) {
398
- state.before_handle(event, config, prerender);
399
- }
400
-
401
399
  if (state.emulator?.platform) {
402
400
  event.platform = await state.emulator.platform({ config, prerender });
403
401
  }
402
+
403
+ if (state.before_handle) {
404
+ return await state.before_handle(event, config, prerender, handle);
405
+ }
406
+ }
407
+ }
408
+
409
+ return await handle();
410
+ } catch (e) {
411
+ if (e instanceof Redirect) {
412
+ try {
413
+ const response =
414
+ is_data_request || remote_id
415
+ ? redirect_json_response(e)
416
+ : route?.page && is_action_json_request(event)
417
+ ? action_json_redirect(e)
418
+ : redirect_response(e.status, e.location);
419
+ add_cookies_to_headers(response.headers, new_cookies.values());
420
+ return response;
421
+ } catch (err) {
422
+ return await handle_fatal_error(event, event_state, options, err);
404
423
  }
405
424
  }
425
+ return await handle_fatal_error(event, event_state, options, e);
426
+ }
406
427
 
428
+ async function handle() {
407
429
  set_trailing_slash(trailing_slash);
408
430
 
409
431
  if (state.prerendering && !state.prerendering.fallback && !state.prerendering.inside_reroute) {
@@ -519,22 +541,6 @@ export async function internal_respond(request, options, manifest, state) {
519
541
  }
520
542
 
521
543
  return response;
522
- } catch (e) {
523
- if (e instanceof Redirect) {
524
- try {
525
- const response =
526
- is_data_request || remote_id
527
- ? redirect_json_response(e)
528
- : route?.page && is_action_json_request(event)
529
- ? action_json_redirect(e)
530
- : redirect_response(e.status, e.location);
531
- add_cookies_to_headers(response.headers, new_cookies.values());
532
- return response;
533
- } catch (err) {
534
- return await handle_fatal_error(event, event_state, options, err);
535
- }
536
- }
537
- return await handle_fatal_error(event, event_state, options, e);
538
544
  }
539
545
 
540
546
  /**
@@ -552,6 +558,23 @@ export async function internal_respond(request, options, manifest, state) {
552
558
  };
553
559
  }
554
560
 
561
+ if (resolved_path === null) {
562
+ return await respond_with_error({
563
+ event,
564
+ event_state,
565
+ options,
566
+ manifest,
567
+ state,
568
+ status: 400,
569
+ error: new SvelteKitError(
570
+ 400,
571
+ 'Malformed URI',
572
+ `Failed to decode URI: ${event.url.pathname}`
573
+ ),
574
+ resolve_opts
575
+ });
576
+ }
577
+
555
578
  if (options.hash_routing || state.prerendering?.fallback) {
556
579
  return await render_response({
557
580
  event,
@@ -1,3 +1,5 @@
1
+ /** @import { ServerHooks } from 'types' */
2
+ import * as devalue from 'devalue';
1
3
  import { DEV } from 'esm-env';
2
4
  import { json, text } from '@sveltejs/kit';
3
5
  import { HttpError } from '@sveltejs/kit/internal';
@@ -40,7 +42,7 @@ export function allowed_methods(mod) {
40
42
  * @param {import('types').SSROptions} options
41
43
  */
42
44
  export function get_global_name(options) {
43
- return DEV ? '__sveltekit_dev' : `__sveltekit_${options.version_hash}`;
45
+ return __SVELTEKIT_DEV__ ? '__sveltekit_dev' : `__sveltekit_${options.version_hash}`;
44
46
  }
45
47
 
46
48
  /**
@@ -53,7 +55,7 @@ export function get_global_name(options) {
53
55
  export function static_error_page(options, status, message) {
54
56
  let page = options.templates.error({ status, message: escape_html(message) });
55
57
 
56
- if (DEV) {
58
+ if (__SVELTEKIT_DEV__) {
57
59
  // inject Vite HMR client, for easier debugging
58
60
  page = page.replace('</head>', '<script type="module" src="/@vite/client"></script></head>');
59
61
  }
@@ -103,7 +105,7 @@ export async function handle_error_and_jsonify(event, state, options, error) {
103
105
  return { message: 'Unknown Error', ...error.body };
104
106
  }
105
107
 
106
- if (DEV && typeof error == 'object') {
108
+ if (__SVELTEKIT_DEV__ && typeof error == 'object') {
107
109
  fix_stack_trace(error);
108
110
  }
109
111
 
@@ -261,3 +263,21 @@ export function get_node_type(node_id) {
261
263
  export function count_non_ssi_comments(str) {
262
264
  return (str.match(/<!--(?!#)/g) ?? []).length;
263
265
  }
266
+
267
+ /**
268
+ * Creates a serialiser for non-arbitrary POJOs using the app's transport hook
269
+ * @param {ServerHooks['transport']} transport
270
+ * @returns {(thing: unknown) => string | undefined}
271
+ */
272
+ export function create_replacer(transport) {
273
+ /** @param {unknown} thing */
274
+ const replacer = (thing) => {
275
+ for (const key in transport) {
276
+ const encoded = transport[key].encode(thing);
277
+ if (encoded) {
278
+ return `app.decode('${key}', ${devalue.uneval(encoded, replacer)})`;
279
+ }
280
+ }
281
+ };
282
+ return replacer;
283
+ }
@@ -3,6 +3,11 @@ declare global {
3
3
  const __SVELTEKIT_APP_DIR__: string;
4
4
  const __SVELTEKIT_APP_VERSION_FILE__: string;
5
5
  const __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: number;
6
+ /**
7
+ * True if the user ran `vite dev`. This is different from `esm-env` because
8
+ * it is influenced by `NODE_ENV` which can still be true during `vite preview`
9
+ */
10
+ const __SVELTEKIT_DEV__: boolean;
6
11
  const __SVELTEKIT_EMBEDDED__: boolean;
7
12
  const __SVELTEKIT_PATHS_ASSETS__: string;
8
13
  const __SVELTEKIT_PATHS_BASE__: string;
@@ -35,6 +35,7 @@ import {
35
35
  } from './private.js';
36
36
  import { Span } from '@opentelemetry/api';
37
37
  import type { PageOptions } from '../exports/vite/static_analysis/index.js';
38
+ import { SharedIterator } from '../utils/shared-iterator.js';
38
39
 
39
40
  export interface ServerModule {
40
41
  Server: typeof InternalServer;
@@ -179,9 +180,15 @@ export class InternalServer extends Server {
179
180
  request: Request,
180
181
  options: RequestOptions & {
181
182
  prerendering?: PrerenderOptions;
182
- read: (file: string) => NonSharedBuffer;
183
- /** A hook called before `handle` during dev, so that `AsyncLocalStorage` can be populated. */
184
- before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void;
183
+ /** @internal for saving dependencies during prerendering and generating fallback pages */
184
+ read: (file: string) => Buffer<ArrayBuffer>;
185
+ /** @internal used during development to check feature availability depending on the current route */
186
+ before_handle?: (
187
+ event: RequestEvent,
188
+ config: any,
189
+ prerender: PrerenderOption,
190
+ handle: () => Promise<Response>
191
+ ) => Promise<Response>;
185
192
  emulator?: Emulator;
186
193
  }
187
194
  ): Promise<Response>;
@@ -469,7 +476,10 @@ export interface SSRNode {
469
476
  universal_id?: string;
470
477
  server_id?: string;
471
478
 
472
- /** inlined styles */
479
+ /**
480
+ * During development, all styles are inlined for the page to avoid FOUC.
481
+ * But in production, this stores styles that are below the inline threshold
482
+ */
473
483
  inline_styles?(): MaybePromise<
474
484
  Record<string, string | ((assets: string, base: string) => string)>
475
485
  >;
@@ -565,12 +575,18 @@ export interface SSRState {
565
575
  * prerender option is inherited by the endpoint, unless overridden.
566
576
  */
567
577
  prerender_default?: PrerenderOption;
568
- read?: (file: string) => NonSharedBuffer;
578
+ /** @internal reads from the filesystem when user code tries to fetch a static asset */
579
+ read?: (file: string) => Buffer<ArrayBuffer>;
569
580
  /**
570
581
  * Used to set up `__SVELTEKIT_TRACK__` which checks if a used feature is supported.
571
582
  * E.g. if `read` from `$app/server` is used, it checks whether the route's config is compatible.
572
583
  */
573
- before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void;
584
+ before_handle?: (
585
+ event: RequestEvent,
586
+ config: any,
587
+ prerender: PrerenderOption,
588
+ handle: () => Promise<Response>
589
+ ) => Promise<Response>;
574
590
  emulator?: Emulator;
575
591
  }
576
592
 
@@ -700,10 +716,31 @@ export interface RequestState {
700
716
  RemoteInternals,
701
717
  Record<string, { serialize: boolean; data: MaybePromise<any> }>
702
718
  >;
703
- forms: null | Map<any, any>;
719
+ /** Instances created via `myForm.for(...)` */
720
+ forms: null | Map<string, any>;
721
+ /** A map of remote function key to corresponding single-flight-mutation promise */
704
722
  refreshes: null | Map<string, Promise<any>>;
705
- reconnects: null | Map<string, Promise<any>>;
723
+ reconnects: null | Map<string, Promise<void>>;
724
+ /** A map of remote function ID to payloads requested for refreshing by the client */
706
725
  requested: null | Map<string, string[]>;
726
+ /** A map of query.batch ID to payloads requested for that batch within the same macrotask */
727
+ batches: null | Map<
728
+ string,
729
+ Map<
730
+ string,
731
+ {
732
+ get_validated: () => MaybePromise<any>;
733
+ resolvers: Array<{ resolve: (value: any) => void; reject: (error: any) => void }>;
734
+ }
735
+ >
736
+ >;
737
+ /**
738
+ * A per-request cache of shared live-query iterators, keyed by remote id
739
+ * and stringified argument payload. Used so that multiple `for await`
740
+ * consumers of the same `query.live` call within one request multiplex a
741
+ * single underlying user generator.
742
+ */
743
+ live_iterators: null | Map<string, SharedIterator<any>>;
707
744
  };
708
745
  readonly is_in_remote_function: boolean;
709
746
  readonly is_in_render: boolean;
@@ -3,6 +3,8 @@ import { decode_params } from './url.js';
3
3
 
4
4
  const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;
5
5
 
6
+ const root_group_pattern = /^\/\((?:.+)\)$/;
7
+
6
8
  /**
7
9
  * Creates the regex pattern, extracts parameter names, and generates types for a route
8
10
  * @param {string} id
@@ -12,7 +14,7 @@ export function parse_route_id(id) {
12
14
  const params = [];
13
15
 
14
16
  const pattern =
15
- id === '/'
17
+ id === '/' || root_group_pattern.test(id)
16
18
  ? /^\/$/
17
19
  : new RegExp(
18
20
  `^${get_route_segments(id)