@sveltejs/kit 2.54.0 → 2.56.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 (45) hide show
  1. package/package.json +3 -4
  2. package/src/core/postbuild/analyse.js +3 -3
  3. package/src/core/postbuild/prerender.js +9 -6
  4. package/src/core/sync/write_non_ambient.js +143 -37
  5. package/src/core/sync/write_tsconfig.js +3 -1
  6. package/src/core/sync/write_types/index.js +1 -5
  7. package/src/exports/internal/remote-functions.js +2 -2
  8. package/src/exports/public.d.ts +38 -12
  9. package/src/exports/vite/build/build_server.js +24 -4
  10. package/src/exports/vite/build/build_service_worker.js +16 -6
  11. package/src/exports/vite/build/utils.js +18 -3
  12. package/src/exports/vite/index.js +336 -327
  13. package/src/runtime/app/paths/server.js +1 -1
  14. package/src/runtime/app/server/index.js +1 -1
  15. package/src/runtime/app/server/remote/command.js +12 -7
  16. package/src/runtime/app/server/remote/form.js +14 -14
  17. package/src/runtime/app/server/remote/index.js +1 -0
  18. package/src/runtime/app/server/remote/prerender.js +8 -7
  19. package/src/runtime/app/server/remote/query.js +141 -66
  20. package/src/runtime/app/server/remote/requested.js +172 -0
  21. package/src/runtime/app/server/remote/shared.js +32 -10
  22. package/src/runtime/app/state/server.js +1 -1
  23. package/src/runtime/client/client.js +45 -20
  24. package/src/runtime/client/remote-functions/command.svelte.js +39 -16
  25. package/src/runtime/client/remote-functions/form.svelte.js +41 -24
  26. package/src/runtime/client/remote-functions/prerender.svelte.js +105 -76
  27. package/src/runtime/client/remote-functions/query.svelte.js +408 -138
  28. package/src/runtime/client/remote-functions/shared.svelte.js +95 -94
  29. package/src/runtime/components/svelte-5/error.svelte +2 -0
  30. package/src/runtime/form-utils.js +3 -7
  31. package/src/runtime/server/endpoint.js +0 -1
  32. package/src/runtime/server/page/actions.js +2 -1
  33. package/src/runtime/server/page/load_data.js +3 -1
  34. package/src/runtime/server/page/render.js +38 -15
  35. package/src/runtime/server/remote.js +65 -50
  36. package/src/runtime/server/respond.js +17 -3
  37. package/src/runtime/server/utils.js +0 -12
  38. package/src/runtime/shared.js +233 -5
  39. package/src/types/global-private.d.ts +4 -4
  40. package/src/types/internal.d.ts +80 -44
  41. package/src/utils/css.js +0 -3
  42. package/src/utils/escape.js +15 -3
  43. package/src/version.js +1 -1
  44. package/types/index.d.ts +67 -13
  45. package/types/index.d.ts.map +6 -1
@@ -1,16 +1,41 @@
1
1
  /** @import { RemoteQueryFunction } from '@sveltejs/kit' */
2
2
  /** @import { RemoteFunctionResponse } from 'types' */
3
3
  import { app_dir, base } from '$app/paths/internal/client';
4
- import { app, goto, query_map, remote_responses } from '../client.js';
5
- import { tick } from 'svelte';
4
+ import { app, goto, query_map, query_responses } from '../client.js';
6
5
  import {
7
- create_remote_function,
8
6
  get_remote_request_headers,
7
+ QUERY_FUNCTION_ID,
8
+ QUERY_OVERRIDE_KEY,
9
+ QUERY_RESOURCE_KEY,
9
10
  remote_request
10
11
  } from './shared.svelte.js';
11
12
  import * as devalue from 'devalue';
12
13
  import { HttpError, Redirect } from '@sveltejs/kit/internal';
13
14
  import { DEV } from 'esm-env';
