@sveltejs/kit 2.57.1 → 2.59.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 (35) hide show
  1. package/package.json +2 -2
  2. package/src/exports/internal/remote-functions.js +1 -1
  3. package/src/exports/public.d.ts +107 -16
  4. package/src/runtime/app/paths/client.js +7 -0
  5. package/src/runtime/app/paths/server.js +7 -0
  6. package/src/runtime/app/server/remote/command.js +7 -6
  7. package/src/runtime/app/server/remote/form.js +2 -1
  8. package/src/runtime/app/server/remote/prerender.js +3 -4
  9. package/src/runtime/app/server/remote/query.js +327 -113
  10. package/src/runtime/app/server/remote/requested.js +127 -32
  11. package/src/runtime/app/server/remote/shared.js +89 -20
  12. package/src/runtime/client/client.js +92 -62
  13. package/src/runtime/client/ndjson.js +42 -0
  14. package/src/runtime/client/remote-functions/command.svelte.js +8 -3
  15. package/src/runtime/client/remote-functions/form.svelte.js +24 -6
  16. package/src/runtime/client/remote-functions/index.js +3 -1
  17. package/src/runtime/client/remote-functions/query-batch.svelte.js +105 -0
  18. package/src/runtime/client/remote-functions/query-live.svelte.js +636 -0
  19. package/src/runtime/client/remote-functions/query.svelte.js +48 -148
  20. package/src/runtime/client/remote-functions/shared.svelte.js +76 -23
  21. package/src/runtime/form-utils.js +33 -12
  22. package/src/runtime/server/page/index.js +26 -16
  23. package/src/runtime/server/page/load_data.js +4 -2
  24. package/src/runtime/server/page/render.js +21 -18
  25. package/src/runtime/server/remote.js +117 -9
  26. package/src/runtime/server/respond.js +11 -8
  27. package/src/runtime/server/utils.js +10 -0
  28. package/src/runtime/shared.js +3 -3
  29. package/src/runtime/utils.js +0 -1
  30. package/src/types/internal.d.ts +54 -14
  31. package/src/utils/page_nodes.js +1 -0
  32. package/src/utils/url.js +3 -3
  33. package/src/version.js +1 -1
  34. package/types/index.d.ts +182 -30
  35. package/types/index.d.ts.map +8 -4
@@ -1,13 +1,19 @@
1
- /** @import { RemoteQuery, RemoteQueryFunction } from '@sveltejs/kit' */
2
- /** @import { RemoteInternals, MaybePromise, RequestState, RemoteQueryBatchInternals, RemoteQueryInternals } from 'types' */
1
+ /** @import { RemoteLiveQuery, RemoteLiveQueryFunction, RemoteQuery, RemoteQueryFunction } from '@sveltejs/kit' */
2
+ /** @import { RemoteInternals, MaybePromise, RequestState, RemoteQueryLiveInternals, RemoteQueryBatchInternals, RemoteQueryInternals, RemoteLiveQueryUserFunctionReturnType } from 'types' */
3
3
  /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
4
4
  import { get_request_store } from '@sveltejs/kit/internal/server';
5
5
  import { create_remote_key, stringify, stringify_remote_arg } from '../../../shared.js';
6
6
  import { prerendering } from '__sveltekit/environment';
7
- import { noop } from '../../../../utils/functions.js';
8
- import { create_validator, get_cache, get_response, run_remote_function } from './shared.js';
7
+ import {
8
+ create_validator,
9
+ get_cache,
10
+ get_response,
11
+ run_remote_function,
12
+ run_remote_generator
13
+ } from './shared.js';
9
14
  import { handle_error_and_jsonify } from '../../../server/utils.js';
10
15
  import { HttpError, SvelteKitError } from '@sveltejs/kit/internal';
16
+ import { noop } from '../../../../utils/functions.js';
11
17
 
12
18
  /**
13
19
  * Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
@@ -43,7 +49,7 @@ import { HttpError, SvelteKitError } from '@sveltejs/kit/internal';
43
49
  * @overload
44
50
  * @param {Schema} schema
45
51
  * @param {(arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>} fn
46
- * @returns {RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output>}
52
+ * @returns {RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output, StandardSchemaV1.InferOutput<Schema>>}
47
53
  * @since 2.27
48
54
  */
