@sveltejs/kit 2.30.1 → 2.31.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 (38) hide show
  1. package/package.json +12 -1
  2. package/src/core/adapt/builder.js +122 -13
  3. package/src/core/config/options.js +6 -0
  4. package/src/exports/hooks/sequence.js +53 -31
  5. package/src/{runtime/app/server → exports/internal}/event.js +24 -9
  6. package/src/exports/internal/server.js +22 -0
  7. package/src/exports/public.d.ts +117 -5
  8. package/src/exports/vite/dev/index.js +8 -0
  9. package/src/exports/vite/index.js +30 -3
  10. package/src/exports/vite/utils.js +44 -0
  11. package/src/runtime/app/server/index.js +1 -1
  12. package/src/runtime/app/server/remote/command.js +4 -5
  13. package/src/runtime/app/server/remote/form.js +5 -7
  14. package/src/runtime/app/server/remote/prerender.js +5 -7
  15. package/src/runtime/app/server/remote/query.js +6 -8
  16. package/src/runtime/app/server/remote/shared.js +9 -13
  17. package/src/runtime/client/client.js +9 -14
  18. package/src/runtime/server/data/index.js +10 -5
  19. package/src/runtime/server/endpoint.js +4 -3
  20. package/src/runtime/server/page/actions.js +55 -24
  21. package/src/runtime/server/page/index.js +22 -5
  22. package/src/runtime/server/page/load_data.js +131 -121
  23. package/src/runtime/server/page/render.js +15 -7
  24. package/src/runtime/server/page/respond_with_error.js +7 -2
  25. package/src/runtime/server/remote.js +59 -13
  26. package/src/runtime/server/respond.js +110 -34
  27. package/src/runtime/server/utils.js +20 -5
  28. package/src/runtime/shared.js +20 -0
  29. package/src/runtime/telemetry/noop.js +81 -0
  30. package/src/runtime/telemetry/otel.js +21 -0
  31. package/src/runtime/telemetry/record_span.js +65 -0
  32. package/src/types/global-private.d.ts +2 -0
  33. package/src/types/internal.d.ts +28 -0
  34. package/src/types/synthetic/$env+dynamic+private.md +1 -1
  35. package/src/version.js +1 -1
  36. package/types/index.d.ts +117 -4
  37. package/types/index.d.ts.map +2 -2
  38. package/src/runtime/server/event-state.js +0 -40
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "2.30.1",
3
+ "version": "2.31.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -33,6 +33,7 @@
33
33
  "sirv": "^3.0.0"
34
34
  },
35
35
  "devDependencies": {
36
+ "@opentelemetry/api": "^1.0.0",
36
37
  "@playwright/test": "^1.51.1",
37
38
  "@sveltejs/vite-plugin-svelte": "^6.0.0-next.3",
38
39
  "@types/connect": "^3.4.38",
@@ -48,9 +49,15 @@
48
49
  },
49
50
  "peerDependencies": {
50
51
  "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0",
52
+ "@opentelemetry/api": "^1.0.0",
51
53
  "svelte": "^4.0.0 || ^5.0.0-next.0",
52
54
  "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0"
53
55
  },
56
+ "peerDependenciesMeta": {
57
+ "@opentelemetry/api": {
58
+ "optional": true
59
+ }
60
+ },
54
61
  "bin": {
55
62
  "svelte-kit": "svelte-kit.js"
56
63
  },
@@ -72,6 +79,10 @@
72
79
  "types": "./types/index.d.ts",
73
80
  "import": "./src/exports/internal/index.js"
74
81
  },
