@sveltejs/kit 3.0.0-next.16 → 3.0.0-next.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "3.0.0-next.16",
3
+ "version": "3.0.0-next.18",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -232,6 +232,7 @@ function update_types(config, routes, route, root, to_delete = new Set()) {
232
232
  'type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;',
233
233
 
234
234
  // Re-export `Snapshot` from @sveltejs/kit — in future we could use this to infer <T> from the return type of `snapshot.capture`
235
+ '/** @deprecated Use the `snapshot` helper from `$app/navigation` instead. */',
235
236
  'export type Snapshot<T = any> = Kit.Snapshot<T>;',
236
237
 
237
238
  'export type ErrorProps = { error: App.Error };'
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  merge_tracing,
4
4
  get_request_store,
5
+ record_span,
5
6
  with_request_store
6
7
  } from '@sveltejs/kit/internal/server';
7
8
 
@@ -95,7 +96,7 @@ export function sequence(...handlers) {
95
96
  function apply_handle(i, event, parent_options) {
96
97
  const handle = handlers[i];
97
98
 
98
- return state.tracing.record_span({
99
+ return record_span({
99
100
  name: `sveltekit.handle.sequenced.${handle.name ? handle.name : i}`,
100
101
  attributes: {},
101
102
  fn: async (current) => {
@@ -32,4 +32,6 @@ export {
32
32
 
33
33
  export { init_remote_functions } from './remote-functions.js';
34
34
 
35
+ export { init_tracing, otel, record_span } from './telemetry.js';
36
+
35
37
  export * from '../shared.js';
@@ -0,0 +1,95 @@
1
+ /** @import { Tracer, SpanStatusCode, PropagationAPI, ContextAPI } from '@opentelemetry/api' */
2
+ /** @import { RecordSpan } from 'types' */
3
+ import { HttpError, Redirect } from '../shared.js';
4
+ import { noop_span } from '../../../runtime/telemetry/noop.js';
5
+
6
+ // Import this module by its bare specifier so bundled and external code share its state.
7
+
8
+ /** @type {Promise<{ tracer: Tracer, SpanStatusCode: typeof SpanStatusCode, propagation: PropagationAPI, context: ContextAPI }> | null} */
9
+ export let otel = null;
10
+
11
+ /**
12
+ * The caller passes in `import('@opentelemetry/api')` so the import lives behind
13
+ * `__SVELTEKIT_SERVER_TRACING_ENABLED__` in the bundled runtime and is eliminated
14
+ * from builds with tracing disabled, where the package may not be installed.
15
+ * @param {Promise<typeof import('@opentelemetry/api')>} api
16
+ * @returns {void}
17
+ */
18
+ export function init_tracing(api) {
19
+ otel ??= api
20
+ .then((module) => {
21
+ return {
22
+ tracer: module.trace.getTracer('sveltekit'),
23
+ propagation: module.propagation,
24
+ context: module.context,
25
+ SpanStatusCode: module.SpanStatusCode
26
+ };
27
+ })
28
+ .catch(() => {
29
+ throw new Error(
30
+ 'Tracing is enabled (see the SvelteKit plugin `tracing.server` option in your vite.config.js), but `@opentelemetry/api` is not available. This error will likely resolve itself when you set up your tracing instrumentation in `instrumentation.server.js`. For more information, see https://svelte.dev/docs/kit/observability#opentelemetry-api'
31
+ );
32
+ });
33
+ }
34
+
35
+ /** @type {RecordSpan} */
36
+ export async function record_span({ name, attributes, fn }) {
37
+ if (otel === null) {
38
+ return fn(noop_span);
39
+ }
40
+
41
+ const { SpanStatusCode, tracer } = await otel;
42
+
43
+ return tracer.startActiveSpan(name, { attributes }, async (span) => {
44
+ try {
45
+ return await fn(span);
46
+ } catch (error) {
47
+ if (error instanceof HttpError) {
48
+ span.setAttributes({
49
+ [`${name}.result.type`]: 'known_error',
50
+ [`${name}.result.status`]: error.status,
51
+ [`${name}.result.message`]: error.body.message
52
+ });
53
+ if (error.status >= 500) {
54
+ span.recordException({
55
+ name: 'HttpError',
56
+ message: error.body.message
57
+ });
58
+ span.setStatus({
59
+ code: SpanStatusCode.ERROR,
60
+ message: error.body.message
61
+ });
62
+ }
63
+ } else if (error instanceof Redirect) {
64
+ span.setAttributes({
65
+ [`${name}.result.type`]: 'redirect',
66
+ [`${name}.result.status`]: error.status,
67
+ [`${name}.result.location`]: error.location
68
+ });
69
+ } else if (error instanceof Error) {
70
+ span.setAttributes({
71
+ [`${name}.result.type`]: 'unknown_error'
72
+ });
73
+ span.recordException({
74
+ name: error.name,
75
+ message: error.message,
76
+ // conditional so this compiles under consumers' `exactOptionalPropertyTypes`
77
+ ...(error.stack !== undefined && { stack: error.stack })
78
+ });
79
+ span.setStatus({
80
+ code: SpanStatusCode.ERROR,
81
+ message: error.message
82
+ });
83
+ } else {
84
+ span.setAttributes({
85
+ [`${name}.result.type`]: 'unknown_error'
86
+ });
87
+ span.setStatus({ code: SpanStatusCode.ERROR });
88
+ }
89
+
90
+ throw error;
91
+ } finally {
92
+ span.end();
93
+ }
94
+ });
95
+ }
@@ -72,11 +72,18 @@ export interface Adapter {
72
72
  */
73
73
  emulate?: () => MaybePromise<Emulator>;
74
74
  vite?: {
75
- /**
76
- * Plugins provided by the adapter are placed before any of SvelteKit's own plugins.
77
- * @since 3.0.0
78
- */
79
- plugins?: Plugin[];
75
+ plugins?: {
76
+ /**
77
+ * Vite plugins placed before any of SvelteKit's own plugins.
78
+ * @since 3.0.0
79
+ */
80
+ pre?: Plugin[];
81
+ /**
82
+ * Vite plugins placed after any of SvelteKit's own plugins.
83
+ * @since 3.0.0
84
+ */
85
+ post?: Plugin[];
86
+ };
80
87
  };
81
88
  }
82
89
 
@@ -460,7 +467,10 @@ export interface KitConfig {
460
467
  *
461
468
  * CSRF checks only apply in production, not in local development.
462
469
  * @default []
463
- * @example ['https://checkout.stripe.com', 'https://accounts.google.com']
470
+ * @example
471
+ * ```js
472
+ * ['https://checkout.stripe.com', 'https://accounts.google.com']
473
+ * ```
464
474
  */
465
475
  trustedOrigins?: string[];
466
476
  };
@@ -1941,15 +1951,20 @@ export type Actions<
1941
1951
  * };
1942
1952
  * }}
1943
1953
  * ```
1954
+ *
1955
+ * Success and failure results carry the root-relative `pathname + search` of the action URL, with
1956
+ * the `?/actionName` parameter removed. Redirect results carry the redirect target. Server-generated
1957
+ * error results also carry the action location, while client-generated errors such as network
1958
+ * failures do not. `update` uses this location to emulate native form navigation.
1944
1959
  */
1945
1960
  export type ActionResult<
1946
1961
  Success extends Record<string, unknown> | undefined = Record<string, any>,
1947
1962
  Failure extends Record<string, unknown> | undefined = Record<string, any>
1948
1963
  > =
1949
- | { type: 'success'; status: number; data?: Success }
1950
- | { type: 'failure'; status: number; data?: Failure }
1964
+ | { type: 'success'; status: number; data?: Success; location: string }
1965
+ | { type: 'failure'; status: number; data?: Failure; location: string }
1951
1966
  | { type: 'redirect'; status: number; location: string }
1952
- | { type: 'error'; status?: number; error: App.Error };
1967
+ | { type: 'error'; status?: number; error: App.Error; location?: string };
1953
1968
 
1954
1969
  /**
1955
1970
  * The object returned by the [`error`](https://svelte.dev/docs/kit/@sveltejs-kit#error) function.
@@ -1990,15 +2005,21 @@ export type SubmitFunction<
1990
2005
  result: ActionResult<Success, Failure>;
1991
2006
  /**
1992
2007
  * Call this to get the default behavior of a form submission response.
1993
- * @param options Set `reset: false` if you don't want the `<form>` values to be reset after a successful submission.
1994
- * @param invalidateAll Set `invalidateAll: false` if you don't want the action to call `invalidateAll` after submission.
2008
+ * @param options Set `reset: false` if you don't want the `<form>` values to be reset after a successful submission. `refreshAll` defaults to `true` for successful results and `false` for failures. When the submission navigates, setting it to `false` still runs the destination's `load` functions but may reuse shared layout data. Set `navigate: false` to apply non-redirect results to the current page instead of navigating to `result.location`. Redirects are always followed.
1995
2009
  */
1996
- update: (options?: { reset?: boolean; invalidateAll?: boolean }) => Promise<void>;
2010
+ update: (options?: {
2011
+ reset?: boolean;
2012
+ refreshAll?: boolean;
2013
+ navigate?: boolean;
2014
+ /** @deprecated Use `refreshAll` instead. */
2015
+ invalidateAll?: boolean;
2016
+ }) => Promise<void>;
1997
2017
  }) => MaybePromise<void>)
1998
2018
  >;
1999
2019
 
2000
2020
  /**
2001
2021
  * The type of `export const snapshot` exported from a page or layout component.
2022
+ * @deprecated Use the [`snapshot`](https://svelte.dev/docs/kit/$app-navigation#snapshot) helper from `$app/navigation` instead.
2002
2023
  */
2003
2024
  export interface Snapshot<T = any> {
2004
2025
  capture: () => T;
@@ -2056,7 +2056,7 @@ function kit({ svelte_config }) {
2056
2056
 
2057
2057
  return /** @type {Plugin[]} */ (
2058
2058
  [
2059
- svelte_config.kit.adapter?.vite?.plugins,
2059
+ svelte_config.kit.adapter?.vite?.plugins?.pre,
2060
2060
  plugin_resolve_root,
2061
2061
  plugin_setup,
2062
2062
  plugin_remote_guard,
@@ -2066,7 +2066,8 @@ function kit({ svelte_config }) {
2066
2066
  plugin_service_worker,
2067
2067
  plugin_service_worker_env,
2068
2068
  plugin_compile,
2069
- plugin_adapter
2069
+ plugin_adapter,
2070
+ svelte_config.kit.adapter?.vite?.plugins?.post
2070
2071
  ].filter(Boolean)
2071
2072
  );
2072
2073
  }
@@ -1,7 +1,12 @@
1
1
  import { DEV } from 'esm-env';
2
2
  import { noop } from '../../utils/functions.js';
3
3
  import { refreshAll } from './navigation.js';
4
- import { applyAction, handle_error } from '../client/client.js';
4
+ import {
5
+ applyAction,
6
+ apply_action_navigation,
7
+ handle_error,
8
+ is_current_location
9
+ } from '../client/client.js';
5
10
  import { notify_version } from '../client/state.svelte.js';
6
11
  import { parse } from '#app/internal/transport';
7
12
 
@@ -30,10 +35,6 @@ export { applyAction };
30
35
  * @returns {import('@sveltejs/kit').ActionResult<Success, Failure>}
31
36
  */
32
37
  export function deserialize(result) {
33
- if (result === '') {
34
- return { type: 'success', status: 204, data: undefined };
35
- }
36
-
37
38
  const parsed = JSON.parse(result);
38
39
 
39
40
  if (parsed.data) {
@@ -63,17 +64,18 @@ function clone(element) {
63
64
  * If a function is returned, that function is called with the response from the server.
64
65
  * If nothing is returned, the fallback will be used.
65
66
  *
66
- * If this function or its return value isn't set, it
67
- * - falls back to updating the `form` prop with the returned data if the action is on the same page as the form
68
- * - updates `page.status`
69
- * - resets the `<form>` element and invalidates all data in case of successful submission with no redirect response
67
+ * If this function or its return value isn't set, it emulates the browser-native behaviour, just without the full-page reload. It
68
+ * - resets the `<form>` element and refreshes all data in case of a successful submission with no redirect response
69
+ * - updates the `form` prop, `page.form` and `page.status` if the action is on the same page as the form
70
+ * - navigates to the page the submission lands on — populating that page's `form` prop and `page.status` on success and failure if that isn't the current page, just as a native form submission would, but with the `?/actionName` param stripped from the destination URL
70
71
  * - redirects in case of a redirect response
71
- * - redirects to the nearest error page in case of an unexpected error
72
+ * - renders the nearest error page in case of an unexpected error — the one nearest the action's route, if the action is on a different page
72
73
  *
73
74
  * If you provide a custom function with a callback and want to use the default behavior, invoke `update` in your callback.
74
75
  * It accepts an options object
75
76
  * - `reset: false` if you don't want the `<form>` values to be reset after a successful submission
76
- * - `invalidateAll: false` if you don't want the action to call `invalidateAll` after submission
77
+ * - `refreshAll` to control whether all data is refreshed after submission; it defaults to `true` for successes and `false` for failures
78
+ * - `navigate: false` to apply non-redirect results to the current page rather than navigating to `result.location`; redirects are always followed
77
79
  * @template {Record<string, unknown> | undefined} Success
78
80
  * @template {Record<string, unknown> | undefined} Failure
79
81
  * @param {HTMLFormElement} form_element The form element
@@ -86,37 +88,50 @@ export function enhance(form_element, submit = noop) {
86
88
 
87
89
  /**
88
90
  * @param {{
89
- * action: URL;
90
- * invalidateAll?: boolean;
91
91
  * result: import('@sveltejs/kit').ActionResult;
92
- * reset?: boolean
92
+ * reset?: boolean;
93
+ * refreshAll?: boolean;
94
+ * invalidateAll?: boolean;
95
+ * navigate?: boolean;
93
96
  * }} opts
94
97
  */
95
98
  const fallback_callback = async ({
96
- action,
97
99
  result,
98
100
  reset = true,
99
- invalidateAll: shouldInvalidateAll = true
101
+ refreshAll: should_refresh_all,
102
+ invalidateAll: deprecated_invalidate_all,
103
+ navigate = true
100
104
  }) => {
101
- if (result.type === 'success') {
102
- if (reset) {
103
- // We call reset from the prototype to avoid DOM clobbering
104
- HTMLFormElement.prototype.reset.call(form_element);
105
- }
106
- if (shouldInvalidateAll) {
105
+ if (DEV && deprecated_invalidate_all !== undefined) {
106
+ console.warn(
107
+ 'The `update({ invalidateAll })` option has been deprecated in favour of `update({ refreshAll })`'
108
+ );
109
+ }
110
+
111
+ should_refresh_all ??= deprecated_invalidate_all ?? result.type === 'success';
112
+
113
+ if (result.type === 'success' && reset) {
114
+ // We call reset from the prototype to avoid DOM clobbering
115
+ HTMLFormElement.prototype.reset.call(form_element);
116
+ }
117
+
118
+ const destination =
119
+ navigate && result.type !== 'redirect' && !is_current_location(result.location)
120
+ ? result.location
121
+ : undefined;
122
+
123
+ if (destination === undefined) {
124
+ if (should_refresh_all && result.type !== 'redirect') {
107
125
  await refreshAll();
108
126
  }
109
- }
110
127
 
111
- // For success/failure results, only apply action if it belongs to the
112
- // current page, otherwise `form` will be updated erroneously
113
- if (
114
- location.origin + location.pathname === action.origin + action.pathname ||
115
- result.type === 'redirect' ||
116
- result.type === 'error'
117
- ) {
118
128
  await applyAction(result);
129
+ return;
119
130
  }
131
+
132
+ // emulate the browser: navigate to where the submission lands, rendering that
133
+ // page with this result
134
+ await apply_action_navigation(destination, result, should_refresh_all);
120
135
  };
121
136
 
122
137
  /** @param {SubmitEvent} event */
@@ -202,13 +217,9 @@ export function enhance(form_element, submit = noop) {
202
217
  // detect new deployments from the response header
203
218
  notify_version(response.headers.get('x-sveltekit-version'));
204
219
 
205
- if (response.status === 204) {
206
- result = { type: 'success', status: 204 };
207
- } else {
208
- result = deserialize(await response.text());
209
- if (result.type === 'error' || result.type === 'failure') {
210
- result.status = response.status;
211
- }
220
+ result = deserialize(await response.text());
221
+ if (result.type === 'error' || result.type === 'failure') {
222
+ result.status = response.status;
212
223
  }
213
224
  } catch (error) {
214
225
  if (/** @type {any} */ (error)?.name === 'AbortError') return;
@@ -228,10 +239,11 @@ export function enhance(form_element, submit = noop) {
228
239
  formElement: form_element,
229
240
  update: (opts) =>
230
241
  fallback_callback({
231
- action,
232
242
  result,
233
243
  reset: opts?.reset,
234
- invalidateAll: opts?.invalidateAll
244
+ refreshAll: opts?.refreshAll,
245
+ invalidateAll: opts?.invalidateAll,
246
+ navigate: opts?.navigate
235
247
  }),
236
248
  // @ts-expect-error generic constraints stuff we don't care about
237
249
  result
@@ -12,3 +12,4 @@ export {
12
12
  pushState,
13
13
  replaceState
14
14
  } from '../client/client.js';
15
+ export { snapshot } from '../client/snapshots.js';