49
55
  /**
@@ -63,7 +69,19 @@ export function query(validate_or_fn, maybe_fn) {
63
69
  const validate = create_validator(validate_or_fn, maybe_fn);
64
70
 
65
71
  /** @type {RemoteQueryInternals} */
66
- const __ = { type: 'query', id: '', name: '', validate };
72
+ const __ = {
73
+ type: 'query',
74
+ id: '',
75
+ name: '',
76
+ validate,
77
+ bind(payload, validated_arg) {
78
+ const { event, state } = get_request_store();
79
+
80
+ return create_query_resource(__, payload, state, () =>
81
+ run_remote_function(event, state, false, () => validated_arg, fn)
82
+ );
83
+ }
84
+ };
67
85
 
68
86
  /** @type {RemoteQueryFunction<Input, Output> & { __: RemoteQueryInternals }} */
69
87
  const wrapper = (arg) => {
@@ -74,11 +92,10 @@ export function query(validate_or_fn, maybe_fn) {
74
92
  }
75
93
 
76
94
  const { event, state } = get_request_store();
77
- // if the user got this argument from `requested(query)`, it will have already passed validation
78
- const is_validated = is_validated_argument(__, state, arg);
95
+ const payload = stringify_remote_arg(arg, state.transport);
79
96
 
80
- return create_query_resource(__, arg, state, () =>
81
- run_remote_function(event, state, false, () => (is_validated ? arg : validate(arg)), fn)
97
+ return create_query_resource(__, payload, state, () =>
98
+ run_remote_function(event, state, false, () => validate(arg), fn)
82
99
  );
83
100
  };
84
101
 
@@ -88,30 +105,107 @@ export function query(validate_or_fn, maybe_fn) {
88
105
  }
89
106
 
90
107
  /**
91
- * @param {RemoteQueryInternals} __
92
- * @param {RequestState} state
93
- * @param {any} arg
108
+ * Creates a live remote query. When called from the browser, the function will be invoked on the server via a streaming `fetch` call.
109
+ *
110
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query.live) for full documentation.
111
+ *
112
+ * @template Output
113
+ * @overload
114
+ * @param {(arg: void) => RemoteLiveQueryUserFunctionReturnType<Output>} fn
115
+ * @returns {RemoteLiveQueryFunction<void, Output>}
94
116
  */
95
- function is_validated_argument(__, state, arg) {
96
- return state.remote.validated?.get(__.id)?.has(arg) ?? false;
97
- }
98
-
99
117
  /**
100
- * @param {RemoteQueryInternals} __
101
- * @param {RequestState} state
102
- * @param {any} arg
118
+ * @template Input
119
+ * @template Output
120
+ * @overload
121
+ * @param {'unchecked'} validate
122
+ * @param {(arg: Input) => RemoteLiveQueryUserFunctionReturnType<Output>} fn
123
+ * @returns {RemoteLiveQueryFunction<Input, Output>}
124
+ */
125
+ /**
126
+ * @template {StandardSchemaV1} Schema
127
+ * @template Output
128
+ * @overload
129
+ * @param {Schema} schema
130
+ * @param {(arg: StandardSchemaV1.InferOutput<Schema>) => RemoteLiveQueryUserFunctionReturnType<Output>} fn
131
+ * @returns {RemoteLiveQueryFunction<StandardSchemaV1.InferInput<Schema>, Output, StandardSchemaV1.InferOutput<Schema>>}
132
+ */
133
+ /**
134
+ * @template Input
135
+ * @template Output
136
+ * @param {any} validate_or_fn
137
+ * @param {(args: Input) => RemoteLiveQueryUserFunctionReturnType<Output>} [maybe_fn]
138
+ * @returns {RemoteLiveQueryFunction<Input, Output>}
103
139
  */
104
- export function mark_argument_validated(__, state, arg) {
105
- const validated = (state.remote.validated ??= new Map());
106
- let validated_args = validated.get(__.id);
140
+ /*@__NO_SIDE_EFFECTS__*/
141
+ function live(validate_or_fn, maybe_fn) {
142
+ /** @type {(arg: Input) => RemoteLiveQueryUserFunctionReturnType<Output>} */
143
+ const fn = maybe_fn ?? validate_or_fn;
107
144
 
108
- if (!validated_args) {
109
- validated_args = new Set();
110
- validated.set(__.id, validated_args);
111
- }
145
+ /** @type {(arg?: any) => MaybePromise<Input>} */
146
+ const validate = create_validator(validate_or_fn, maybe_fn);
147
+
148
+ /**
149
+ * @param {any} event
150
+ * @param {any} state
151
+ * @param {any} get_input
152
+ */
153
+ const run = (event, state, get_input) =>
154
+ run_remote_generator(event, state, false, get_input, fn, __.name);
155
+
156
+ /**
157
+ * @param {any} generator
158
+ * @returns {Promise<any>}
159
+ */
160
+ const first_value = async (generator) => {
161
+ try {
162
+ const { value, done } = await generator.next();
163
+
164
+ if (done) {
165
+ throw new Error(`query.live '${__.name}' did not yield a value`);
166
+ }
112
167
 
113
- validated_args.add(arg);
114
- return arg;
168
+ return value;
169
+ } finally {
170
+ await generator.return(undefined);
171
+ }
172
+ };
173
+
174
+ /** @type {RemoteQueryLiveInternals} */
175
+ const __ = {
176
+ type: 'query_live',
177
+ id: '',
178
+ name: '',
179
+ run: (event, state, arg) => run(event, state, () => validate(arg)),
180
+ validate,
181
+ bind(payload, validated_arg) {
182
+ const { event, state } = get_request_store();
183
+
184
+ return create_live_query_resource(__, payload, state, () =>
185
+ first_value(run(event, state, () => validated_arg))
186
+ );
187
+ }
188
+ };
189
+
190
+ /** @type {RemoteLiveQueryFunction<Input, Output> & { __: RemoteQueryLiveInternals }} */
191
+ const wrapper = (arg) => {
192
+ if (prerendering) {
193
+ throw new Error(
194
+ `Cannot call query.live '${__.name}' while prerendering, as prerendered pages need static data. Use 'prerender' from $app/server instead`
195
+ );
196
+ }
197
+
198
+ const { event, state } = get_request_store();
199
+ const payload = stringify_remote_arg(arg, state.transport);
200
+
201
+ return create_live_query_resource(__, payload, state, () =>
202
+ first_value(run(event, state, () => validate(arg)))
203
+ );
204
+ };
205
+
206
+ Object.defineProperty(wrapper, '__', { value: __ });
207
+
208
+ return wrapper;
115
209
  }
116
210
 
117
211
  /**
@@ -137,7 +231,7 @@ export function mark_argument_validated(__, state, arg) {
137
231
  * @overload
138
232
  * @param {Schema} schema
139
233
  * @param {(args: StandardSchemaV1.InferOutput<Schema>[]) => MaybePromise<(arg: StandardSchemaV1.InferOutput<Schema>, idx: number) => Output>} fn
140
- * @returns {RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output>}
234
+ * @returns {RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output, StandardSchemaV1.InferOutput<Schema>>}
141
235
  * @since 2.35
142
236
  */
143
237
  /**
@@ -156,11 +250,81 @@ function batch(validate_or_fn, maybe_fn) {
156
250
  /** @type {(arg?: any) => MaybePromise<Input>} */
157
251
  const validate = create_validator(validate_or_fn, maybe_fn);
158
252
 
253
+ /** @type {Map<string, { get_validated: () => MaybePromise<any>, resolvers: Array<{resolve: (value: any) => void, reject: (error: any) => void}> }>} */
254
+ let batching = new Map();
255
+
256
+ /**
257
+ * Enqueues a single call into the current batch (creating one if necessary)
258
+ * and returns a promise that resolves with the result for this entry.
259
+ *
260
+ * @param {string} payload — the stringified raw argument (cache key)
261
+ * @param {() => MaybePromise<any>} get_validated — produces the validated argument for this entry
262
+ * @returns {Promise<any>}
263
+ */
264
+ const enqueue = (payload, get_validated) => {
265
+ const { event, state } = get_request_store();
266
+
267
+ return new Promise((resolve, reject) => {
268
+ const entry = batching.get(payload);
269
+
270
+ if (entry) {
271
+ entry.resolvers.push({ resolve, reject });
272
+ return;
273
+ }
274
+
275
+ batching.set(payload, {
276
+ get_validated,
277
+ resolvers: [{ resolve, reject }]
278
+ });
279
+
280
+ if (batching.size > 1) return;
281
+
282
+ setTimeout(async () => {
283
+ const batched = batching;
284
+ batching = new Map();
285
+ const entries = Array.from(batched.values());
286
+
287
+ try {
288
+ return await run_remote_function(
289
+ event,
290
+ state,
291
+ false,
292
+ async () => Promise.all(entries.map((entry) => entry.get_validated())),
293
+ async (input) => {
294
+ const get_result = await fn(input);
295
+
296
+ for (let i = 0; i < entries.length; i++) {
297
+ try {
298
+ const result = get_result(input[i], i);
299
+
300
+ for (const resolver of entries[i].resolvers) {
301
+ resolver.resolve(result);
302
+ }
303
+ } catch (error) {
304
+ for (const resolver of entries[i].resolvers) {
305
+ resolver.reject(error);
306
+ }
307
+ }
308
+ }
309
+ }
310
+ );
311
+ } catch (error) {
312
+ for (const entry of batched.values()) {
313
+ for (const resolver of entry.resolvers) {
314
+ resolver.reject(error);
315
+ }
316
+ }
317
+ }
318
+ }, 0);
319
+ });
320
+ };
321
+
159
322
  /** @type {RemoteQueryBatchInternals} */
160
323
  const __ = {
161
324
  type: 'query_batch',
162
325
  id: '',
163
326
  name: '',
327
+ validate,
164
328
  run: async (args, options) => {
165
329
  const { event, state } = get_request_store();
166
330
 
@@ -191,12 +355,14 @@ function batch(validate_or_fn, maybe_fn) {
191
355
  );
192
356
  }
193
357
  );
358
+ },
359
+ bind(payload, validated_arg) {
360
+ const { state } = get_request_store();
361
+
362
+ return create_query_resource(__, payload, state, () => enqueue(payload, () => validated_arg));
194
363
  }
195
364
  };
