@sveltejs/kit 2.64.0 → 2.65.1

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 (35) hide show
  1. package/package.json +2 -2
  2. package/src/core/postbuild/analyse.js +0 -5
  3. package/src/core/postbuild/prerender.js +2 -0
  4. package/src/core/sync/write_client_manifest.js +5 -0
  5. package/src/exports/public.d.ts +1 -1
  6. package/src/exports/vite/build/build_server.js +43 -57
  7. package/src/exports/vite/build/build_service_worker.js +10 -0
  8. package/src/exports/vite/build/utils.js +0 -8
  9. package/src/exports/vite/index.js +237 -178
  10. package/src/runtime/app/server/remote/command.js +0 -3
  11. package/src/runtime/app/server/remote/form.js +18 -13
  12. package/src/runtime/app/server/remote/prerender.js +28 -34
  13. package/src/runtime/app/server/remote/query.js +105 -94
  14. package/src/runtime/app/server/remote/requested.js +14 -10
  15. package/src/runtime/app/server/remote/shared.js +25 -18
  16. package/src/runtime/client/client.js +25 -15
  17. package/src/runtime/client/remote-functions/command.svelte.js +5 -30
  18. package/src/runtime/client/remote-functions/form.svelte.js +62 -82
  19. package/src/runtime/client/remote-functions/prerender.svelte.js +14 -6
  20. package/src/runtime/client/remote-functions/query/index.js +6 -14
  21. package/src/runtime/client/remote-functions/query/instance.svelte.js +37 -8
  22. package/src/runtime/client/remote-functions/query/proxy.js +3 -3
  23. package/src/runtime/client/remote-functions/query-batch.svelte.js +59 -68
  24. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +20 -6
  25. package/src/runtime/client/remote-functions/shared.svelte.js +76 -59
  26. package/src/runtime/server/page/render.js +20 -83
  27. package/src/runtime/server/page/server_routing.js +20 -15
  28. package/src/runtime/server/remote.js +296 -204
  29. package/src/runtime/server/respond.js +4 -2
  30. package/src/runtime/shared.js +0 -15
  31. package/src/types/global-private.d.ts +3 -3
  32. package/src/types/internal.d.ts +53 -34
  33. package/src/version.js +1 -1
  34. package/types/index.d.ts +4 -4
  35. package/types/index.d.ts.map +1 -1
@@ -1,4 +1,4 @@
1
- /** @import { ServerNodesResponse, ServerRedirectNode } from 'types' */
1
+ /** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */
2
2
  /** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
3
3
  /** @import { Query } from './remote-functions/query/instance.svelte.js' */
4
4
  /** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
@@ -202,18 +202,20 @@ let target;
202
202
  export let app;
203
203
 
204
204
  /**
205
- * Data that was serialized during SSR for queries/forms/commands.
206
- * This is cleared before client-side loads run.
207
- * @type {Record<string, any>}
205
+ * Data that was serialized during SSR for queries/forms/commands, stored as
206
+ * `{ v }` (value) or `{ e }` (error) nodes so that failed states survive hydration.
207
+ * Entries are deleted as they are consumed (when the corresponding resource is created).
208
+ * @type {Record<string, RemoteFunctionDataNode>}
208
209
  */
209
- export let query_responses = {};
210
+ export const query_responses = {};
210
211
 
211
212
  /**
212
- * Data that was serialized during SSR for prerender functions.
213
+ * Data that was serialized during SSR for prerender functions, stored as
214
+ * `{ v }` (value) or `{ e }` (error) nodes.
213
215
  * This persists across client-side navigations.
214
- * @type {Record<string, any>}
216
+ * @type {Record<string, RemoteFunctionDataNode>}
215
217
  */
216
- export let prerender_responses = {};
218
+ export const prerender_responses = {};
217
219
 
218
220
  /** @type {Array<((url: URL) => boolean)>} */
219
221
  const invalidated = [];
@@ -328,9 +330,15 @@ export async function start(_app, _target, hydrate) {
328
330
  );
329
331
  }
330
332
 
