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

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.17",
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
+ }
@@ -1941,15 +1941,20 @@ export type Actions<
1941
1941
  * };
1942
1942
  * }}
1943
1943
  * ```
1944
+ *
1945
+ * Success and failure results carry the root-relative `pathname + search` of the action URL, with
1946
+ * the `?/actionName` parameter removed. Redirect results carry the redirect target. Server-generated
1947
+ * error results also carry the action location, while client-generated errors such as network
1948
+ * failures do not. `update` uses this location to emulate native form navigation.
1944
1949
  */
1945
1950
  export type ActionResult<
1946
1951
  Success extends Record<string, unknown> | undefined = Record<string, any>,
1947
1952
  Failure extends Record<string, unknown> | undefined = Record<string, any>
1948
1953
  > =
1949
- | { type: 'success'; status: number; data?: Success }
1950
- | { type: 'failure'; status: number; data?: Failure }
1954
+ | { type: 'success'; status: number; data?: Success; location: string }
1955
+ | { type: 'failure'; status: number; data?: Failure; location: string }
1951
1956
  | { type: 'redirect'; status: number; location: string }
1952
- | { type: 'error'; status?: number; error: App.Error };
1957
+ | { type: 'error'; status?: number; error: App.Error; location?: string };
1953
1958
 
1954
1959
  /**
1955
1960
  * The object returned by the [`error`](https://svelte.dev/docs/kit/@sveltejs-kit#error) function.
@@ -1990,15 +1995,21 @@ export type SubmitFunction<
1990
1995
  result: ActionResult<Success, Failure>;
1991
1996
  /**
1992
1997
  * 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.
1998
+ * @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
1999
  */
1996
- update: (options?: { reset?: boolean; invalidateAll?: boolean }) => Promise<void>;
2000
+ update: (options?: {
2001
+ reset?: boolean;
2002
+ refreshAll?: boolean;
2003
+ navigate?: boolean;
2004
+ /** @deprecated Use `refreshAll` instead. */
2005
+ invalidateAll?: boolean;
2006
+ }) => Promise<void>;
1997
2007
  }) => MaybePromise<void>)
1998
2008
  >;
1999
2009
 
2000
2010
  /**
2001
2011
  * The type of `export const snapshot` exported from a page or layout component.
2012
+ * @deprecated Use the [`snapshot`](https://svelte.dev/docs/kit/$app-navigation#snapshot) helper from `$app/navigation` instead.
2002
2013
  */
2003
2014
  export interface Snapshot<T = any> {
2004
2015
  capture: () => T;
@@ -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';