196
365
 
197
- /** @type {Map<string, { arg: any, resolvers: Array<{resolve: (value: any) => void, reject: (error: any) => void}> }>} */
198
- let batching = new Map();
199
-
200
366
  /** @type {RemoteQueryFunction<Input, Output> & { __: RemoteQueryBatchInternals }} */
201
367
  const wrapper = (arg) => {
202
368
  if (prerendering) {
@@ -205,67 +371,14 @@ function batch(validate_or_fn, maybe_fn) {
205
371
  );
206
372
  }
207
373
 
208
- const { event, state } = get_request_store();
374
+ const { state } = get_request_store();
375
+ const payload = stringify_remote_arg(arg, state.transport);
209
376
 
210
- return create_query_resource(__, arg, state, () => {
377
+ return create_query_resource(__, payload, state, () =>
211
378
  // Collect all the calls to the same query in the same macrotask,
212
379
  // then execute them as one backend request.
213
- return new Promise((resolve, reject) => {
214
- const key = stringify_remote_arg(arg, state.transport);
215
- const entry = batching.get(key);
216
-
217
- if (entry) {
218
- entry.resolvers.push({ resolve, reject });
219
- return;
220
- }
221
-
222
- batching.set(key, {
223
- arg,
224
- resolvers: [{ resolve, reject }]
225
- });
226
-
227
- if (batching.size > 1) return;
228
-
229
- setTimeout(async () => {
230
- const batched = batching;
231
- batching = new Map();
232
- const entries = Array.from(batched.values());
233
- const args = entries.map((entry) => entry.arg);
234
-
235
- try {
236
- return await run_remote_function(
237
- event,
238
- state,
239
- false,
240
- async () => Promise.all(args.map(validate)),
241
- async (input) => {
242
- const get_result = await fn(input);
243
-
244
- for (let i = 0; i < entries.length; i++) {
245
- try {
246
- const result = get_result(input[i], i);
247
-
248
- for (const resolver of entries[i].resolvers) {
249
- resolver.resolve(result);
250
- }
251
- } catch (error) {
252
- for (const resolver of entries[i].resolvers) {
253
- resolver.reject(error);
254
- }
255
- }
256
- }
257
- }
258
- );
259
- } catch (error) {
260
- for (const entry of batched.values()) {
261
- for (const resolver of entry.resolvers) {
262
- resolver.reject(error);
263
- }
264
- }
265
- }
266
- }, 0);
267
- });
268
- });
380
+ enqueue(payload, () => validate(arg))
381
+ );
269
382
  };
