@sveltejs/kit 2.58.0 → 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.
- package/package.json +1 -1
- package/src/exports/internal/remote-functions.js +1 -1
- package/src/exports/public.d.ts +77 -10
- package/src/runtime/app/server/remote/command.js +7 -6
- package/src/runtime/app/server/remote/form.js +2 -1
- package/src/runtime/app/server/remote/query.js +304 -75
- package/src/runtime/app/server/remote/requested.js +109 -20
- package/src/runtime/app/server/remote/shared.js +84 -9
- package/src/runtime/client/client.js +92 -62
- package/src/runtime/client/ndjson.js +42 -0
- package/src/runtime/client/remote-functions/command.svelte.js +6 -1
- package/src/runtime/client/remote-functions/form.svelte.js +23 -6
- package/src/runtime/client/remote-functions/index.js +3 -1
- package/src/runtime/client/remote-functions/query-batch.svelte.js +105 -0
- package/src/runtime/client/remote-functions/query-live.svelte.js +636 -0
- package/src/runtime/client/remote-functions/query.svelte.js +15 -115
- package/src/runtime/client/remote-functions/shared.svelte.js +76 -23
- package/src/runtime/form-utils.js +31 -10
- package/src/runtime/server/page/load_data.js +4 -2
- package/src/runtime/server/page/render.js +4 -1
- package/src/runtime/server/remote.js +117 -9
- package/src/runtime/server/respond.js +1 -0
- package/src/runtime/shared.js +2 -2
- package/src/runtime/utils.js +0 -1
- package/src/types/internal.d.ts +45 -13
- package/src/version.js +1 -1
- package/types/index.d.ts +137 -15
- package/types/index.d.ts.map +7 -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 {
|
|
8
|
-
|
|
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.
|
|
@@ -98,6 +104,110 @@ export function query(validate_or_fn, maybe_fn) {
|
|
|
98
104
|
return wrapper;
|
|
99
105
|
}
|
|
100
106
|
|
|
107
|
+
/**
|
|
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>}
|
|
116
|
+
*/
|
|
117
|
+
/**
|
|
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>}
|
|
139
|
+
*/
|
|
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;
|
|
144
|
+
|
|
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
|
+
}
|
|
167
|
+
|
|
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;
|
|
209
|
+
}
|
|
210
|
+
|
|
101
211
|
/**
|
|
102
212
|
* Creates a batch query function that collects multiple calls and executes them in a single request
|
|
103
213
|
*
|
|
@@ -140,11 +250,81 @@ function batch(validate_or_fn, maybe_fn) {
|
|
|
140
250
|
/** @type {(arg?: any) => MaybePromise<Input>} */
|
|
141
251
|
const validate = create_validator(validate_or_fn, maybe_fn);
|
|
142
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
|
+
|
|
143
322
|
/** @type {RemoteQueryBatchInternals} */
|
|
144
323
|
const __ = {
|
|
145
324
|
type: 'query_batch',
|
|
146
325
|
id: '',
|
|
147
326
|
name: '',
|
|
327
|
+
validate,
|
|
148
328
|
run: async (args, options) => {
|
|
149
329
|
const { event, state } = get_request_store();
|
|
150
330
|
|
|
@@ -175,12 +355,14 @@ function batch(validate_or_fn, maybe_fn) {
|
|
|
175
355
|
);
|
|
176
356
|
}
|
|
177
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));
|
|
178
363
|
}
|
|
179
364
|
};
|
|
180
365
|
|
|
181
|
-
/** @type {Map<string, { arg: any, resolvers: Array<{resolve: (value: any) => void, reject: (error: any) => void}> }>} */
|
|
182
|
-
let batching = new Map();
|
|
183
|
-
|
|
184
366
|
/** @type {RemoteQueryFunction<Input, Output> & { __: RemoteQueryBatchInternals }} */
|
|
185
367
|
const wrapper = (arg) => {
|
|
186
368
|
if (prerendering) {
|
|
@@ -189,69 +371,14 @@ function batch(validate_or_fn, maybe_fn) {
|
|
|
189
371
|
);
|
|
190
372
|
}
|
|
191
373
|
|
|
192
|
-
const {
|
|
193
|
-
// batched queries do not participate in `requested(...)`, so `arg` is
|
|
194
|
-
// always the raw user-supplied value and can be used for the cache key directly
|
|
374
|
+
const { state } = get_request_store();
|
|
195
375
|
const payload = stringify_remote_arg(arg, state.transport);
|
|
196
376
|
|
|
197
|
-
return create_query_resource(__, payload, state, () =>
|
|
377
|
+
return create_query_resource(__, payload, state, () =>
|
|
198
378
|
// Collect all the calls to the same query in the same macrotask,
|
|
199
379
|
// then execute them as one backend request.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (entry) {
|
|
204
|
-
entry.resolvers.push({ resolve, reject });
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
batching.set(payload, {
|
|
209
|
-
arg,
|
|
210
|
-
resolvers: [{ resolve, reject }]
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
if (batching.size > 1) return;
|
|
214
|
-
|
|
215
|
-
setTimeout(async () => {
|
|
216
|
-
const batched = batching;
|
|
217
|
-
batching = new Map();
|
|
218
|
-
const entries = Array.from(batched.values());
|
|
219
|
-
const args = entries.map((entry) => entry.arg);
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
return await run_remote_function(
|
|
223
|
-
event,
|
|
224
|
-
state,
|
|
225
|
-
false,
|
|
226
|
-
async () => Promise.all(args.map(validate)),
|
|
227
|
-
async (input) => {
|
|
228
|
-
const get_result = await fn(input);
|
|
229
|
-
|
|
230
|
-
for (let i = 0; i < entries.length; i++) {
|
|
231
|
-
try {
|
|
232
|
-
const result = get_result(input[i], i);
|
|
233
|
-
|
|
234
|
-
for (const resolver of entries[i].resolvers) {
|
|
235
|
-
resolver.resolve(result);
|
|
236
|
-
}
|
|
237
|
-
} catch (error) {
|
|
238
|
-
for (const resolver of entries[i].resolvers) {
|
|
239
|
-
resolver.reject(error);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
);
|
|
245
|
-
} catch (error) {
|
|
246
|
-
for (const entry of batched.values()) {
|
|
247
|
-
for (const resolver of entry.resolvers) {
|
|
248
|
-
resolver.reject(error);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}, 0);
|
|
253
|
-
});
|
|
254
|
-
});
|
|
380
|
+
enqueue(payload, () => validate(arg))
|
|
381
|
+
);
|
|
255
382
|
};
|
|
256
383
|
|
|
257
384
|
Object.defineProperty(wrapper, '__', { value: __ });
|
|
@@ -274,19 +401,38 @@ function create_query_resource(__, payload, state, fn) {
|
|
|
274
401
|
return (promise ??= get_response(__, payload, state, fn));
|
|
275
402
|
};
|
|
276
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());
|
|
409
|
+
};
|
|
410
|
+
|
|
277
411
|
return {
|
|
278
412
|
/** @type {Promise<any>['catch']} */
|
|
279
413
|
catch(onrejected) {
|
|
280
414
|
return get_promise().catch(onrejected);
|
|
281
415
|
},
|
|
282
|
-
current
|
|
283
|
-
|
|
416
|
+
get current() {
|
|
417
|
+
populate_hydratable();
|
|
418
|
+
return undefined;
|
|
419
|
+
},
|
|
420
|
+
get error() {
|
|
421
|
+
populate_hydratable();
|
|
422
|
+
return undefined;
|
|
423
|
+
},
|
|
284
424
|
/** @type {Promise<any>['finally']} */
|
|
285
425
|
finally(onfinally) {
|
|
286
426
|
return get_promise().finally(onfinally);
|
|
287
427
|
},
|
|
288
|
-
loading
|
|
289
|
-
|
|
428
|
+
get loading() {
|
|
429
|
+
populate_hydratable();
|
|
430
|
+
return true;
|
|
431
|
+
},
|
|
432
|
+
get ready() {
|
|
433
|
+
populate_hydratable();
|
|
434
|
+
return false;
|
|
435
|
+
},
|
|
290
436
|
refresh() {
|
|
291
437
|
const refresh_context = get_refresh_context(__, 'refresh', payload);
|
|
292
438
|
const is_immediate_refresh = !refresh_context.cache[refresh_context.payload];
|
|
@@ -321,14 +467,92 @@ function create_query_resource(__, payload, state, fn) {
|
|
|
321
467
|
};
|
|
322
468
|
}
|
|
323
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
|
+
|
|
324
547
|
// Add batch as a property to the query function
|
|
325
548
|
Object.defineProperty(query, 'batch', { value: batch, enumerable: true });
|
|
549
|
+
Object.defineProperty(query, 'live', { value: live, enumerable: true });
|
|
326
550
|
|
|
327
551
|
/**
|
|
328
552
|
* @param {RemoteInternals} __
|
|
329
553
|
* @param {'set' | 'refresh'} action
|
|
330
|
-
* @param {string} payload — the stringified raw argument
|
|
331
|
-
* @returns {{ __: RemoteInternals; state: any; refreshes:
|
|
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 }}
|
|
332
556
|
*/
|
|
333
557
|
function get_refresh_context(__, action, payload) {
|
|
334
558
|
const { state } = get_request_store();
|
|
@@ -348,7 +572,7 @@ function get_refresh_context(__, action, payload) {
|
|
|
348
572
|
}
|
|
349
573
|
|
|
350
574
|
/**
|
|
351
|
-
* @param {{ __: RemoteInternals; refreshes:
|
|
575
|
+
* @param {{ __: RemoteInternals; refreshes: Map<string, Promise<any>>; cache: Record<string, { serialize: boolean; data: any }>; refreshes_key: string; payload: string }} context
|
|
352
576
|
* @param {any} value
|
|
353
577
|
* @param {boolean} [is_immediate_refresh=false]
|
|
354
578
|
* @returns {Promise<void>}
|
|
@@ -365,8 +589,13 @@ function update_refresh_value(
|
|
|
365
589
|
}
|
|
366
590
|
|
|
367
591
|
if (__.id) {
|
|
368
|
-
refreshes
|
|
592
|
+
refreshes.set(refreshes_key, promise);
|
|
369
593
|
}
|
|
370
594
|
|
|
371
|
-
|
|
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();
|
|
372
601
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** @import { RemoteQueryFunction, RequestedResult } from '@sveltejs/kit' */
|
|
2
|
-
/** @import { MaybePromise,
|
|
1
|
+
/** @import { RemoteLiveQuery, RemoteLiveQueryFunction, RemoteQuery, RemoteQueryFunction, RequestedResult, QueryRequestedResult, LiveQueryRequestedResult } from '@sveltejs/kit' */
|
|
2
|
+
/** @import { MaybePromise, RemoteAnyQueryInternals } from 'types' */
|
|
3
3
|
import { get_request_store } from '@sveltejs/kit/internal/server';
|
|
4
4
|
import { create_remote_key, parse_remote_arg } from '../../../shared.js';
|
|
5
5
|
import { noop } from '../../../../utils/functions.js';
|
|
@@ -30,30 +30,104 @@ import { noop } from '../../../../utils/functions.js';
|
|
|
30
30
|
*
|
|
31
31
|
* As a shorthand for the above, you can also call `refreshAll` on the result:
|
|
32
32
|
*
|
|
33
|
+
* @example
|
|
33
34
|
* ```ts
|
|
34
35
|
* import { requested } from '$app/server';
|
|
35
36
|
*
|
|
36
37
|
* await requested(getPost, 5).refreshAll();
|
|
37
38
|
* ```
|
|
38
39
|
*
|
|
40
|
+
* Works with `query.batch` as well — refreshes for individual entries are
|
|
41
|
+
* collected into a single batched call.
|
|
42
|
+
*
|
|
43
|
+
* For live queries, the same applies, but with `reconnect` and `reconnectAll`.
|
|
44
|
+
*
|
|
39
45
|
* @template Input
|
|
40
46
|
* @template Output
|
|
41
47
|
* @template [Validated=Input]
|
|
48
|
+
* @overload
|
|
42
49
|
* @param {RemoteQueryFunction<Input, Output, Validated>} query
|
|
43
50
|
* @param {number} limit
|
|
51
|
+
* @returns {QueryRequestedResult<Validated, Output>}
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* In the context of a remote `command` or `form` request, returns an iterable
|
|
55
|
+
* of `{ arg, query }` entries for the reconnects requested by the client, up to
|
|
56
|
+
* the supplied `limit`. Each `query` is a `RemoteLiveQuery` bound to the original
|
|
57
|
+
* client-side cache key, so `reconnect()` propagates correctly even when
|
|
58
|
+
* the query's schema transforms the input. `arg` is the *validated* argument.
|
|
59
|
+
*
|
|
60
|
+
* Arguments that fail validation or exceed `limit` are recorded as failures in
|
|
61
|
+
* the response to the client.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* import { requested } from '$app/server';
|
|
66
|
+
*
|
|
67
|
+
* for (const { query } of requested(getPost, 5)) {
|
|
68
|
+
* void query.reconnect();
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* As a shorthand, you can also call `reconnectAll` on the result:
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* import { requested } from '$app/server';
|
|
77
|
+
*
|
|
78
|
+
* await requested(getPost, 5).reconnectAll();
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @template Input
|
|
82
|
+
* @template Output
|
|
83
|
+
* @template [Validated=Input]
|
|
84
|
+
* @overload
|
|
85
|
+
* @param {RemoteLiveQueryFunction<Input, Output, Validated>} query
|
|
86
|
+
* @param {number} limit
|
|
87
|
+
* @returns {LiveQueryRequestedResult<Validated, Output>}
|
|
88
|
+
*/
|
|
89
|
+
/**
|
|
90
|
+
* @template Input
|
|
91
|
+
* @template Output
|
|
92
|
+
* @template [Validated=Input]
|
|
93
|
+
* @param {RemoteQueryFunction<Input, Output, Validated> | RemoteLiveQueryFunction<Input, Output, Validated>} query
|
|
94
|
+
* @param {number} limit
|
|
44
95
|
* @returns {RequestedResult<Validated, Output>}
|
|
45
96
|
*/
|
|
46
97
|
export function requested(query, limit) {
|
|
47
98
|
const { state } = get_request_store();
|
|
48
|
-
const internals = /** @type {
|
|
99
|
+
const internals = /** @type {RemoteAnyQueryInternals | undefined} */ (
|
|
100
|
+
/** @type {any} */ (query).__
|
|
101
|
+
);
|
|
49
102
|
|
|
50
|
-
if (
|
|
51
|
-
|
|
103
|
+
if (
|
|
104
|
+
internals?.type !== 'query' &&
|
|
105
|
+
internals?.type !== 'query_batch' &&
|
|
106
|
+
internals?.type !== 'query_live'
|
|
107
|
+
) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
'requested(...) expects a query function created with query(...), query.batch(...), or query.live(...)'
|
|
110
|
+
);
|
|
52
111
|
}
|
|
53
112
|
|
|
113
|
+
// narrow-stable alias so generator closures below don't lose the narrowing
|
|
114
|
+
const __ = internals;
|
|
115
|
+
|
|
54
116
|
const requested = state.remote.requested;
|
|
55
|
-
const payloads = requested?.get(
|
|
56
|
-
|
|
117
|
+
const payloads = requested?.get(__.id) ?? [];
|
|
118
|
+
// note: don't initialize these maps here -- they will be initialized by the
|
|
119
|
+
// command/form wrapper when we enter them, and if we initialize them here
|
|
120
|
+
// we will enable requested(...) in contexts where it shouldn't be allowed,
|
|
121
|
+
// such as load functions or other server functions
|
|
122
|
+
const refreshes = state.remote.refreshes;
|
|
123
|
+
const reconnects = state.remote.reconnects;
|
|
124
|
+
const store = __.type === 'query_live' ? reconnects : refreshes;
|
|
125
|
+
|
|
126
|
+
if (!store) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
'requested(...) can only be called in the context of a command/form remote function'
|
|
129
|
+
);
|
|
130
|
+
}
|
|
57
131
|
const [selected, skipped] = split_limit(payloads, limit);
|
|
58
132
|
|
|
59
133
|
/**
|
|
@@ -64,34 +138,34 @@ export function requested(query, limit) {
|
|
|
64
138
|
const promise = Promise.reject(error);
|
|
65
139
|
promise.catch(noop);
|
|
66
140
|
|
|
67
|
-
const key = create_remote_key(
|
|
68
|
-
|
|
141
|
+
const key = create_remote_key(__.id, payload);
|
|
142
|
+
store.set(key, promise);
|
|
69
143
|
};
|
|
70
144
|
|
|
71
145
|
for (const payload of skipped) {
|
|
72
146
|
record_failure(
|
|
73
147
|
payload,
|
|
74
148
|
new Error(
|
|
75
|
-
`Requested refresh was rejected because it exceeded requested(${
|
|
149
|
+
`Requested refresh was rejected because it exceeded requested(${__.name}, ${limit}) limit`
|
|
76
150
|
)
|
|
77
151
|
);
|
|
78
152
|
}
|
|
79
153
|
|
|
80
|
-
|
|
154
|
+
const result = {
|
|
81
155
|
*[Symbol.iterator]() {
|
|
82
156
|
for (const payload of selected) {
|
|
83
157
|
try {
|
|
84
158
|
const parsed = parse_remote_arg(payload, state.transport);
|
|
85
|
-
const validated =
|
|
159
|
+
const validated = __.validate(parsed);
|
|
86
160
|
|
|
87
161
|
if (is_thenable(validated)) {
|
|
88
162
|
throw new Error(
|
|
89
163
|
// TODO improve
|
|
90
|
-
`requested(${
|
|
164
|
+
`requested(${__.name}, ${limit}) cannot be used with synchronous iteration because the query validator is async. Use \`for await ... of\` instead`
|
|
91
165
|
);
|
|
92
166
|
}
|
|
93
167
|
|
|
94
|
-
yield { arg: validated, query:
|
|
168
|
+
yield { arg: validated, query: __.bind(payload, validated) };
|
|
95
169
|
} catch (error) {
|
|
96
170
|
record_failure(payload, error);
|
|
97
171
|
continue;
|
|
@@ -102,20 +176,35 @@ export function requested(query, limit) {
|
|
|
102
176
|
yield* race_all(selected, async (payload) => {
|
|
103
177
|
try {
|
|
104
178
|
const parsed = parse_remote_arg(payload, state.transport);
|
|
105
|
-
const validated = await
|
|
106
|
-
return { arg: validated, query:
|
|
179
|
+
const validated = await __.validate(parsed);
|
|
180
|
+
return { arg: validated, query: __.bind(payload, validated) };
|
|
107
181
|
} catch (error) {
|
|
108
182
|
record_failure(payload, error);
|
|
109
|
-
throw new Error(`Skipping ${
|
|
183
|
+
throw new Error(`Skipping ${__.name}(${payload})`, { cause: error });
|
|
110
184
|
}
|
|
111
185
|
});
|
|
112
186
|
},
|
|
113
187
|
async refreshAll() {
|
|
114
|
-
|
|
115
|
-
|
|
188
|
+
if (__.type === 'query_live') {
|
|
189
|
+
throw new Error('refreshAll() is invalid for live queries. Use reconnectAll() instead.');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for await (const { query } of result) {
|
|
193
|
+
void (/** @type {RemoteQuery<Output>} */ (query).refresh());
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
async reconnectAll() {
|
|
197
|
+
if (__.type !== 'query_live') {
|
|
198
|
+
throw new Error('reconnectAll() is invalid for regular queries. Use refreshAll() instead.');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for await (const { query } of result) {
|
|
202
|
+
void (/** @type {RemoteLiveQuery<Output>} */ (query).reconnect());
|
|
116
203
|
}
|
|
117
204
|
}
|
|
118
205
|
};
|
|
206
|
+
|
|
207
|
+
return /** @type {RequestedResult<Validated, Output>} */ (/** @type {unknown} */ (result));
|
|
119
208
|
}
|
|
120
209
|
|
|
121
210
|
/**
|
|
@@ -163,7 +252,7 @@ async function* race_all(array, fn) {
|
|
|
163
252
|
value: result
|
|
164
253
|
}));
|
|
165
254
|
|
|
166
|
-
promise.catch(
|
|
255
|
+
promise.catch(() => pending.delete(promise));
|
|
167
256
|
pending.add(promise);
|
|
168
257
|
}
|
|
169
258
|
|