82
+ "./internal/server": {
83
+ "types": "./types/index.d.ts",
84
+ "import": "./src/exports/internal/server.js"
85
+ },
75
86
  "./node": {
76
87
  "types": "./types/index.d.ts",
77
88
  "import": "./src/exports/node/index.js"
@@ -1,6 +1,10 @@
1
+ /** @import { Builder } from '@sveltejs/kit' */
2
+ /** @import { ResolvedConfig } from 'vite' */
3
+ /** @import { RouteDefinition } from '@sveltejs/kit' */
4
+ /** @import { RouteData, ValidatedConfig, BuildData, ServerMetadata, ServerMetadataRoute, Prerendered, PrerenderMap, Logger } from 'types' */
1
5
  import colors from 'kleur';
2
6
  import { createReadStream, createWriteStream, existsSync, statSync } from 'node:fs';
3
- import { extname, resolve } from 'node:path';
7
+ import { extname, resolve, join, dirname, relative } from 'node:path';
4
8
  import { pipeline } from 'node:stream';
5
9
  import { promisify } from 'node:util';
6
10
  import zlib from 'node:zlib';
@@ -12,6 +16,7 @@ import generate_fallback from '../postbuild/fallback.js';
12
16
  import { write } from '../sync/utils.js';
13
17
  import { list_files } from '../utils.js';
14
18
  import { find_server_assets } from '../generate_manifest/find_server_assets.js';
19
+ import { reserved } from '../env.js';
15
20
 
16
21
  const pipe = promisify(pipeline);
17
22
  const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.wasm'];
@@ -19,16 +24,16 @@ const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.w
19
24
  /**
20
25
  * Creates the Builder which is passed to adapters for building the application.
21
26
  * @param {{
22
- * config: import('types').ValidatedConfig;
23
- * build_data: import('types').BuildData;
24
- * server_metadata: import('types').ServerMetadata;
25
- * route_data: import('types').RouteData[];
26
- * prerendered: import('types').Prerendered;
27
- * prerender_map: import('types').PrerenderMap;
28
- * log: import('types').Logger;
29
- * vite_config: import('vite').ResolvedConfig;
27
+ * config: ValidatedConfig;
28
+ * build_data: BuildData;
29
+ * server_metadata: ServerMetadata;
30
+ * route_data: RouteData[];
31
+ * prerendered: Prerendered;
32
+ * prerender_map: PrerenderMap;
33
+ * log: Logger;
34
+ * vite_config: ResolvedConfig;
30
35
  * }} opts
31
- * @returns {import('@sveltejs/kit').Builder}
36
+ * @returns {Builder}
32
37
  */
33
38
  export function create_builder({
34
39
  config,
@@ -40,7 +45,7 @@ export function create_builder({
40
45
  log,
41
46
  vite_config
42
47
  }) {
43
- /** @type {Map<import('@sveltejs/kit').RouteDefinition, import('types').RouteData>} */
48
+ /** @type {Map<RouteDefinition, RouteData>} */
44
49
  const lookup = new Map();
45
50
 
46
51
  /**
@@ -48,11 +53,11 @@ export function create_builder({
48
53
  * we expose a stable type that adapters can use to group/filter routes
49
54
  */
50
55
  const routes = route_data.map((route) => {
51
- const { config, methods, page, api } = /** @type {import('types').ServerMetadataRoute} */ (
56
+ const { config, methods, page, api } = /** @type {ServerMetadataRoute} */ (
52
57
  server_metadata.routes.get(route.id)
53
58
  );
54
59
 
55
- /** @type {import('@sveltejs/kit').RouteDefinition} */
60
+ /** @type {RouteDefinition} */
56
61
  const facade = {
57
62
  id: route.id,
58
63
  api,
@@ -229,6 +234,53 @@ export function create_builder({
229
234
 
230
235
  writeServer(dest) {
231
236
  return copy(`${config.kit.outDir}/output/server`, dest);
237
+ },
238
+
239
+ hasServerInstrumentationFile() {
240
+ return existsSync(`${config.kit.outDir}/output/server/instrumentation.server.js`);
241
+ },
242
+
243
+ instrument({
244
+ entrypoint,
245
+ instrumentation,
246
+ start = join(dirname(entrypoint), 'start.js'),
247
+ module = {
248
+ exports: ['default']
249
+ }
250
+ }) {
251
+ if (!existsSync(instrumentation)) {
252
+ throw new Error(
253
+ `Instrumentation file ${instrumentation} not found. This is probably a bug in your adapter.`
254
+ );
255
+ }
256
+ if (!existsSync(entrypoint)) {
257
+ throw new Error(
258
+ `Entrypoint file ${entrypoint} not found. This is probably a bug in your adapter.`
259
+ );
260
+ }
261
+
262
+ copy(entrypoint, start);
263
+ if (existsSync(`${entrypoint}.map`)) {
264
+ copy(`${entrypoint}.map`, `${start}.map`);
265
+ }
266
+
267
+ const relative_instrumentation = relative(dirname(entrypoint), instrumentation);
268
+ const relative_start = relative(dirname(entrypoint), start);
269
+
270
+ const facade =
271
+ 'generateText' in module
272
+ ? module.generateText({
273
+ instrumentation: relative_instrumentation,
274
+ start: relative_start
275
+ })
276
+ : create_instrumentation_facade({
277
+ instrumentation: relative_instrumentation,
278
+ start: relative_start,
279
+ exports: module.exports
280
+ });
281
+
282
+ rimraf(entrypoint);
283
+ write(entrypoint, facade);
232
284
  }
233
285
  };
234
286
  }
@@ -254,3 +306,60 @@ async function compress_file(file, format = 'gz') {
254
306
 
255
307
  await pipe(source, compress, destination);
256
308
  }
309
+
310
+ /**
311
+ * Given a list of exports, generate a facade that:
312
+ * - Imports the instrumentation file
313
+ * - Imports `exports` from the entrypoint (dynamically, if `tla` is true)
314
+ * - Re-exports `exports` from the entrypoint
315
+ *
316
+ * `default` receives special treatment: It will be imported as `default` and exported with `export default`.
317
+ *
318
+ * @param {{ instrumentation: string; start: string; exports: string[] }} opts
319
+ * @returns {string}
320
+ */
321
+ function create_instrumentation_facade({ instrumentation, start, exports }) {
322
+ const import_instrumentation = `import './${instrumentation}';`;
323
+
324
+ let alias_index = 0;
325
+ const aliases = new Map();
326
+
327
+ for (const name of exports.filter((name) => reserved.has(name))) {
328
+ /*
329
+ * you can do evil things like `export { c as class }`.
330
+ * in order to import these, you need to alias them, and then un-alias them when re-exporting
331
+ * this map will allow us to generate the following:
332
+ * import { class as _1 } from 'entrypoint';
333
+ * export { _1 as class };
334
+ */
335
+ let alias = `_${alias_index++}`;
336
+ while (exports.includes(alias)) {
337
+ alias = `_${alias_index++}`;
338
+ }
339
+
340
+ aliases.set(name, alias);
341
+ }
342
+
343
+ const import_statements = [];
344
+ const export_statements = [];
345
+
346
+ for (const name of exports) {
347
+ const alias = aliases.get(name);
348
+ if (alias) {
349
+ import_statements.push(`${name}: ${alias}`);
350
+ export_statements.push(`${alias} as ${name}`);
351
+ } else {
352
+ import_statements.push(`${name}`);
353
+ export_statements.push(`${name}`);
354
+ }
355
+ }
356
+
357
+ const entrypoint_facade = [
358
+ `const { ${import_statements.join(', ')} } = await import('./${start}');`,
359
+ export_statements.length > 0 ? `export { ${export_statements.join(', ')} };` : ''
360
+ ]
361
+ .filter(Boolean)
362
+ .join('\n');
363
+
364
+ return `${import_instrumentation}\n${entrypoint_facade}`;
365
+ }
@@ -120,6 +120,12 @@ const options = object(
120
120
  }),
121
121
 
122
122
  experimental: object({
123
+ tracing: object({
124
+ server: boolean(false)
125
+ }),
126
+ instrumentation: object({
127
+ server: boolean(false)
128
+ }),
123
129
  remoteFunctions: boolean(false)
124
130
  }),
125
131
 
@@ -1,3 +1,11 @@
1
+ /** @import { Handle, RequestEvent, ResolveOptions } from '@sveltejs/kit' */
2
+ /** @import { MaybePromise } from 'types' */
3
+ import {
4
+ merge_tracing,
5
+ get_request_store,
6
+ with_request_store
7
+ } from '@sveltejs/kit/internal/server';
8
+
1
9
  /**
2
10
  * A helper function for sequencing multiple `handle` calls in a middleware-like manner.
3
11
  * The behavior for the `handle` options is as follows:
@@ -66,56 +74,70 @@
66
74
  * first post-processing
67
75
  * ```
68
76
  *
69
- * @param {...import('@sveltejs/kit').Handle} handlers The chain of `handle` functions
70
- * @returns {import('@sveltejs/kit').Handle}
77
+ * @param {...Handle} handlers The chain of `handle` functions
78
+ * @returns {Handle}
71
79
  */
72
80
  export function sequence(...handlers) {
73
81
  const length = handlers.length;
74
82
  if (!length) return ({ event, resolve }) => resolve(event);
75
83
 
76
84
  return ({ event, resolve }) => {
85
+ const { state } = get_request_store();
77
86
  return apply_handle(0, event, {});
78
87
 
79
88
  /**
80
89
  * @param {number} i
81
- * @param {import('@sveltejs/kit').RequestEvent} event
82
- * @param {import('@sveltejs/kit').ResolveOptions | undefined} parent_options
83
- * @returns {import('types').MaybePromise<Response>}
90
+ * @param {RequestEvent} event
91
+ * @param {ResolveOptions | undefined} parent_options
92
+ * @returns {MaybePromise<Response>}
84
93
  */
85
94
  function apply_handle(i, event, parent_options) {
86
95
  const handle = handlers[i];
87
96
 
88
- return handle({
89
- event,
90
- resolve: (event, options) => {
91
- /** @type {import('@sveltejs/kit').ResolveOptions['transformPageChunk']} */
92
- const transformPageChunk = async ({ html, done }) => {
93
- if (options?.transformPageChunk) {
94
- html = (await options.transformPageChunk({ html, done })) ?? '';
95
- }
97
+ return state.tracing.record_span({
98
+ name: `sveltekit.handle.sequenced.${handle.name ? handle.name : i}`,
99
+ attributes: {},
100
+ fn: async (current) => {
101
+ const traced_event = merge_tracing(event, current);
102
+ return await with_request_store({ event: traced_event, state }, () =>
103
+ handle({
104
+ event: traced_event,
105
+ resolve: (event, options) => {
106
+ /** @type {ResolveOptions['transformPageChunk']} */
107
+ const transformPageChunk = async ({ html, done }) => {
108
+ if (options?.transformPageChunk) {
109
+ html = (await options.transformPageChunk({ html, done })) ?? '';
110
+ }
96
111
 
97
- if (parent_options?.transformPageChunk) {
98
- html = (await parent_options.transformPageChunk({ html, done })) ?? '';
99
- }
112
+ if (parent_options?.transformPageChunk) {
113
+ html = (await parent_options.transformPageChunk({ html, done })) ?? '';
114
+ }
100
115
 
101
- return html;
102
- };
116
+ return html;
117
+ };
103
118
 
104
- /** @type {import('@sveltejs/kit').ResolveOptions['filterSerializedResponseHeaders']} */
105
- const filterSerializedResponseHeaders =
106
- parent_options?.filterSerializedResponseHeaders ??
107
- options?.filterSerializedResponseHeaders;
119
+ /** @type {ResolveOptions['filterSerializedResponseHeaders']} */
120
+ const filterSerializedResponseHeaders =
121
+ parent_options?.filterSerializedResponseHeaders ??
122
+ options?.filterSerializedResponseHeaders;
108
123
 
109
- /** @type {import('@sveltejs/kit').ResolveOptions['preload']} */
110
- const preload = parent_options?.preload ?? options?.preload;
124
+ /** @type {ResolveOptions['preload']} */
125
+ const preload = parent_options?.preload ?? options?.preload;
111
126
 
112
- return i < length - 1
113
- ? apply_handle(i + 1, event, {
114
- transformPageChunk,
115
- filterSerializedResponseHeaders,
116
- preload
117
- })
118
- : resolve(event, { transformPageChunk, filterSerializedResponseHeaders, preload });
127
+ return i < length - 1
128
+ ? apply_handle(i + 1, event, {
129
+ transformPageChunk,
130
+ filterSerializedResponseHeaders,
131
+ preload
132
+ })
133
+ : resolve(event, {
134
+ transformPageChunk,
135
+ filterSerializedResponseHeaders,
136
+ preload
137
+ });
138
+ }
139
+ })
140
+ );
119
141
  }
120
142
  });
121
143
  }
@@ -1,9 +1,11 @@
1
1
  /** @import { RequestEvent } from '@sveltejs/kit' */
2
+ /** @import { RequestStore } from 'types' */
3
+ /** @import { AsyncLocalStorage } from 'node:async_hooks' */
2
4
 
3
- /** @type {RequestEvent | null} */
4
- let request_event = null;
5
+ /** @type {RequestStore | null} */
6
+ let sync_store = null;
5
7
 
6
- /** @type {import('node:async_hooks').AsyncLocalStorage<RequestEvent | null>} */
8
+ /** @type {AsyncLocalStorage<RequestStore | null> | null} */
7
9
  let als;
8
10
 
9
11
  import('node:async_hooks')
@@ -19,10 +21,11 @@ import('node:async_hooks')
19
21
  *
20
22
  * In environments without [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage), this must be called synchronously (i.e. not after an `await`).
21
23
  * @since 2.20.0
24
+ *
22
25
  * @returns {RequestEvent}
23
26
  */
24
27
  export function getRequestEvent() {
25
- const event = request_event ?? als?.getStore();
28
+ const event = try_get_request_store()?.event;
26
29
 
27
30
  if (!event) {
28
31
  let message =
@@ -39,16 +42,28 @@ export function getRequestEvent() {
39
42
  return event;
40
43
  }
41
44
 
45
+ export function get_request_store() {
46
+ const result = try_get_request_store();
47
+ if (!result) {
48
+ throw new Error('Could not get the request store. This is an internal error.');
49
+ }
50
+ return result;
51
+ }
52
+
53
+ export function try_get_request_store() {
54
+ return sync_store ?? als?.getStore() ?? null;
55
+ }
56
+
42
57
  /**
43
58
  * @template T
44
- * @param {RequestEvent | null} event
59
+ * @param {RequestStore | null} store
45
60
  * @param {() => T} fn
46
61
  */
47
- export function with_event(event, fn) {
62
+ export function with_request_store(store, fn) {
48
63
  try {
49
- request_event = event;
50
- return als ? als.run(event, fn) : fn();
64
+ sync_store = store;
65
+ return als ? als.run(store, fn) : fn();
51
66
  } finally {
52
- request_event = null;
67
+ sync_store = null;
53
68
  }
54
69
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @template {{ tracing: { enabled: boolean, root: import('@opentelemetry/api').Span, current: import('@opentelemetry/api').Span } }} T
3
+ * @param {T} event_like
4
+ * @param {import('@opentelemetry/api').Span} current
5
+ * @returns {T}
6
+ */
7
+ export function merge_tracing(event_like, current) {
8
+ return {
9
+ ...event_like,
10
+ tracing: {
11
+ ...event_like.tracing,
12
+ current
13
+ }
14
+ };
15
+ }
16
+
17
+ export {
18
+ with_request_store,
19
+ getRequestEvent,
20
+ get_request_store,
21
+ try_get_request_store
22
+ } from './event.js';
@@ -17,13 +17,14 @@ import {
17
17
  RouteSegment
18
18
  } from '../types/private.js';
19
19
  import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types';
20
- import type { SvelteConfig } from '@sveltejs/vite-plugin-svelte';
21
- import type { StandardSchemaV1 } from '@standard-schema/spec';
20
+ import { SvelteConfig } from '@sveltejs/vite-plugin-svelte';
21
+ import { StandardSchemaV1 } from '@standard-schema/spec';
22
22
  import {
23
23
  RouteId as AppRouteId,
24
24
  LayoutParams as AppLayoutParams,
25
25
  ResolvedPathname
26
26
  } from '$app/types';
27
+ import { Span } from '@opentelemetry/api';
27
28
 
28
29
  export { PrerenderOption } from '../types/private.js';
29
30
 
@@ -49,6 +50,12 @@ export interface Adapter {
49
50
  * @param details.config The merged route config
50
51
  */
51
52
  read?: (details: { config: any; route: { id: string } }) => boolean;
53
+
54
+ /**
55
+ * Test support for `instrumentation.server.js`. To pass, the adapter must support running `instrumentation.server.js` prior to the application code.
56
+ * @since 2.31.0
57
+ */
58
+ instrumentation?: () => boolean;
52
59
  };
53
60
  /**
54
61
  * Creates an `Emulator`, which allows the adapter to influence the environment
@@ -186,6 +193,47 @@ export interface Builder {
186
193
  }
187
194
  ) => string[];
188
195
 
196
+ /**
197
+ * Check if the server instrumentation file exists.
198
+ * @returns true if the server instrumentation file exists, false otherwise
199
+ * @since 2.31.0
200
+ */
201
+ hasServerInstrumentationFile: () => boolean;
202
+
203
+ /**
204
+ * Instrument `entrypoint` with `instrumentation`.
205
+ *
206
+ * Renames `entrypoint` to `start` and creates a new module at
207
+ * `entrypoint` which imports `instrumentation` and then dynamically imports `start`. This allows
208
+ * the module hooks necessary for instrumentation libraries to be loaded prior to any application code.
209
+ *
210
+ * Caveats:
211
+ * - "Live exports" will not work. If your adapter uses live exports, your users will need to manually import the server instrumentation on startup.
212
+ * - If `tla` is `false`, OTEL auto-instrumentation may not work properly. Use it if your environment supports it.
213
+ * - Use `hasServerInstrumentationFile` to check if the user has a server instrumentation file; if they don't, you shouldn't do this.
214
+ *
215
+ * @param options an object containing the following properties:
216
+ * @param options.entrypoint the path to the entrypoint to trace.
217
+ * @param options.instrumentation the path to the instrumentation file.
218
+ * @param options.start the name of the start file. This is what `entrypoint` will be renamed to.
219
+ * @param options.module configuration for the resulting entrypoint module.
220
+ * @param options.module.exports
221
+ * @param options.module.generateText a function that receives the relative paths to the instrumentation and start files, and generates the text of the module to be traced. If not provided, the default implementation will be used, which uses top-level await.
222
+ * @since 2.31.0
223
+ */
224
+ instrument: (args: {
225
+ entrypoint: string;
226
+ instrumentation: string;
227
+ start?: string;
228
+ module?:
229
+ | {
230
+ exports: string[];
231
+ }
232
+ | {
233
+ generateText: (args: { instrumentation: string; start: string }) => string;
234
+ };
235
+ }) => void;
236
+
189
237
  /**
190
238
  * Compress files in `directory` with gzip and brotli, where appropriate. Generates `.gz` and `.br` files alongside the originals.
191
239
  * @param {string} directory The directory containing the files to be compressed
@@ -407,10 +455,34 @@ export interface KitConfig {
407
455
  */
408
456
  privatePrefix?: string;
409
457
  };
410
- /**
411
- * Experimental features which are exempt from semantic versioning. These features may be changed or removed at any time.
412
- */
458
+ /** Experimental features. Here be dragons. These are not subject to semantic versioning, so breaking changes or removal can happen in any release. */
413
459
  experimental?: {
460
+ /**
461
+ * Options for enabling server-side [OpenTelemetry](https://opentelemetry.io/) tracing for SvelteKit operations including the [`handle` hook](https://svelte.dev/docs/kit/hooks#Server-hooks-handle), [`load` functions](https://svelte.dev/docs/kit/load), [form actions](https://svelte.dev/docs/kit/form-actions), and [remote functions](https://svelte.dev/docs/kit/remote-functions).
462
+ * @default { server: false, serverFile: false }
463
+ * @since 2.31.0
464
+ */
465
+ tracing?: {
466
+ /**
467
+ * Enables server-side [OpenTelemetry](https://opentelemetry.io/) span emission for SvelteKit operations including the [`handle` hook](https://svelte.dev/docs/kit/hooks#Server-hooks-handle), [`load` functions](https://svelte.dev/docs/kit/load), [form actions](https://svelte.dev/docs/kit/form-actions), and [remote functions](https://svelte.dev/docs/kit/remote-functions).
468
+ * @default false
469
+ * @since 2.31.0
470
+ */
471
+ server?: boolean;
472
+ };
473
+
474
+ /**
475
+ * @since 2.31.0
476
+ */
477
+ instrumentation?: {
478
+ /**
479
+ * Enables `instrumentation.server.js` for tracing and observability instrumentation.
480
+ * @default false
481
+ * @since 2.31.0
482
+ */
483
+ server?: boolean;
484
+ };
485
+
414
486
  /**
415
487
  * Whether to enable the experimental remote functions feature. This feature is not yet stable and may be changed or removed at any time.
416
488
  * @default false
@@ -1026,6 +1098,19 @@ export interface LoadEvent<
1026
1098
  * ```
1027
1099
  */
1028
1100
  untrack: <T>(fn: () => T) => T;
1101
+
1102
+ /**
1103
+ * Access to spans for tracing. If tracing is not enabled or the function is being run in the browser, these spans will do nothing.
1104
+ * @since 2.31.0
1105
+ */
1106
+ tracing: {
1107
+ /** Whether tracing is enabled. */
1108
+ enabled: boolean;
1109
+ /** The root span for the request. This span is named `sveltekit.handle.root`. */
1110
+ root: Span;
1111
+ /** The span associated with the current `load` function. */
1112
+ current: Span;
1113
+ };
1029
1114
  }
1030
1115
 
1031
1116
  export interface NavigationEvent<
@@ -1304,6 +1389,20 @@ export interface RequestEvent<
1304
1389
  * `true` for `+server.js` calls coming from SvelteKit without the overhead of actually making an HTTP request. This happens when you make same-origin `fetch` requests on the server.
1305
1390
  */
1306
1391
  isSubRequest: boolean;
1392
+
1393
+ /**
1394
+ * Access to spans for tracing. If tracing is not enabled, these spans will do nothing.
1395
+ * @since 2.31.0
1396
+ */
1397
+ tracing: {
1398
+ /** Whether tracing is enabled. */
1399
+ enabled: boolean;
1400
+ /** The root span for the request. This span is named `sveltekit.handle.root`. */
1401
+ root: Span;
1402
+ /** The span associated with the current `handle` hook, `load` function, or form action. */
1403
+ current: Span;
1404
+ };
1405
+
1307
1406
  /**
1308
1407
  * `true` if the request comes from the client via a remote function. The `url` property will be stripped of the internal information
1309
1408
  * related to the data request in this case. Use this property instead if the distinction is important to you.
@@ -1467,6 +1566,19 @@ export interface ServerLoadEvent<
1467
1566
  * ```
1468
1567
  */
1469
1568
  untrack: <T>(fn: () => T) => T;
1569
+
1570
+ /**
1571
+ * Access to spans for tracing. If tracing is not enabled, these spans will do nothing.
1572
+ * @since 2.31.0
1573
+ */
1574
+ tracing: {
1575
+ /** Whether tracing is enabled. */
1576
+ enabled: boolean;
1577
+ /** The root span for the request. This span is named `sveltekit.handle.root`. */
1578
+ root: Span;
1579
+ /** The span associated with the current server `load` function. */
1580
+ current: Span;
1581
+ };
1470
1582
  }
1471
1583
 
1472
1584
  /**
@@ -501,6 +501,14 @@ export async function dev(vite, vite_config, svelte_config) {
501
501
  return;
502
502
  }
503
503
 
504
+ const resolved_instrumentation = resolve_entry(
505
+ path.join(svelte_config.kit.files.src, 'instrumentation.server')
506
+ );
507
+
508
+ if (resolved_instrumentation) {
509
+ await vite.ssrLoadModule(resolved_instrumentation);
510
+ }
511
+
504
512
  // we have to import `Server` before calling `set_assets`
505
513
  const { Server } = /** @type {import('types').ServerModule} */ (
506
514
  await vite.ssrLoadModule(`${runtime_base}/server/index.js`, { fixStacktrace: true })