@sveltejs/kit 2.26.0 → 2.27.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 (56) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -2
  3. package/src/core/adapt/builder.js +6 -1
  4. package/src/core/config/options.js +4 -0
  5. package/src/core/generate_manifest/index.js +3 -0
  6. package/src/core/postbuild/analyse.js +25 -1
  7. package/src/core/postbuild/fallback.js +2 -1
  8. package/src/core/postbuild/prerender.js +41 -10
  9. package/src/core/sync/create_manifest_data/index.js +35 -1
  10. package/src/core/sync/write_server.js +4 -2
  11. package/src/core/sync/write_types/index.js +12 -5
  12. package/src/exports/index.js +1 -1
  13. package/src/exports/internal/index.js +3 -1
  14. package/src/exports/internal/remote-functions.js +21 -0
  15. package/src/exports/public.d.ts +162 -2
  16. package/src/exports/vite/build/build_remote.js +129 -0
  17. package/src/exports/vite/dev/index.js +7 -0
  18. package/src/exports/vite/index.js +123 -8
  19. package/src/exports/vite/module_ids.js +3 -2
  20. package/src/exports/vite/preview/index.js +3 -1
  21. package/src/runtime/app/navigation.js +1 -0
  22. package/src/runtime/app/server/index.js +2 -0
  23. package/src/runtime/app/server/remote/command.js +91 -0
  24. package/src/runtime/app/server/remote/form.js +124 -0
  25. package/src/runtime/app/server/remote/index.js +4 -0
  26. package/src/runtime/app/server/remote/prerender.js +163 -0
  27. package/src/runtime/app/server/remote/query.js +115 -0
  28. package/src/runtime/app/server/remote/shared.js +153 -0
  29. package/src/runtime/client/client.js +107 -39
  30. package/src/runtime/client/fetcher.js +1 -1
  31. package/src/runtime/client/remote-functions/command.js +71 -0
  32. package/src/runtime/client/remote-functions/form.svelte.js +312 -0
  33. package/src/runtime/client/remote-functions/index.js +4 -0
  34. package/src/runtime/client/remote-functions/prerender.svelte.js +166 -0
  35. package/src/runtime/client/remote-functions/query.svelte.js +219 -0
  36. package/src/runtime/client/remote-functions/shared.svelte.js +143 -0
  37. package/src/runtime/client/types.d.ts +2 -0
  38. package/src/runtime/server/data/index.js +6 -4
  39. package/src/runtime/server/event-state.js +41 -0
  40. package/src/runtime/server/index.js +12 -3
  41. package/src/runtime/server/page/actions.js +1 -1
  42. package/src/runtime/server/page/index.js +10 -3
  43. package/src/runtime/server/page/load_data.js +18 -12
  44. package/src/runtime/server/page/render.js +31 -5
  45. package/src/runtime/server/page/serialize_data.js +1 -1
  46. package/src/runtime/server/remote.js +237 -0
  47. package/src/runtime/server/respond.js +57 -36
  48. package/src/runtime/shared.js +61 -0
  49. package/src/types/global-private.d.ts +2 -0
  50. package/src/types/internal.d.ts +51 -4
  51. package/src/types/synthetic/$env+static+private.md +1 -1
  52. package/src/utils/routing.js +1 -1
  53. package/src/version.js +1 -1
  54. package/types/index.d.ts +266 -3
  55. package/types/index.d.ts.map +14 -1
  56. /package/src/{runtime → utils}/hash.js +0 -0