331
- if (__SVELTEKIT_PAYLOAD__) {
332
- query_responses = __SVELTEKIT_PAYLOAD__.query ?? {};
333
- prerender_responses = __SVELTEKIT_PAYLOAD__.prerender ?? {};
333
+ if (__SVELTEKIT_PAYLOAD__.data) {
334
+ const { q = {}, p = {}, l = {}, f = {} } = __SVELTEKIT_PAYLOAD__.data;
335
+
336
+ // store the whole nodes — error records seed the corresponding
337
+ // resources in a failed state when they are created during hydration
338
+ for (const k in q) query_responses[k] = q[k];
339
+ for (const k in l) query_responses[k] = l[k];
340
+ for (const k in f) query_responses[k] = f[k];
341
+ for (const k in p) prerender_responses[k] = p[k];
334
342
  }
335
343
 
336
344
  // detect basic auth credentials in the current URL
@@ -546,7 +554,11 @@ export async function _goto(url, options, redirect_count, nav_token) {
546
554
  force_invalidation = true;
547
555
  query_keys = new Set();
548
556
  for (const [id, entries] of query_map) {
549
- for (const payload of entries.keys()) {
557
+ for (const [payload, entry] of entries) {
558
+ // don't refresh yet, as some queries will be unrendered,
559
+ // but clear caches so that newly rendered queries
560
+ // don't use stale data. TODO same for `live_query_map`
561
+ entry.resource?.reset();
550
562
  query_keys.add(create_remote_key(id, payload));
551
563
  }
552
564
  }
@@ -574,7 +586,7 @@ export async function _goto(url, options, redirect_count, nav_token) {
574
586
  for (const [id, entries] of query_map) {
575
587
  for (const [payload, { resource }] of entries) {
576
588
  if (query_keys?.has(create_remote_key(id, payload))) {
577
- void resource.refresh();
589
+ void resource.start();
578
590
  }
579
591
  }
580
592
  }
@@ -3028,8 +3040,6 @@ async function _hydrate(
3028
3040
 
3029
3041
  target.textContent = '';
3030
3042
  hydrate = false;
3031
- } finally {
3032
- query_responses = {};
3033
3043
  }
3034
3044
 
3035
3045
  if (result.props.page) {
@@ -1,16 +1,8 @@
1
1
  /** @import { RemoteCommand, RemoteQueryUpdate } from '@sveltejs/kit' */
2
- /** @import { RemoteFunctionResponse } from 'types' */
3
2
  import { app_dir, base } from '$app/paths/internal/client';
4
- import * as devalue from 'devalue';
5
- import { HttpError } from '@sveltejs/kit/internal';
6
3
  import { app } from '../client.js';
7
4
  import { stringify_command_arg } from '../../shared.js';
8
- import {
9
- get_remote_request_headers,
10
- apply_refreshes,
11
- categorize_updates,
12
- apply_reconnections
13
- } from './shared.svelte.js';
5
+ import { get_remote_request_headers, categorize_updates, remote_request } from './shared.svelte.js';
14
6
 
15
7
  /**
16
8
  * Client-version of the `command` function from `$app/server`.
@@ -53,7 +45,7 @@ export function command(id) {
53
45
  throw updates_error;
54
46
  }
55
47
 
56
- const response = await fetch(`${base}/${app_dir}/remote/${id}`, {
48
+ const response = await remote_request(`${base}/${app_dir}/remote/${id}`, {
57
49
  method: 'POST',
58
50
  body: JSON.stringify({
59
51
  payload: await stringify_command_arg(arg, app.hooks.transport),
@@ -62,30 +54,13 @@ export function command(id) {
62
54
  headers
63
55
  });
64
56
 
65
- if (!response.ok) {
66
- // We only end up here in case of a network error or if the server has an internal error
67
- // (which shouldn't happen because we handle errors on the server and always send a 200 response)
68
- throw new Error('Failed to execute remote function');
69
- }
70
-
71
- const result = /** @type {RemoteFunctionResponse} */ (await response.json());
72
- if (result.type === 'redirect') {
57
+ if (response.redirect) {
73
58
  throw new Error(
74
59
  'Redirects are not allowed in commands. Return a result instead and use goto on the client'
75
60
  );
76
- } else if (result.type === 'error') {
77
- throw new HttpError(result.status ?? 500, result.error);
78
- } else {
79
- if (result.refreshes) {
80
- apply_refreshes(result.refreshes);
81
- }
82
-
83
- if (result.reconnects) {
84
- apply_reconnections(result.reconnects);
85
- }
86
-
87
- return devalue.parse(result.result, app.decoders);
88
61
  }
62
+
63
+ return response._;
89
64
  } finally {
90
65
  overrides?.forEach((fn) => fn());
91
66
 
@@ -1,13 +1,12 @@
1
1
  /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
2
2
  /** @import { RemoteFormInput, RemoteForm, RemoteQueryUpdate } from '@sveltejs/kit' */
3
- /** @import { InternalRemoteFormIssue, RemoteFunctionResponse } from 'types' */
3
+ /** @import { InternalRemoteFormIssue } from 'types' */
4
4
  import { app_dir, base } from '$app/paths/internal/client';
5
- import * as devalue from 'devalue';
6
5
  import { DEV } from 'esm-env';
7
6
  import { HttpError } from '@sveltejs/kit/internal';
8
- import { app, query_responses, _goto, set_nearest_error_page, invalidateAll } from '../client.js';
7
+ import { query_responses, _goto, set_nearest_error_page, invalidateAll } from '../client.js';
9
8
  import { tick } from 'svelte';
10
- import { apply_refreshes, categorize_updates, apply_reconnections } from './shared.svelte.js';
9
+ import { categorize_updates, remote_request } from './shared.svelte.js';
11
10
  import { createAttachmentKey } from 'svelte/attachments';
12
11
  import {
13
12
  convert_formdata,
@@ -60,18 +59,25 @@ export function form(id) {
60
59
  const action_id = id + (key != undefined ? `/${JSON.stringify(key)}` : '');
61
60
  const action = '?/remote=' + encodeURIComponent(action_id);
62
61
 
62
+ // the output of a non-enhanced submission that resulted in this page —
63
+ // consume it so the form's state survives hydration (form outputs are
64
+ // always value nodes; the server never serializes them as errors)
65
+ /** @type {{ input?: Record<string, any>, issues?: InternalRemoteFormIssue[], result?: any } | undefined} */
66
+ const initial = query_responses[action_id]?.v;
67
+ delete query_responses[action_id];
68
+
63
69
  /**
64
70
  * @type {Record<string, string | string[] | File | File[]>}
65
71
  */
66
- let input = $state({});
72
+ let input = $state(initial?.input ?? {});
67
73
 
68
74
  /** @type {InternalRemoteFormIssue[]} */
69
- let raw_issues = $state.raw([]);
75
+ let raw_issues = $state.raw(initial?.issues ?? []);
70
76
 
71
77
  const issues = $derived(flatten_issues(raw_issues));
72
78
 
73
79
  /** @type {any} */
74
- let result = $state.raw(query_responses[action_id]);
80
+ let result = $state.raw(initial?.result);
75
81
 
76
82
  /** @type {number} */
77
83
  let pending_count = $state(0);
@@ -182,77 +188,54 @@ export function form(id) {
182
188
  remote_refreshes: Array.from(refreshes ?? [])
183
189
  });
184
190
 
185
- const response = await fetch(`${base}/${app_dir}/remote/${action_id_without_key}`, {
186
- method: 'POST',
187
- headers: {
188
- 'Content-Type': BINARY_FORM_CONTENT_TYPE,
189
- // Forms cannot be called during rendering, so it's save to use location here
190
- 'x-sveltekit-pathname': location.pathname,
191
- 'x-sveltekit-search': location.search
192
- },
193
- body: blob
194
- });
195
-
196
- if (!response.ok) {
197
- // We only end up here in case of a network error or if the server has an internal error
198
- // (which shouldn't happen because we handle errors on the server and always send a 200 response)
199
- throw new Error('Failed to execute remote function');
200
- }
201
-
202
- const form_result = /** @type { RemoteFunctionResponse} */ (await response.json());
203
-
204
- // reset issues in case it's a redirect or error (but issues passed in that case)
205
- raw_issues = [];
206
- result = undefined;
207
-
208
- if (form_result.type === 'result') {
209
- ({ issues: raw_issues = [], result } = devalue.parse(form_result.result, app.decoders));
210
- const succeeded = raw_issues.length === 0;
211
-
212
- if (succeeded) {
213
- if (refreshes === null && !form_result.refreshes && !form_result.reconnects) {
214
- void invalidateAll();
215
- } else {
216
- if (form_result.refreshes) {
217
- apply_refreshes(form_result.refreshes);
218
- }
219
- if (form_result.reconnects) {
220
- apply_reconnections(form_result.reconnects);
221
- }
222
- }
223
- } else {
224
- if (DEV) {
225
- warn_on_missing_issue_reads();
226
- }
191
+ const response = await remote_request(
192
+ `${base}/${app_dir}/remote/${action_id_without_key}`,
193
+ {
194
+ method: 'POST',
195
+ headers: {
196
+ 'Content-Type': BINARY_FORM_CONTENT_TYPE,
197
+ // Forms cannot be called during rendering, so it's save to use location here
198
+ 'x-sveltekit-pathname': location.pathname,
199
+ 'x-sveltekit-search': location.search
200
+ },
201
+ body: blob
227
202
  }
203
+ );
228
204
 
229
- return succeeded;
230
- } else if (form_result.type === 'redirect') {
231
- const stringified_refreshes = form_result.refreshes ?? '';
232
- const stringified_reconnects = form_result.reconnects ?? '';
233
- if (stringified_refreshes) {
234
- apply_refreshes(stringified_refreshes);
235
- }
205
+ ({ issues: raw_issues = [], result } = response._ ?? {});
236
206
 
237
- if (stringified_reconnects) {
238
- apply_reconnections(stringified_reconnects);
239
- }
207
+ // if the developer took control of updates via `.updates(...)` (even with
208
+ // no arguments), or the server performed explicit refreshes, don't invalidateAll
209
+ const should_invalidate = refreshes === null && !response.r;
240
210
 
211
+ if (response.redirect) {
241
212
  // Use internal version to allow redirects to external URLs
242
213
  void _goto(
243
- form_result.location,
214
+ response.redirect,
244
215
  {
245
- invalidateAll:
246
- refreshes === null && !stringified_refreshes && !stringified_reconnects
216
+ invalidateAll: should_invalidate
247
217
  },
248
218
  0
249
219
  );
250
220
  return true;
221
+ }
222
+
223
+ const succeeded = raw_issues.length === 0;
224
+
225
+ if (succeeded) {
226
+ if (should_invalidate) {
227
+ void invalidateAll();
228
+ }
251
229
  } else {
252
- throw new HttpError(form_result.status ?? 500, form_result.error);
230
+ if (DEV) {
231
+ warn_on_missing_issue_reads();
232
+ }
253
233
  }
234
+
235
+ return succeeded;
254
236
  } catch (e) {
255
237
  result = undefined;
238
+ raw_issues = [];
256
239
  throw e;
257
240
  } finally {
258
241
  overrides?.forEach((fn) => fn());
@@ -661,30 +644,27 @@ export function form(id) {
661
644
  if (validated?.issues) {
662
645
  array = validated.issues.map((issue) => normalize_issue(issue, false));
663
646
  } else if (!preflightOnly) {
664
- const response = await fetch(`${base}/${app_dir}/remote/${action_id_without_key}`, {
665
- method: 'POST',
666
- headers: {
667
- 'Content-Type': BINARY_FORM_CONTENT_TYPE,
668
- // Validation should not be and will not be called during rendering, so it's save to use location here
669
- 'x-sveltekit-pathname': location.pathname,
670
- 'x-sveltekit-search': location.search
671
- },
672
- body: serialize_binary_form(data, {
673
- validate_only: true
674
- }).blob
675
- });
676
-
677
- const result = await response.json();
647
+ const result = await remote_request(
648
+ `${base}/${app_dir}/remote/${action_id_without_key}`,
649
+ {
650
+ method: 'POST',
651
+ headers: {
652
+ 'Content-Type': BINARY_FORM_CONTENT_TYPE,
653
+ // Validation should not be and will not be called during rendering, so it's save to use location here
654
+ 'x-sveltekit-pathname': location.pathname,
655
+ 'x-sveltekit-search': location.search
656
+ },
657
+ body: serialize_binary_form(data, {
658
+ validate_only: true
659
+ }).blob
660
+ }
661
+ );
678
662
 
679
663
  if (validate_id !== id) {
680
664
  return;
681
665
  }
682
666
 
683
- if (result.type === 'result') {
684
- array = /** @type {InternalRemoteFormIssue[]} */ (
685
- devalue.parse(result.result, app.decoders)
686
- );
687
- }
667
+ array = /** @type {InternalRemoteFormIssue[]} */ (result._);
688
668
  }
689
669
 
690
670
  if (!includeUntouched && !submitted) {
@@ -2,8 +2,8 @@
2
2
  import { app_dir, base } from '$app/paths/internal/client';
3
3
  import { version } from '$app/env';
4
4
  import * as devalue from 'devalue';
5
- import { app, prerender_responses } from '../client.js';
6
- import { get_remote_request_headers, remote_request } from './shared.svelte.js';
5
+ import { app, goto, prerender_responses } from '../client.js';
6
+ import { get_remote_request_headers, remote_request, unwrap_node } from './shared.svelte.js';
7
7
  import { create_remote_key, stringify_remote_arg } from '../../shared.js';
8
8
 
9
9
  // Initialize Cache API for prerender functions
@@ -67,7 +67,7 @@ export function prerender(id) {
67
67
  const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ''}`;
68
68
 
69
69
  if (Object.hasOwn(prerender_responses, cache_key)) {
70
- const data = prerender_responses[cache_key];
70
+ const data = unwrap_node(prerender_responses[cache_key]);
71
71
 
72
72
  if (prerender_cache) {
73
73
  void put(url, devalue.stringify(data, app.encoders));
@@ -77,6 +77,7 @@ export function prerender(id) {
77
77
  }
78
78
 
79
79
  // Do this here, after await Svelte' reactivity context is gone.
80
+ // TODO we really don't want to be sending these specific headers here?
80
81
  const headers = get_remote_request_headers();
81
82
 
82
83
  // Check the Cache API first
@@ -93,14 +94,21 @@ export function prerender(id) {
93
94
  }
94
95
  }
95
96
 
96
- const encoded = await remote_request(url, headers);
97
+ const result = await remote_request(url, { headers });
98
+
99
+ if (result.redirect) {
100
+ void goto(result.redirect);
101
+ return;
102
+ }
103
+
104
+ const data = result._;
97
105
 
98
106
  // For successful prerender requests, save to cache
99
107
  if (prerender_cache) {
100
- void put(url, encoded);
108
+ void put(url, devalue.stringify(data, app.encoders));
101
109
  }
102
110
 
103
- return devalue.parse(encoded, app.decoders);
111
+ return data;
104
112
  });
105
113
 
106
114
  prerender_resources.set(cache_key, new WeakRef(resource));
@@ -1,10 +1,8 @@
1
1
  /** @import { RemoteQueryFunction } from '@sveltejs/kit' */
2
2
  import { app_dir, base } from '$app/paths/internal/client';
3
- import { app, query_map, query_responses } from '../../client.js';
3
+ import { goto, query_map } from '../../client.js';
4
4
  import { get_remote_request_headers, QUERY_FUNCTION_ID, remote_request } from '../shared.svelte.js';
5
- import * as devalue from 'devalue';
6
5
  import { DEV } from 'esm-env';
7
- import { unfriendly_hydratable } from '../../../shared.js';
8
6
  import { QueryProxy } from './proxy.js';
9
7
 
10
8
  /**
@@ -25,20 +23,14 @@ export function query(id) {
25
23
 
26
24
  /** @type {RemoteQueryFunction<any, any>} */
27
25
  const wrapper = (arg) => {
28
- return new QueryProxy(id, arg, async (key, payload) => {
29
- if (Object.hasOwn(query_responses, key)) {
30
- const value = query_responses[key];
31
- delete query_responses[key];
32
- return value;
33
- }
34
-
26
+ return new QueryProxy(id, arg, async (payload) => {
35
27
  const url = `${base}/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
36
28
 
37
- const serialized = await unfriendly_hydratable(key, () =>
38
- remote_request(url, get_remote_request_headers())
39
- );
29
+ const result = await remote_request(url, { headers: get_remote_request_headers() });
40
30
 
41
- return devalue.parse(serialized, app.decoders);
31
+ if (result.redirect) {
32
+ await goto(result.redirect);
33
+ }
42
34
  });
43
35
  };
44
36
 
@@ -1,4 +1,5 @@
1
1
  import { query_responses } from '../../client.js';
2
+ import { HttpError } from '@sveltejs/kit/internal';
2
3
  import { QUERY_OVERRIDE_KEY } from '../shared.svelte.js';
3
4
  import { noop } from '../../../../utils/functions.js';
4
5
  import { with_resolvers } from '../../../../utils/promise.js';
@@ -64,6 +65,17 @@ export class Query {
64
65
  constructor(key, fn) {
65
66
  this.#key = key;
66
67
  this.#fn = fn;
68
+
69
+ if (Object.hasOwn(query_responses, key)) {
70
+ const node = query_responses[key];
71
+ delete query_responses[key];
72
+
73
+ if (node.e) {
74
+ this.fail(new HttpError(node.e[0] ?? 500, node.e[1]));
75
+ } else {
76
+ this.set(/** @type {T} */ (node.v));
77
+ }
78
+ }
67
79
  }
68
80
 
69
81
  #get_promise() {
@@ -71,7 +83,7 @@ export class Query {
71
83
  return /** @type {Promise<T>} */ (this.#promise);
72
84
  }
73
85
 
74
- #start() {
86
+ start() {
75
87
  // there is a really weird bug with untrack and writes and initializations
76
88
  // every time you see this comment, try removing the `tick.then` here and see
77
89
  // if all the tests still pass with the latest svelte version
@@ -132,12 +144,12 @@ export class Query {
132
144
  get then() {
133
145
  // TODO this should be unnecessary but due to the bug described
134
146
  // in #start, we need to do this in some circumstances
135
- this.#start();
147
+ this.start();
136
148
  return this.#then;
137
149
  }
138
150
 
139
151
  get catch() {
140
- this.#start();
152
+ this.start();
141
153
  this.#then;
142
154
  return (/** @type {any} */ reject) => {
143
155
  return this.#then(undefined, reject);
@@ -145,7 +157,7 @@ export class Query {
145
157
  }
146
158
 
147
159
  get finally() {
148
- this.#start();
160
+ this.start();
149
161
  this.#then;
150
162
  return (/** @type {any} */ fn) => {
151
163
  return this.#then(
@@ -162,12 +174,12 @@ export class Query {
162
174
  }
163
175
 
164
176
  get current() {
165
- this.#start();
177
+ this.start();
166
178
  return this.#current;
167
179
  }
168
180
 
169
181
  get error() {
170
- this.#start();
182
+ this.start();
171
183
  return this.#error;
172
184
  }
173
185
 
@@ -175,7 +187,7 @@ export class Query {
175
187
  * Returns true if the resource is loading or reloading.
176
188
  */
177
189
  get loading() {
178
- this.#start();
190
+ this.start();
179
191
  return this.#loading;
180
192
  }
181
193
 
@@ -183,7 +195,7 @@ export class Query {
183
195
  * Returns true once the resource has been loaded for the first time.
184
196
  */
185
197
  get ready() {
186
- this.#start();
198
+ this.start();
187
199
  return this.#ready;
188
200
  }
189
201
 
@@ -199,6 +211,10 @@ export class Query {
199
211
  * @param {T} value
200
212
  */
201
213
  set(value) {
214
+ // normally consumed in the constructor, but make sure a leftover
215
+ // SSR record can never shadow the newly-set value
216
+ delete query_responses[this.#key];
217
+
202
218
  this.#clear_pending();
203
219
  this.#ready = true;
204
220
  this.#loading = false;
@@ -211,6 +227,10 @@ export class Query {
211
227
  * @param {unknown} error
212
228
  */
213
229
  fail(error) {
230
+ // normally consumed in the constructor, but make sure a leftover
231
+ // SSR record can never shadow the newly-set error
232
+ delete query_responses[this.#key];
233
+
214
234
  this.#clear_pending();
215
235
  this.#loading = false;
216
236
  this.#error = error;
@@ -243,6 +263,15 @@ export class Query {
243
263
  return release;
244
264
  }
245
265
 
266
+ /**
267
+ * Reset ahead of a navigation that invalidates all, to force newly
268
+ * rendered queries to get fresh data
269
+ */
270
+ reset() {
271
+ this.#promise = null;
272
+ delete query_responses[this.#key];
273
+ }
274
+
246
275
  get [Symbol.toStringTag]() {
247
276
  return 'Query';
248
277
  }
@@ -25,7 +25,7 @@ export class QueryProxy {
25
25
  /**
26
26
  * @param {string} id
27
27
  * @param {any} arg
28
- * @param {(key: string, payload: string) => Promise<T>} fn
28
+ * @param {(payload: string) => Promise<T>} fn
29
29
  */
30
30
  constructor(id, arg, fn) {
31
31
  this.#id = id;
@@ -41,7 +41,7 @@ export class QueryProxy {
41
41
  this.#payload,
42
42
  // IMPORTANT: This cannot close over `this` or it becomes impossible to
43
43
  // garbage collect the QueryProxy and thus impossible to evict cache entries.
44
- () => new Query(key, () => fn(key, payload))
44
+ () => new Query(key, () => fn(payload))
45
45
  );
46
46
 
47
47
  cache.ref(this, entry, this.#id, this.#payload);
@@ -98,7 +98,7 @@ export class QueryProxy {
98
98
  this.#payload,
99
99
  // IMPORTANT: This cannot close over `this` or it becomes impossible to
100
100
  // garbage collect the QueryProxy and thus impossible to evict cache entries.
101
- () => new Query(key_ref, () => fn_ref(key_ref, payload_ref))
101
+ () => new Query(key_ref, () => fn_ref(payload_ref))
102
102
  );
103
103
 
104
104
  const deref = cache.manual_ref(entry, this.#id, this.#payload);