270
383
 
271
384
  Object.defineProperty(wrapper, '__', { value: __ });
@@ -275,17 +388,24 @@ function batch(validate_or_fn, maybe_fn) {
275
388
 
276
389
  /**
277
390
  * @param {RemoteInternals} __
278
- * @param {any} arg
391
+ * @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
279
392
  * @param {RequestState} state
280
393
  * @param {() => Promise<any>} fn
281
394
  * @returns {RemoteQuery<any>}
282
395
  */
283
- function create_query_resource(__, arg, state, fn) {
396
+ function create_query_resource(__, payload, state, fn) {
284
397
  /** @type {Promise<any> | null} */
285
398
  let promise = null;
286
399
 
287
400
  const get_promise = () => {
288
- return (promise ??= get_response(__, arg, state, fn));
401
+ return (promise ??= get_response(__, payload, state, fn));
402
+ };
403
+
404
+ const populate_hydratable = () => {
405
+ // accessing data properties needs to kick off the work
406
+ // so that it gets seeded in the hydration cache
407
+ // and becomes available on the client
408
+ void (__.id && state.is_in_render && get_promise());
289
409
  };
290
410
 
291
411
  return {
@@ -293,17 +413,29 @@ function create_query_resource(__, arg, state, fn) {
293
413
  catch(onrejected) {
294
414
  return get_promise().catch(onrejected);
295
415
  },
296
- current: undefined,
297
- error: undefined,
416
+ get current() {
417
+ populate_hydratable();
418
+ return undefined;
419
+ },
420
+ get error() {
421
+ populate_hydratable();
422
+ return undefined;
423
+ },
298
424
  /** @type {Promise<any>['finally']} */
299
425
  finally(onfinally) {
300
426
  return get_promise().finally(onfinally);
301
427
  },
302
- loading: true,
303
- ready: false,
428
+ get loading() {
429
+ populate_hydratable();
430
+ return true;
431
+ },
432
+ get ready() {
433
+ populate_hydratable();
434
+ return false;
435
+ },
304
436
  refresh() {
305
- const refresh_context = get_refresh_context(__, 'refresh', arg);
306
- const is_immediate_refresh = !refresh_context.cache[refresh_context.cache_key];
437
+ const refresh_context = get_refresh_context(__, 'refresh', payload);
438
+ const is_immediate_refresh = !refresh_context.cache[refresh_context.payload];
307
439
  const value = is_immediate_refresh ? get_promise() : fn();
308
440
  return update_refresh_value(refresh_context, value, is_immediate_refresh);
309
441
  },
@@ -316,11 +448,11 @@ function create_query_resource(__, arg, state, fn) {
316
448
  'On the server, .run() can only be called in universal `load` functions. Anywhere else, just await the query directly'
317
449
  );
318
450
  }
319
- return get_response(__, arg, state, fn);
451
+ return get_response(__, payload, state, fn);
320
452
  },
321
453
  /** @param {any} value */
322
454
  set(value) {
323
- return update_refresh_value(get_refresh_context(__, 'set', arg), value);
455
+ return update_refresh_value(get_refresh_context(__, 'set', payload), value);
324
456
  },
325
457
  /** @type {Promise<any>['then']} */
326
458
  then(onfulfilled, onrejected) {
@@ -335,16 +467,94 @@ function create_query_resource(__, arg, state, fn) {
335
467
  };
336
468
  }
337
469
 
470
+ /**
471
+ * @param {RemoteQueryLiveInternals} __
472
+ * @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
473
+ * @param {RequestState} state
474
+ * @param {() => Promise<any>} get_first_value
475
+ * @returns {RemoteLiveQuery<any>}
476
+ */
477
+ function create_live_query_resource(__, payload, state, get_first_value) {
478
+ /** @type {Promise<any> | null} */
479
+ let promise = null;
480
+
481
+ const get_promise = () => {
482
+ return (promise ??= get_response(__, payload, state, get_first_value));
483
+ };
484
+
485
+ const populate_hydratable = () => {
486
+ void (__.id && state.is_in_render && get_promise());
487
+ };
488
+
489
+ return {
490
+ /** @type {Promise<any>['catch']} */
491
+ catch(onrejected) {
492
+ return get_promise().catch(onrejected);
493
+ },
494
+ get current() {
495
+ populate_hydratable();
496
+ return undefined;
497
+ },
498
+ get error() {
499
+ populate_hydratable();
500
+ return undefined;
501
+ },
502
+ /** @type {Promise<any>['finally']} */
503
+ finally(onfinally) {
504
+ return get_promise().finally(onfinally);
505
+ },
506
+ get done() {
507
+ populate_hydratable();
508
+ return false;
509
+ },
510
+ get loading() {
511
+ populate_hydratable();
512
+ return true;
513
+ },
514
+ get ready() {
515
+ populate_hydratable();
516
+ return false;
517
+ },
518
+ get connected() {
519
+ populate_hydratable();
520
+ return false;
521
+ },
522
+ reconnect() {
523
+ const reconnects = state.remote.reconnects;
524
+
525
+ if (!reconnects) {
526
+ throw new Error(
527
+ `Cannot call reconnect on query.live '${__.name}' because it is not executed in the context of a command/form remote function`
528
+ );
529
+ }
530
+
531
+ reconnects.set(create_remote_key(__.id, payload), get_promise());
532
+ return Promise.resolve();
533
+ },
534
+ run() {
535
+ throw new Error('Cannot call .run() on a live query on the server');
536
+ },
537
+ /** @type {Promise<any>['then']} */
538
+ then(onfulfilled, onrejected) {
539
+ return get_promise().then(onfulfilled, onrejected);
540
+ },
541
+ get [Symbol.toStringTag]() {
542
+ return 'LiveQueryResource';
543
+ }
544
+ };
545
+ }
546
+
338
547
  // Add batch as a property to the query function
339
548
  Object.defineProperty(query, 'batch', { value: batch, enumerable: true });
549
+ Object.defineProperty(query, 'live', { value: live, enumerable: true });
340
550
 
341
551
  /**
342
552
  * @param {RemoteInternals} __
343
553
  * @param {'set' | 'refresh'} action
344
- * @param {any} [arg]
345
- * @returns {{ __: RemoteInternals; state: any; refreshes: Record<string, Promise<any>>; cache: Record<string, { serialize: boolean; data: any }>; refreshes_key: string; cache_key: string }}
554
+ * @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
555
+ * @returns {{ __: RemoteInternals; state: any; refreshes: Map<string, Promise<any>>; cache: Record<string, { serialize: boolean; data: any }>; refreshes_key: string; payload: string }}
346
556
  */
347
- function get_refresh_context(__, action, arg) {
557
+ function get_refresh_context(__, action, payload) {
348
558
  const { state } = get_request_store();
349
559
  const { refreshes } = state.remote;
350
560
 
@@ -356,32 +566,36 @@ function get_refresh_context(__, action, arg) {
356
566
  }
357
567
 
358
568
  const cache = get_cache(__, state);
359
- const cache_key = stringify_remote_arg(arg, state.transport);
360
- const refreshes_key = create_remote_key(__.id, cache_key);
569
+ const refreshes_key = create_remote_key(__.id, payload);
361
570
 
362
- return { __, state, refreshes, refreshes_key, cache, cache_key };
571
+ return { __, state, refreshes, refreshes_key, cache, payload };
363
572
  }
364
573
 
365
574
  /**
366
- * @param {{ __: RemoteInternals; refreshes: Record<string, Promise<any>>; cache: Record<string, { serialize: boolean; data: any }>; refreshes_key: string; cache_key: string }} context
575
+ * @param {{ __: RemoteInternals; refreshes: Map<string, Promise<any>>; cache: Record<string, { serialize: boolean; data: any }>; refreshes_key: string; payload: string }} context
367
576
  * @param {any} value
368
577
  * @param {boolean} [is_immediate_refresh=false]
369
578
  * @returns {Promise<void>}
370
579
  */
371
580
  function update_refresh_value(
372
- { __, refreshes, refreshes_key, cache, cache_key },
581
+ { __, refreshes, refreshes_key, cache, payload },
373
582
  value,
374
583
  is_immediate_refresh = false
375
584
  ) {
376
585
  const promise = Promise.resolve(value);
377
586
 
378
587
  if (!is_immediate_refresh) {
379
- cache[cache_key] = { serialize: true, data: promise };
588
+ cache[payload] = { serialize: true, data: promise };
380
589
  }
381
590
 
382
591
  if (__.id) {
383
- refreshes[refreshes_key] = promise;
592
+ refreshes.set(refreshes_key, promise);
384
593
  }
385
594
 
386
- return promise.then(noop, noop);
595
+ promise.catch(noop);
596
+
597
+ // we return an immediately-resolving promise so that the `refresh()` signature is consistent,
598
+ // but it doesn't delay anything if awaited inside a command. this way, people aren't
599
+ // penalised if they do `await q1.refresh(); await q2.refresh()`
600
+ return Promise.resolve();
387
601
  }