@@ -0,0 +1,219 @@
1
+ /** @import { RemoteQueryFunction } from '@sveltejs/kit' */
2
+ import { app_dir } from '__sveltekit/paths';
3
+ import { remote_responses, started } from '../client.js';
4
+ import { tick } from 'svelte';
5
+ import { create_remote_function, remote_request } from './shared.svelte.js';
6
+
7
+ /**
8
+ * @param {string} id
9
+ * @returns {RemoteQueryFunction<any, any>}
10
+ */
11
+ export function query(id) {
12
+ return create_remote_function(id, (cache_key, payload) => {
13
+ return new Query(cache_key, async () => {
14
+ if (!started) {
15
+ const result = remote_responses[cache_key];
16
+ if (result) {
17
+ return result;
18
+ }
19
+ }
20
+
21
+ const url = `/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
22
+
23
+ return await remote_request(url);
24
+ });
25
+ });
26
+ }
27
+
28
+ /**
29
+ * @template T
30
+ * @implements {Partial<Promise<T>>}
31
+ */
32
+ export class Query {
33
+ /** @type {string} */
34
+ _key;
35
+
36
+ #init = false;
37
+ /** @type {() => Promise<T>} */
38
+ #fn;
39
+ #loading = $state(true);
40
+ /** @type {Array<() => void>} */
41
+ #latest = [];
42
+
43
+ /** @type {boolean} */
44
+ #ready = $state(false);
45
+ /** @type {T | undefined} */
46
+ #raw = $state.raw();
47
+ /** @type {Promise<void>} */
48
+ #promise;
49
+ /** @type {Array<(old: T) => T>} */
50
+ #overrides = $state([]);
51
+
52
+ /** @type {T | undefined} */
53
+ #current = $derived.by(() => {
54
+ // don't reduce undefined value
55
+ if (!this.#ready) return undefined;
56
+
57
+ return this.#overrides.reduce((v, r) => r(v), /** @type {T} */ (this.#raw));
58
+ });
59
+
60
+ #error = $state.raw(undefined);
61
+
62
+ /** @type {Promise<T>['then']} */
63
+ // @ts-expect-error TS doesn't understand that the promise returns something
64
+ #then = $derived.by(() => {
65
+ const p = this.#promise;
66
+ this.#overrides.length;
67
+
68
+ return async (resolve, reject) => {
69
+ try {
70
+ await p;
71
+ // svelte-ignore await_reactivity_loss
72
+ await tick();
73
+ resolve?.(/** @type {T} */ (this.#current));
74
+ } catch (error) {
75
+ reject?.(error);
76
+ }
77
+ };
78
+ });
79
+
80
+ /**
81
+ * @param {string} key
82
+ * @param {() => Promise<T>} fn
83
+ */
84
+ constructor(key, fn) {
85
+ this._key = key;
86
+ this.#fn = fn;
87
+ this.#promise = $state.raw(this.#run());
88
+ }
89
+
90
+ #run() {
91
+ // Prevent state_unsafe_mutation error on first run when the resource is created within the template
92
+ if (this.#init) {
93
+ this.#loading = true;
94
+ } else {
95
+ this.#init = true;
96
+ }
97
+
98
+ // Don't use Promise.withResolvers, it's too new still
99
+ /** @type {() => void} */
100
+ let resolve;
101
+ /** @type {(e?: any) => void} */
102
+ let reject;
103
+ /** @type {Promise<void>} */
104
+ const promise = new Promise((res, rej) => {
105
+ resolve = res;
106
+ reject = rej;
107
+ });
108
+
109
+ this.#latest.push(
110
+ // @ts-expect-error it's defined at this point
111
+ resolve
112
+ );
113
+
114
+ Promise.resolve(this.#fn())
115
+ .then((value) => {
116
+ // Skip the response if resource was refreshed with a later promise while we were waiting for this one to resolve
117
+ const idx = this.#latest.indexOf(resolve);
118
+ if (idx === -1) return;
119
+
120
+ this.#latest.splice(0, idx).forEach((r) => r());
121
+ this.#ready = true;
122
+ this.#loading = false;
123
+ this.#raw = value;
124
+ this.#error = undefined;
125
+
126
+ resolve();
127
+ })
128
+ .catch((e) => {
129
+ const idx = this.#latest.indexOf(resolve);
130
+ if (idx === -1) return;
131
+
132
+ this.#latest.splice(0, idx).forEach((r) => r());
133
+ this.#error = e;
134
+ this.#loading = false;
135
+ reject(e);
136
+ });
137
+
138
+ return promise;
139
+ }
140
+
141
+ get then() {
142
+ return this.#then;
143
+ }
144
+
145
+ get catch() {
146
+ this.#then;
147
+ return (/** @type {any} */ reject) => {
148
+ return this.#then(undefined, reject);
149
+ };
150
+ }
151
+
152
+ get finally() {
153
+ this.#then;
154
+ return (/** @type {any} */ fn) => {
155
+ return this.#then(
156
+ () => fn(),
157
+ () => fn()
158
+ );
159
+ };
160
+ }
161
+
162
+ get current() {
163
+ return this.#current;
164
+ }
165
+
166
+ get error() {
167
+ return this.#error;
168
+ }
169
+
170
+ /**
171
+ * Returns true if the resource is loading or reloading.
172
+ */
173
+ get loading() {
174
+ return this.#loading;
175
+ }
176
+
177
+ /**
178
+ * Returns true once the resource has been loaded for the first time.
179
+ */
180
+ get ready() {
181
+ return this.#ready;
182
+ }
183
+
184
+ /**
185
+ * @returns {Promise<void>}
186
+ */
187
+ refresh() {
188
+ return (this.#promise = this.#run());
189
+ }
190
+
191
+ /**
192
+ * @param {T} value
193
+ */
194
+ set(value) {
195
+ this.#ready = true;
196
+ this.#loading = false;
197
+ this.#error = undefined;
198
+ this.#raw = value;
199
+ this.#promise = Promise.resolve();
200
+ }
201
+
202
+ /**
203
+ * @param {(old: T) => T} fn
204
+ */
205
+ withOverride(fn) {
206
+ this.#overrides.push(fn);
207
+
208
+ return {
209
+ _key: this._key,
210
+ release: () => {
211
+ const i = this.#overrides.indexOf(fn);
212
+
213
+ if (i !== -1) {
214
+ this.#overrides.splice(i, 1);
215
+ }
216
+ }
217
+ };
218
+ }
219
+ }
@@ -0,0 +1,143 @@
1
+ /** @import { RemoteQueryOverride } from '@sveltejs/kit' */
2
+ /** @import { RemoteFunctionResponse } from 'types' */
3
+ /** @import { Query } from './query.svelte.js' */
4
+ import * as devalue from 'devalue';
5
+ import { app, goto, invalidateAll, query_map } from '../client.js';
6
+ import { HttpError, Redirect } from '@sveltejs/kit/internal';
7
+ import { tick } from 'svelte';
8
+ import { create_remote_cache_key, stringify_remote_arg } from '../../shared.js';
9
+
10
+ /**
11
+ *
12
+ * @param {string} url
13
+ */
14
+ export async function remote_request(url) {
15
+ const response = await fetch(url);
16
+
17
+ if (!response.ok) {
18
+ throw new HttpError(500, 'Failed to execute remote function');
19
+ }
20
+
21
+ const result = /** @type {RemoteFunctionResponse} */ (await response.json());
22
+
23
+ if (result.type === 'redirect') {
24
+ // resource_cache.delete(cache_key);
25
+ // version++;
26
+ // await goto(result.location);
27
+ // /** @type {Query<any>} */ (resource).refresh();
28
+ // TODO double-check this
29
+ await goto(result.location);
30
+ await new Promise((r) => setTimeout(r, 100));
31
+ throw new Redirect(307, result.location);
32
+ }
33
+
34
+ if (result.type === 'error') {
35
+ throw new HttpError(result.status ?? 500, result.error);
36
+ }
37
+
38
+ return devalue.parse(result.result, app.decoders);
39
+ }
40
+
41
+ /**
42
+ * Client-version of the `query`/`prerender`/`cache` function from `$app/server`.
43
+ * @param {string} id
44
+ * @param {(key: string, args: string) => any} create
45
+ */
46
+ export function create_remote_function(id, create) {
47
+ return (/** @type {any} */ arg) => {
48
+ const payload = stringify_remote_arg(arg, app.hooks.transport);
49
+ const cache_key = create_remote_cache_key(id, payload);
50
+ let entry = query_map.get(cache_key);
51
+
52
+ let tracking = true;
53
+ try {
54
+ $effect.pre(() => {
55
+ if (entry) entry.count++;
56
+ return () => {
57
+ const entry = query_map.get(cache_key);
58
+ if (entry) {
59
+ entry.count--;
60
+ void tick().then(() => {
61
+ if (!entry.count && entry === query_map.get(cache_key)) {
62
+ query_map.delete(cache_key);
63
+ }
64
+ });
65
+ }
66
+ };
67
+ });
68
+ } catch {
69
+ tracking = false;
70
+ }
71
+
72
+ let resource = entry?.resource;
73
+ if (!resource) {
74
+ resource = create(cache_key, payload);
75
+
76
+ Object.defineProperty(resource, '_key', {
77
+ value: cache_key
78
+ });
79
+
80
+ query_map.set(
81
+ cache_key,
82
+ (entry = {
83
+ count: tracking ? 1 : 0,
84
+ resource
85
+ })
86
+ );
87
+
88
+ resource
89
+ .then(() => {
90
+ void tick().then(() => {
91
+ if (
92
+ !(/** @type {NonNullable<typeof entry>} */ (entry).count) &&
93
+ entry === query_map.get(cache_key)
94
+ ) {
95
+ // If no one is tracking this resource anymore, we can delete it from the cache
96
+ query_map.delete(cache_key);
97
+ }
98
+ });
99
+ })
100
+ .catch(() => {
101
+ // error delete the resource from the cache
102
+ // TODO is that correct?
103
+ query_map.delete(cache_key);
104
+ });
105
+ }
106
+
107
+ return resource;
108
+ };
109
+ }
110
+
111
+ /**
112
+ * @param {Array<Query<any> | RemoteQueryOverride>} updates
113
+ */
114
+ export function release_overrides(updates) {
115
+ for (const update of updates) {
116
+ if ('release' in update) {
117
+ update.release();
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * @param {string} stringified_refreshes
124
+ * @param {Array<Query<any> | RemoteQueryOverride>} updates
125
+ */
126
+ export function refresh_queries(stringified_refreshes, updates = []) {
127
+ const refreshes = Object.entries(devalue.parse(stringified_refreshes, app.decoders));
128
+ if (refreshes.length > 0) {
129
+ // `refreshes` is a superset of `updates`
130
+ for (const [key, value] of refreshes) {
131
+ // If there was an optimistic update, release it right before we update the query
132
+ const update = updates.find((u) => u._key === key);
133
+ if (update && 'release' in update) {
134
+ update.release();
135
+ }
136
+ // Update the query with the new value
137
+ const entry = query_map.get(key);
138
+ entry?.resource.set(value);
139
+ }
140
+ } else {
141
+ void invalidateAll();
142
+ }
143
+ }
@@ -125,4 +125,6 @@ export interface HydrateOptions {
125
125
  server_route?: CSRRouteServer;
126
126
  data: Array<ServerDataNode | null>;
127
127
  form: Record<string, any> | null;
128
+ /** The results of all remote functions executed during SSR so that they can be reused during hydration */
129
+ remote: Record<string, any>;
128
130
  }
@@ -176,10 +176,12 @@ function json_response(json, status = 200) {
176
176
  * @param {Redirect} redirect
177
177
  */
178
178
  export function redirect_json_response(redirect) {
179
- return json_response({
180
- type: 'redirect',
181
- location: redirect.location
182
- });
179
+ return json_response(
180
+ /** @type {import('types').ServerRedirectNode} */ ({
181
+ type: 'redirect',
182
+ location: redirect.location
183
+ })
184
+ );
183
185
  }
184
186
 
185
187
  /**
@@ -0,0 +1,41 @@
1
+ /** @import { RequestEvent } from '@sveltejs/kit' */
2
+ /** @import { PrerenderOptions, ServerHooks, SSROptions, SSRState } from 'types' */
3
+
4
+ export const EVENT_STATE = Symbol('remote');
5
+
6
+ /**
7
+ * Internal state associated with the current `RequestEvent`,
8
+ * used for tracking things like remote function calls
9
+ * @typedef {{
10
+ * prerendering: PrerenderOptions | undefined
11
+ * transport: ServerHooks['transport'];
12
+ * handleValidationError: ServerHooks['handleValidationError'];
13
+ * form_instances?: Map<any, any>;
14
+ * form_result?: [key: any, value: any];
15
+ * remote_data?: Record<string, Promise<any>>;
16
+ * refreshes?: Record<string, any>;
17
+ * }} RequestEventState
18
+ */
19
+
20
+ /**
21
+ * @param {SSRState} state
22
+ * @param {SSROptions} options
23
+ * @returns {RequestEventState}
24
+ */
25
+ export function create_event_state(state, options) {
26
+ return {
27
+ prerendering: state.prerendering,
28
+ transport: options.hooks.transport,
29
+ handleValidationError: options.hooks.handleValidationError
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Returns internal state associated with the current `RequestEvent`
35
+ * @param {RequestEvent} event
36
+ * @returns {RequestEventState}
37
+ */
38
+ export function get_event_state(event) {
39
+ // @ts-expect-error the symbol isn't exposed on the public `RequestEvent` type
40
+ return event[EVENT_STATE];
41
+ }
@@ -111,6 +111,12 @@ export class Server {
111
111
  (({ status, error }) =>
112
112
  console.error((status === 404 && /** @type {Error} */ (error)?.message) || error)),
113
113
  handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)),
114
+ handleValidationError:
115
+ module.handleValidationError ||
116
+ (({ issues }) => {
117
+ console.error('Remote function schema validation failed:', issues);
118
+ return { message: 'Bad Request' };
119
+ }),
114
120
  reroute: module.reroute || (() => {}),
115
121
  transport: module.transport || {}
116
122
  };
@@ -124,14 +130,17 @@ export class Server {
124
130
  if (module.init) {
125
131
  await module.init();
126
132
  }
127
- } catch (error) {
133
+ } catch (e) {
128
134
  if (DEV) {
129
135
  this.#options.hooks = {
130
136
  handle: () => {
131
- throw error;
137
+ throw e;
132
138
  },
133
139
  handleError: ({ error }) => console.error(error),
134
140
  handleFetch: ({ request, fetch }) => fetch(request),
141
+ handleValidationError: () => {
142
+ return { message: 'Bad Request' };
143
+ },
135
144
  reroute: () => {},
136
145
  transport: {}
137
146
  };
@@ -140,7 +149,7 @@ export class Server {
140
149
  decoders: {}
141
150
  });
142
151
  } else {
143
- throw error;
152
+ throw e;
144
153
  }
145
154
  }
146
155
  })());
@@ -104,7 +104,7 @@ export async function handle_action_json_request(event, options, server) {
104
104
  /**
105
105
  * @param {HttpError | Error} error
106
106
  */
107
- function check_incorrect_fail_use(error) {
107
+ export function check_incorrect_fail_use(error) {
108
108
  return error instanceof ActionFailure
109
109
  ? new Error('Cannot "throw fail()". Use "return fail()"')
110
110
  : error;
@@ -15,6 +15,7 @@ import { render_response } from './render.js';
15
15
  import { respond_with_error } from './respond_with_error.js';
16
16
  import { get_data_json } from '../data/index.js';
17
17
  import { DEV } from 'esm-env';
18
+ import { get_remote_action, handle_remote_form_post } from '../remote.js';
18
19
  import { PageNodes } from '../../../utils/page_nodes.js';
19
20
 
20
21
  /**
@@ -54,9 +55,15 @@ export async function render_page(event, page, options, manifest, state, nodes,
54
55
  let action_result = undefined;
55
56
 
56
57
  if (is_action_request(event)) {
57
- // for action requests, first call handler in +page.server.js
58
- // (this also determines status code)
59
- action_result = await handle_action_request(event, leaf_node.server);
58
+ const remote_id = get_remote_action(event.url);
59
+ if (remote_id) {
60
+ action_result = await handle_remote_form_post(event, manifest, remote_id);
61
+ } else {
62
+ // for action requests, first call handler in +page.server.js
63
+ // (this also determines status code)
64
+ action_result = await handle_action_request(event, leaf_node.server);
65
+ }
66
+
60
67
  if (action_result?.type === 'redirect') {
61
68
  return redirect_response(action_result.status, action_result.location);
62
69
  }
@@ -197,21 +197,27 @@ export async function load_data({
197
197
  }) {
198
198
  const server_data_node = await server_data_promise;
199
199
 
200
- if (!node?.universal?.load) {
200
+ const load = node?.universal?.load;
201
+
202
+ if (!load) {
201
203
  return server_data_node?.data ?? null;
202
204
  }
203
205
 
204
- const result = await node.universal.load.call(null, {
205
- url: event.url,
206
- params: event.params,
207
- data: server_data_node?.data ?? null,
208
- route: event.route,
209
- fetch: create_universal_fetch(event, state, fetched, csr, resolve_opts),
210
- setHeaders: event.setHeaders,
211
- depends: () => {},
212
- parent,
213
- untrack: (fn) => fn()
214
- });
206
+ // We're adding getRequestEvent context to the universal load function
207
+ // in order to be able to use remote calls within it.
208
+ const result = await with_event(event, () =>
209
+ load.call(null, {
210
+ url: event.url,
211
+ params: event.params,
212
+ data: server_data_node?.data ?? null,
213
+ route: event.route,
214
+ fetch: create_universal_fetch(event, state, fetched, csr, resolve_opts),
215
+ setHeaders: event.setHeaders,
216
+ depends: () => {},
217
+ parent,
218
+ untrack: (fn) => fn()
219
+ })
220
+ );
215
221
 
216
222
  if (__SVELTEKIT_DEV__) {
217
223
  validate_load_response(result, node.universal_id);
@@ -3,7 +3,7 @@ import { readable, writable } from 'svelte/store';
3
3
  import { DEV } from 'esm-env';
4
4
  import { text } from '@sveltejs/kit';
5
5
  import * as paths from '__sveltekit/paths';
6
- import { hash } from '../../hash.js';
6
+ import { hash } from '../../../utils/hash.js';
7
7
  import { serialize_data } from './serialize_data.js';
8
8
  import { s } from '../../../utils/misc.js';
9
9
  import { Csp } from './csp.js';
@@ -15,6 +15,8 @@ import { SVELTE_KIT_ASSETS } from '../../../constants.js';
15
15
  import { SCHEME } from '../../../utils/url.js';
16
16
  import { create_server_routing_response, generate_route_object } from './server_routing.js';
17
17
  import { add_resolution_suffix } from '../../pathname.js';
18
+ import { with_event } from '../../app/server/event.js';
19
+ import { get_event_state } from '../event-state.js';
18
20
 
19
21
  // TODO rename this function/module
20
22
 
@@ -189,14 +191,14 @@ export async function render_response({
189
191
  };
190
192
 
191
193
  try {
192
- rendered = options.root.render(props, render_opts);
194
+ rendered = with_event(event, () => options.root.render(props, render_opts));
193
195
  } finally {
194
196
  globalThis.fetch = fetch;
195
197
  paths.reset();
196
198
  }
197
199
  } else {
198
200
  try {
199
- rendered = options.root.render(props, render_opts);
201
+ rendered = with_event(event, () => options.root.render(props, render_opts));
200
202
  } finally {
201
203
  paths.reset();
202
204
  }
@@ -386,7 +388,7 @@ export async function render_response({
386
388
  blocks.push('const element = document.currentScript.parentElement;');
387
389
 
388
390
  if (page_config.ssr) {
389
- const serialized = { form: 'null', error: 'null' };
391
+ const serialized = { form: 'null', error: 'null', remote: 'null' };
390
392
 
391
393
  if (form_value) {
392
394
  serialized.form = uneval_action_response(
@@ -400,11 +402,35 @@ export async function render_response({
400
402
  serialized.error = devalue.uneval(error);
401
403
  }
402
404
 
405
+ const { remote_data } = get_event_state(event);
406
+
407
+ if (remote_data) {
408
+ /** @type {Record<string, any>} */
409
+ const remote = {};
410
+
411
+ for (const key in remote_data) {
412
+ remote[key] = await remote_data[key];
413
+ }
414
+
415
+ // TODO this is repeated in a few places — dedupe it
416
+ const replacer = (/** @type {any} */ thing) => {
417
+ for (const key in options.hooks.transport) {
418
+ const encoded = options.hooks.transport[key].encode(thing);
419
+ if (encoded) {
420
+ return `app.decode('${key}', ${devalue.uneval(encoded, replacer)})`;
421
+ }
422
+ }
423
+ };
424
+
425
+ serialized.remote = devalue.uneval(remote, replacer);
426
+ }
427
+
403
428
  const hydrate = [
404
429
  `node_ids: [${branch.map(({ node }) => node.index).join(', ')}]`,
405
430
  `data: ${data}`,
406
431
  `form: ${serialized.form}`,
407
- `error: ${serialized.error}`
432
+ `error: ${serialized.error}`,
433
+ `remote: ${serialized.remote}`
408
434
  ];
409
435
 
410
436
  if (status !== 200) {
@@ -1,5 +1,5 @@
1
1
  import { escape_html } from '../../../utils/escape.js';
2
- import { hash } from '../../hash.js';
2
+ import { hash } from '../../../utils/hash.js';
3
3
 
4
4
  /**
5
5
  * Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.