15
+ import { with_resolvers } from '../../../utils/promise.js';
16
+ import { tick, untrack } from 'svelte';
17
+ import { create_remote_key, stringify_remote_arg, unfriendly_hydratable } from '../../shared.js';
18
+
19
+ /**
20
+ * @template T
21
+ * @typedef {{
22
+ * count: number;
23
+ * resource: Query<T>;
24
+ * cleanup: () => void;
25
+ * }} RemoteQueryCacheEntry
26
+ */
27
+
28
+ /**
29
+ * @returns {boolean} Returns `true` if we are in an effect
30
+ */
31
+ function is_in_effect() {
32
+ try {
33
+ $effect.pre(() => {});
34
+ return true;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
14
39
 
15
40
  /**
16
41
  * @param {string} id
@@ -19,144 +44,152 @@ import { DEV } from 'esm-env';
19
44
  export function query(id) {
20
45
  if (DEV) {
21
46
  // If this reruns as part of HMR, refresh the query
22
- for (const [key, entry] of query_map) {
23
- if (key === id || key.startsWith(id + '/')) {
47
+ const entries = query_map.get(id);
48
+
49
+ if (entries) {
50
+ for (const entry of entries.values()) {
24
51
  // use optional chaining in case a prerender function was turned into a query
25
- entry.resource.refresh?.();
52
+ void entry.resource.refresh?.();
26
53
  }
27
54
  }
28
55
  }
29
56
 
30
- return create_remote_function(id, (cache_key, payload) => {
31
- return new Query(cache_key, async () => {
32
- if (Object.hasOwn(remote_responses, cache_key)) {
33
- return remote_responses[cache_key];
34
- }
35
-
57
+ /** @type {RemoteQueryFunction<any, any>} */
58
+ const wrapper = (arg) => {
59
+ return new QueryProxy(id, arg, async (key, payload) => {
36
60
  const url = `${base}/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
37
61
 
38
- const result = await remote_request(url, get_remote_request_headers());
39
- return devalue.parse(result, app.decoders);
62
+ const serialized = await unfriendly_hydratable(key, () =>
63
+ remote_request(url, get_remote_request_headers())
64
+ );
65
+
66
+ return devalue.parse(serialized, app.decoders);
40
67
  });
41
- });
68
+ };
69
+
70
+ Object.defineProperty(wrapper, QUERY_FUNCTION_ID, { value: id });
71
+
72
+ return wrapper;
42
73
  }
43
74
 
44
75
  /**
45
76
  * @param {string} id
46
- * @returns {(arg: any) => Query<any>}
77
+ * @returns {RemoteQueryFunction<any, any>}
47
78
  */
48
79
  export function query_batch(id) {
49
80
  /** @type {Map<string, Array<{resolve: (value: any) => void, reject: (error: any) => void}>>} */
50
- // eslint-disable-next-line svelte/prefer-svelte-reactivity -- we don't need reactivity for this
51
81
  let batching = new Map();
52
82
 
53
- return create_remote_function(id, (cache_key, payload) => {
54
- return new Query(cache_key, () => {
55
- if (Object.hasOwn(remote_responses, cache_key)) {
56
- return remote_responses[cache_key];
57
- }
58
-
59
- // Collect all the calls to the same query in the same macrotask,
60
- // then execute them as one backend request.
61
- return new Promise((resolve, reject) => {
62
- // create_remote_function caches identical calls, but in case a refresh to the same query is called multiple times this function
63
- // is invoked multiple times with the same payload, so we need to deduplicate here
64
- const entry = batching.get(payload) ?? [];
65
- entry.push({ resolve, reject });
66
- batching.set(payload, entry);
67
-
68
- if (batching.size > 1) return;
69
-
70
- // Do this here, after await Svelte' reactivity context is gone.
71
- // TODO is it possible to have batches of the same key
72
- // but in different forks/async contexts and in the same macrotask?
73
- // If so this would potentially be buggy
74
- const headers = {
75
- 'Content-Type': 'application/json',
76
- ...get_remote_request_headers()
77
- };
78
-
79
- // Wait for the next macrotask - don't use microtask as Svelte runtime uses these to collect changes and flush them,
80
- // and flushes could reveal more queries that should be batched.
81
- setTimeout(async () => {
82
- const batched = batching;
83
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
84
- batching = new Map();
85
-
86
- try {
87
- const response = await fetch(`${base}/${app_dir}/remote/${id}`, {
88
- method: 'POST',
89
- body: JSON.stringify({
90
- payloads: Array.from(batched.keys())
91
- }),
92
- headers
93
- });
94
-
95
- if (!response.ok) {
96
- throw new Error('Failed to execute batch query');
97
- }
83
+ /** @type {RemoteQueryFunction<any, any>} */
84
+ const wrapper = (arg) => {
85
+ return new QueryProxy(id, arg, async (key, payload) => {
86
+ const serialized = await unfriendly_hydratable(key, () => {
87
+ return new Promise((resolve, reject) => {
88
+ // create_remote_function caches identical calls, but in case a refresh to the same query is called multiple times this function
89
+ // is invoked multiple times with the same payload, so we need to deduplicate here
90
+ const entry = batching.get(payload) ?? [];
91
+ entry.push({ resolve, reject });
92
+ batching.set(payload, entry);
93
+
94
+ if (batching.size > 1) return;
95
+
96
+ // Do this here, after await Svelte' reactivity context is gone.
97
+ // TODO is it possible to have batches of the same key
98
+ // but in different forks/async contexts and in the same macrotask?
99
+ // If so this would potentially be buggy
100
+ const headers = {
101
+ 'Content-Type': 'application/json',
102
+ ...get_remote_request_headers()
103
+ };
104
+
105
+ // Wait for the next macrotask - don't use microtask as Svelte runtime uses these to collect changes and flush them,
106
+ // and flushes could reveal more queries that should be batched.
107
+ setTimeout(async () => {
108
+ const batched = batching;
109
+ batching = new Map();
110
+
111
+ try {
112
+ const response = await fetch(`${base}/${app_dir}/remote/${id}`, {
113
+ method: 'POST',
114
+ body: JSON.stringify({
115
+ payloads: Array.from(batched.keys())
116
+ }),
117
+ headers
118
+ });
119
+
120
+ if (!response.ok) {
121
+ throw new Error('Failed to execute batch query');
122
+ }
98
123
 
99
- const result = /** @type {RemoteFunctionResponse} */ (await response.json());
100
- if (result.type === 'error') {
101
- throw new HttpError(result.status ?? 500, result.error);
102
- }
124
+ const result = /** @type {RemoteFunctionResponse} */ (await response.json());
125
+ if (result.type === 'error') {
126
+ throw new HttpError(result.status ?? 500, result.error);
127
+ }
103
128
 
104
- if (result.type === 'redirect') {
105
- await goto(result.location);
106
- throw new Redirect(307, result.location);
107
- }
129
+ if (result.type === 'redirect') {
130
+ await goto(result.location);
131
+ throw new Redirect(307, result.location);
132
+ }
108
133
 
109
- const results = devalue.parse(result.result, app.decoders);
134
+ const results = devalue.parse(result.result, app.decoders);
110
135
 
111
- // Resolve individual queries
112
- // Maps guarantee insertion order so we can do it like this
113
- let i = 0;
136
+ // Resolve individual queries
137
+ // Maps guarantee insertion order so we can do it like this
138
+ let i = 0;
114
139
 
115
- for (const resolvers of batched.values()) {
116
- for (const { resolve, reject } of resolvers) {
117
- if (results[i].type === 'error') {
118
- reject(new HttpError(results[i].status, results[i].error));
119
- } else {
120
- resolve(results[i].data);
140
+ for (const resolvers of batched.values()) {
141
+ for (const { resolve, reject } of resolvers) {
142
+ if (results[i].type === 'error') {
143
+ reject(new HttpError(results[i].status, results[i].error));
144
+ } else {
145
+ resolve(results[i].data);
146
+ }
121
147
  }
148
+ i++;
122
149
  }
123
- i++;
124
- }
125
- } catch (error) {
126
- // Reject all queries in the batch
127
- for (const resolver of batched.values()) {
128
- for (const { reject } of resolver) {
129
- reject(error);
150
+ } catch (error) {
151
+ // Reject all queries in the batch
152
+ for (const resolver of batched.values()) {
153
+ for (const { reject } of resolver) {
154
+ reject(error);
155
+ }
130
156
  }
131
157
  }
132
- }
133
- }, 0);
158
+ }, 0);
159
+ });
134
160
  });
161
+
162
+ return devalue.parse(serialized, app.decoders);
135
163
  });
136
- });
164
+ };
165
+
166
+ Object.defineProperty(wrapper, QUERY_FUNCTION_ID, { value: id });
167
+
168
+ return wrapper;
137
169
  }
138
170
 
139
171
  /**
172
+ * The actual query instance. There should only ever be one active query instance per key.
173
+ *
140
174
  * @template T
141
- * @implements {Partial<Promise<T>>}
175
+ * @implements {Promise<T>}
142
176
  */
143
177
  export class Query {
144
178
  /** @type {string} */
145
- _key;
179
+ #key;
146
180
 
147
- #init = false;
148
181
  /** @type {() => Promise<T>} */
149
182
  #fn;
150
183
  #loading = $state(true);
151
- /** @type {Array<() => void>} */
184
+ /** @type {Array<(value: undefined) => void>} */
152
185
  #latest = [];
153
186
 
154
187
  /** @type {boolean} */
155
188
  #ready = $state(false);
156
189
  /** @type {T | undefined} */
157
190
  #raw = $state.raw();
158
- /** @type {Promise<void>} */
159
- #promise;
191
+ /** @type {Promise<void> | null} */
192
+ #promise = $state.raw(null);
160
193
  /** @type {Array<(old: T) => T>} */
161
194
  #overrides = $state([]);
162
195
 
@@ -168,21 +201,17 @@ export class Query {
168
201
  return this.#overrides.reduce((v, r) => r(v), /** @type {T} */ (this.#raw));
169
202
  });
170
203
 
204
+ /** @type {any} */
171
205
  #error = $state.raw(undefined);
172
206
 
173
207
  /** @type {Promise<T>['then']} */
174
208
  // @ts-expect-error TS doesn't understand that the promise returns something
175
209
  #then = $derived.by(() => {
176
- const p = this.#promise;
210
+ const p = this.#get_promise();
177
211
  this.#overrides.length;
178
212
 
179
213
  return (resolve, reject) => {
180
- const result = (async () => {
181
- await p;
182
- // svelte-ignore await_reactivity_loss
183
- await tick();
184
- return /** @type {T} */ (this.#current);
185
- })();
214
+ const result = p.then(tick).then(() => /** @type {T} */ (this.#current));
186
215
 
187
216
  if (resolve || reject) {
188
217
  return result.then(resolve, reject);
@@ -197,34 +226,34 @@ export class Query {
197
226
  * @param {() => Promise<T>} fn
198
227
  */
199
228
  constructor(key, fn) {
200
- this._key = key;
229
+ this.#key = key;
201
230
  this.#fn = fn;
202
- this.#promise = $state.raw(this.#run());
231
+ }
232
+
233
+ #get_promise() {
234
+ void untrack(() => (this.#promise ??= this.#run()));
235
+ return /** @type {Promise<T>} */ (this.#promise);
236
+ }
237
+
238
+ #start() {
239
+ // there is a really weird bug with untrack and writes and initializations
240
+ // every time you see this comment, try removing the `tick.then` here and see
241
+ // if all the tests still pass with the latest svelte version
242
+ // if they do, congrats, you can remove tick.then
243
+ void tick().then(() => this.#get_promise());
244
+ }
245
+
246
+ #clear_pending() {
247
+ this.#latest.forEach((r) => r(undefined));
248
+ this.#latest.length = 0;
203
249
  }
204
250
 
205
251
  #run() {
206
- // Prevent state_unsafe_mutation error on first run when the resource is created within the template
207
- if (this.#init) {
208
- this.#loading = true;
209
- } else {
210
- this.#init = true;
211
- }
252
+ this.#loading = true;
212
253
 
213
- // Don't use Promise.withResolvers, it's too new still
214
- /** @type {() => void} */
215
- let resolve;
216
- /** @type {(e?: any) => void} */
217
- let reject;
218
- /** @type {Promise<void>} */
219
- const promise = new Promise((res, rej) => {
220
- resolve = res;
221
- reject = rej;
222
- });
254
+ const { promise, resolve, reject } = with_resolvers();
223
255
 
224
- this.#latest.push(
225
- // @ts-expect-error it's defined at this point
226
- resolve
227
- );
256
+ this.#latest.push(resolve);
228
257
 
229
258
  Promise.resolve(this.#fn())
230
259
  .then((value) => {
@@ -232,21 +261,27 @@ export class Query {
232
261
  const idx = this.#latest.indexOf(resolve);
233
262
  if (idx === -1) return;
234
263
 
235
- this.#latest.splice(0, idx).forEach((r) => r());
236
- this.#ready = true;
237
- this.#loading = false;
238
- this.#raw = value;
239
- this.#error = undefined;
264
+ // Untrack this to not trigger mutation validation errors which can occur if you do e.g. $derived({ a: await queryA(), b: await queryB() })
265
+ untrack(() => {
266
+ this.#latest.splice(0, idx).forEach((r) => r(undefined));
267
+ this.#ready = true;
268
+ this.#loading = false;
269
+ this.#raw = value;
270
+ this.#error = undefined;
271
+ });
240
272
 
241
- resolve();
273
+ resolve(undefined);
242
274
  })
243
275
  .catch((e) => {
244
276
  const idx = this.#latest.indexOf(resolve);
245
277
  if (idx === -1) return;
246
278
 
247
- this.#latest.splice(0, idx).forEach((r) => r());
248
- this.#error = e;
249
- this.#loading = false;
279
+ untrack(() => {
280
+ this.#latest.splice(0, idx).forEach((r) => r(undefined));
281
+ this.#error = e;
282
+ this.#loading = false;
283
+ });
284
+
250
285
  reject(e);
251
286
  });
252
287
 
@@ -281,10 +316,12 @@ export class Query {
281
316
  }
282
317
 
283
318
  get current() {
319
+ this.#start();
284
320
  return this.#current;
285
321
  }
286
322
 
287
323
  get error() {
324
+ this.#start();
288
325
  return this.#error;
289
326
  }
290
327
 
@@ -292,6 +329,7 @@ export class Query {
292
329
  * Returns true if the resource is loading or reloading.
293
330
  */
294
331
  get loading() {
332
+ this.#start();
295
333
  return this.#loading;
296
334
  }
297
335
 
@@ -299,6 +337,7 @@ export class Query {
299
337
  * Returns true once the resource has been loaded for the first time.
300
338
  */
301
339
  get ready() {
340
+ this.#start();
302
341
  return this.#ready;
303
342
  }
304
343
 
@@ -306,7 +345,7 @@ export class Query {
306
345
  * @returns {Promise<void>}
307
346
  */
308
347
  refresh() {
309
- delete remote_responses[this._key];
348
+ delete query_responses[this.#key];
310
349
  return (this.#promise = this.#run());
311
350
  }
312
351
 
@@ -314,6 +353,7 @@ export class Query {
314
353
  * @param {T} value
315
354
  */
316
355
  set(value) {
356
+ this.#clear_pending();
317
357
  this.#ready = true;
318
358
  this.#loading = false;
319
359
  this.#error = undefined;
@@ -321,21 +361,251 @@ export class Query {
321
361
  this.#promise = Promise.resolve();
322
362
  }
323
363
 
364
+ /**
365
+ * @param {unknown} error
366
+ */
367
+ fail(error) {
368
+ this.#clear_pending();
369
+ this.#loading = false;
370
+ this.#error = error;
371
+
372
+ const promise = Promise.reject(error);
373
+
374
+ promise.catch(() => {});
375
+ this.#promise = promise;
376
+ }
377
+
324
378
  /**
325
379
  * @param {(old: T) => T} fn
380
+ * @returns {(() => void) & { [QUERY_OVERRIDE_KEY]: string }}
326
381
  */
327
382
  withOverride(fn) {
328
383
  this.#overrides.push(fn);
329
384
 
330
- return {
331
- _key: this._key,
332
- release: () => {
385
+ const release = /** @type {(() => void) & { [QUERY_OVERRIDE_KEY]: string }} */ (
386
+ () => {
333
387
  const i = this.#overrides.indexOf(fn);
334
388
 
335
389
  if (i !== -1) {
336
390
  this.#overrides.splice(i, 1);
337
391
  }
338
392
  }
393
+ );
394
+
395
+ Object.defineProperty(release, QUERY_OVERRIDE_KEY, { value: this.#key });
396
+
397
+ return release;
398
+ }
399
+
400
+ get [Symbol.toStringTag]() {
401
+ return 'Query';
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Manages the caching layer between the user and the actual {@link Query} instance. This is the thing
407
+ * the developer actually gets to interact with in their application code.
408
+ *
409
+ * @template T
410
+ * @implements {Promise<T>}
411
+ */
412
+ class QueryProxy {
413
+ #id;
414
+ #key;
415
+ #payload;
416
+ #fn;
417
+ #active = true;
418
+ /**
419
+ * Whether this proxy was created in a tracking context.
420
+ * @readonly
421
+ */
422
+ #tracking = is_in_effect();
423
+
424
+ /**
425
+ * @param {string} id
426
+ * @param {any} arg
427
+ * @param {(key: string, payload: string) => Promise<T>} fn
428
+ */
429
+ constructor(id, arg, fn) {
430
+ this.#id = id;
431
+ this.#payload = stringify_remote_arg(arg, app.hooks.transport);
432
+ this.#key = create_remote_key(id, this.#payload);
433
+ Object.defineProperty(this, QUERY_RESOURCE_KEY, { value: this.#key });
434
+ this.#fn = fn;
435
+
436
+ if (!this.#tracking) {
437
+ this.#active = false;
438
+ return;
439
+ }
440
+
441
+ const entry = this.#get_or_create_cache_entry();
442
+
443
+ $effect.pre(() => () => {
444
+ const die = this.#release(entry);
445
+ void tick().then(die);
446
+ });
447
+ }
448
+
449
+ /** @returns {RemoteQueryCacheEntry<T>} */
450
+ #get_or_create_cache_entry() {
451
+ let query_instances = query_map.get(this.#id);
452
+
453
+ if (!query_instances) {
454
+ query_instances = new Map();
455
+ query_map.set(this.#id, query_instances);
456
+ }
457
+
458
+ let this_instance = query_instances.get(this.#payload);
459
+
460
+ if (!this_instance) {
461
+ const c = (this_instance = {
462
+ count: 0,
463
+ resource: /** @type {Query<T>} */ (/** @type {unknown} */ (null)),
464
+ cleanup: /** @type {() => void} */ (/** @type {unknown} */ (null))
465
+ });
466
+
467
+ c.cleanup = $effect.root(() => {
468
+ c.resource = new Query(this.#key, () => this.#fn(this.#key, this.#payload));
469
+ });
470
+
471
+ query_instances.set(this.#payload, this_instance);
472
+ }
473
+
474
+ this_instance.count += 1;
475
+
476
+ return this_instance;
477
+ }
478
+
479
+ /**
480
+ * @param {RemoteQueryCacheEntry<T>} entry
481
+ * @param {boolean} [deactivate]
482
+ * @returns
483
+ */
484
+ #release(entry, deactivate = true) {
485
+ this.#active &&= !deactivate;
486
+ entry.count -= 1;
487
+
488
+ return () => {
489
+ const query_instances = query_map.get(this.#id);
490
+ const this_instance = query_instances?.get(this.#payload);
491
+
492
+ if (this_instance?.count === 0) {
493
+ this_instance.cleanup();
494
+ query_instances?.delete(this.#payload);
495
+ }
496
+ if (query_instances?.size === 0) {
497
+ query_map.delete(this.#id);
498
+ }
339
499
  };
340
500
  }
501
+
502
+ #get_cached_query() {
503
+ // TODO iterate on error messages
504
+ if (!this.#tracking) {
505
+ throw new Error(
506
+ 'This query was not created in a reactive context and is limited to calling `.run`, `.refresh`, and `.set`.'
507
+ );
508
+ }
509
+
510
+ if (!this.#active) {
511
+ throw new Error(
512
+ 'This query instance is no longer active and can no longer be used for reactive state access. ' +
513
+ 'This typically means you created the query in a tracking context and stashed it somewhere outside of a tracking context.'
514
+ );
515
+ }
516
+
517
+ const cached = query_map.get(this.#id)?.get(this.#payload);
518
+
519
+ if (!cached) {
520
+ // The only case where `this.#active` can be `true` is when we've added an entry to `query_map`, and the
521
+ // only way that entry can get removed is if this instance (and all others) have been deactivated.
522
+ // So if we get here, someone (us, check git blame and point fingers) did `entry.count -= 1` improperly.
523
+ throw new Error(
524
+ 'No cached query found. This should be impossible. Please file a bug report.'
525
+ );
526
+ }
527
+
528
+ return cached.resource;
529
+ }
530
+
531
+ #safe_get_cached_query() {
532
+ return query_map.get(this.#id)?.get(this.#payload)?.resource;
533
+ }
534
+
535
+ get current() {
536
+ return this.#get_cached_query().current;
537
+ }
538
+
539
+ get error() {
540
+ return this.#get_cached_query().error;
541
+ }
542
+
543
+ get loading() {
544
+ return this.#get_cached_query().loading;
545
+ }
546
+
547
+ get ready() {
548
+ return this.#get_cached_query().ready;
549
+ }
550
+
551
+ run() {
552
+ if (is_in_effect()) {
553
+ throw new Error(
554
+ 'On the client, .run() can only be called outside render, e.g. in universal `load` functions and event handlers. In render, await the query directly'
555
+ );
556
+ }
557
+
558
+ if (Object.hasOwn(query_responses, this.#key)) {
559
+ return Promise.resolve(query_responses[this.#key]);
560
+ }
561
+ return this.#fn(this.#key, this.#payload);
562
+ }
563
+
564
+ refresh() {
565
+ return this.#safe_get_cached_query()?.refresh() ?? Promise.resolve();
566
+ }
567
+
568
+ /** @type {Query<T>['set']} */
569
+ set(value) {
570
+ this.#safe_get_cached_query()?.set(value);
571
+ }
572
+
573
+ /** @type {Query<T>['withOverride']} */
574
+ withOverride(fn) {
575
+ const entry = this.#get_or_create_cache_entry();
576
+ const override = entry.resource.withOverride(fn);
577
+
578
+ const release = /** @type {(() => void) & { [QUERY_OVERRIDE_KEY]: string }} */ (
579
+ () => {
580
+ override();
581
+ this.#release(entry, false)();
582
+ }
583
+ );
584
+
585
+ Object.defineProperty(release, QUERY_OVERRIDE_KEY, { value: override[QUERY_OVERRIDE_KEY] });
586
+
587
+ return release;
588
+ }
589
+
590
+ /** @type {Query<T>['then']} */
591
+ get then() {
592
+ const cached = this.#get_cached_query();
593
+ return cached.then.bind(cached);
594
+ }
595
+
596
+ /** @type {Query<T>['catch']} */
597
+ get catch() {
598
+ const cached = this.#get_cached_query();
599
+ return cached.catch.bind(cached);
600
+ }
601
+
602
+ /** @type {Query<T>['finally']} */
603
+ get finally() {
604
+ const cached = this.#get_cached_query();
605
+ return cached.finally.bind(cached);
606
+ }
607
+
608
+ get [Symbol.toStringTag]() {
609
+ return 'QueryProxy';
610
+ }
341
611
  }