-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
feat: add remote function query.stream
#14292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1affeca
b70a394
bd0c519
60fb389
41fad93
4819f97
a955c16
1b5f453
5da377d
e036a27
afdae97
6434cf3
5ed076e
3b54a4c
96ea81c
9656a7f
6665ed6
28a3564
531225c
e630de7
cbfa1cf
aa7cd56
910125c
476ce72
2ca6eb1
3447d75
dfa4cb4
d51c20c
44f60b8
d8d02bc
40de6f1
a649cf0
ac02bc1
74d0587
7254df5
cead45e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@sveltejs/kit': minor | ||
--- | ||
|
||
feat: add remote function `query.stream` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/** @import { RemoteQuery, RemoteQueryFunction } from '@sveltejs/kit' */ | ||
/** @import { RemoteQuery, RemoteQueryFunction, RemoteQueryStream, RemoteQueryStreamFunction } from '@sveltejs/kit' */ | ||
/** @import { RemoteInfo, MaybePromise } from 'types' */ | ||
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */ | ||
import { get_request_store } from '@sveltejs/kit/internal/server'; | ||
|
@@ -266,5 +266,120 @@ function batch(validate_or_fn, maybe_fn) { | |
return wrapper; | ||
} | ||
|
||
// Add batch as a property to the query function | ||
/** | ||
* Creates a streaming remote query. When called from the browser, the generator function will be invoked on the server and values will be streamed via Server-Sent Events. | ||
* | ||
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query.stream) for full documentation. | ||
* | ||
* @template Output | ||
* @overload | ||
* @param {() => Generator<Output, void, unknown> | AsyncGenerator<Output, void, unknown>} fn | ||
* @returns {RemoteQueryStreamFunction<void, Output>} | ||
* @since 2.36 | ||
*/ | ||
/** | ||
* Creates a streaming remote query. When called from the browser, the generator function will be invoked on the server and values will be streamed via Server-Sent Events. | ||
* | ||
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query.stream) for full documentation. | ||
* | ||
* @template Input | ||
* @template Output | ||
* @overload | ||
* @param {'unchecked'} validate | ||
* @param {(arg: Input) => Generator<Output, void, unknown> | AsyncGenerator<Output, void, unknown>} fn | ||
* @returns {RemoteQueryStreamFunction<Input, Output>} | ||
* @since 2.36 | ||
*/ | ||
/** | ||
* Creates a streaming remote query. When called from the browser, the generator function will be invoked on the server and values will be streamed via Server-Sent Events. | ||
* | ||
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query.stream) for full documentation. | ||
* | ||
* @template {StandardSchemaV1} Schema | ||
* @template Output | ||
* @overload | ||
* @param {Schema} schema | ||
* @param {(arg: StandardSchemaV1.InferOutput<Schema>) => Generator<Output, void, unknown> | AsyncGenerator<Output, void, unknown>} fn | ||
* @returns {RemoteQueryStreamFunction<StandardSchemaV1.InferInput<Schema>, Output>} | ||
* @since 2.36 | ||
*/ | ||
/** | ||
* @template Input | ||
* @template Output | ||
* @param {any} validate_or_fn | ||
* @param {(arg?: Input) => Generator<Output, void, unknown> | AsyncGenerator<Output, void, unknown>} [maybe_fn] | ||
* @returns {RemoteQueryStreamFunction<Input, Output>} | ||
* @since 2.36 | ||
*/ | ||
/*@__NO_SIDE_EFFECTS__*/ | ||
function stream(validate_or_fn, maybe_fn) { | ||
/** @type {(arg?: Input) => Generator<Output, void, unknown> | AsyncGenerator<Output, void, unknown>} */ | ||
const fn = maybe_fn ?? validate_or_fn; | ||
|
||
/** @type {(arg?: any) => MaybePromise<Input>} */ | ||
const validate = create_validator(validate_or_fn, maybe_fn); | ||
|
||
/** @type {RemoteInfo} */ | ||
const __ = { type: 'query_stream', id: '', name: '' }; | ||
|
||
/** @type {RemoteQueryStreamFunction<Input, Output> & { __: RemoteInfo }} */ | ||
const wrapper = (/** @type {Input} */ arg) => { | ||
if (prerendering) { | ||
throw new Error( | ||
`Cannot call query.stream '${__.name}' while prerendering, as prerendered pages need static data. Use 'prerender' from $app/server instead` | ||
); | ||
} | ||
|
||
const { event, state } = get_request_store(); | ||
|
||
/** @type {IteratorResult<Output> | undefined} */ | ||
let first_value; | ||
|
||
const promise = (async () => { | ||
// We only care about the generator when doing a remote request | ||
if (event.isRemoteRequest) return; | ||
|
||
const generator = await run_remote_function(event, state, false, arg, validate, fn); | ||
first_value = await generator.next(); | ||
await generator.return(); | ||
return first_value.done ? undefined : first_value.value; | ||
})(); | ||
|
||
// Catch promise to avoid unhandled rejection | ||
promise.catch(() => {}); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
Object.assign(promise, { | ||
async *[Symbol.asyncIterator]() { | ||
if (event.isRemoteRequest) { | ||
const generator = await run_remote_function(event, state, false, arg, validate, fn); | ||
yield* generator; | ||
} else { | ||
// TODO how would we subscribe to the stream on the server while deduplicating calls and knowing when to stop? | ||
throw new Error( | ||
'Cannot iterate over a stream on the server. This restriction may be lifted in a future version.' | ||
); | ||
} | ||
Comment on lines
+358
to
+361
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that we no longer deduplicate, is this still a concern? If you for-awaited an infinite async iterable during render then you'd have the same problem whether it's the client or the server, and the solution is 'don't do that'. So I'm not sure if we need this restriction There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. true, let me take a look |
||
}, | ||
get error() { | ||
return undefined; | ||
}, | ||
get ready() { | ||
return !!first_value; | ||
}, | ||
get current() { | ||
return first_value?.value; | ||
} | ||
}); | ||
|
||
return /** @type {RemoteQueryStream<Output>} */ (promise); | ||
}; | ||
|
||
Object.defineProperty(wrapper, '__', { value: __ }); | ||
|
||
return wrapper; | ||
} | ||
|
||
// Add batch and stream as properties to the query function | ||
Object.defineProperty(query, 'batch', { value: batch, enumerable: true }); | ||
Object.defineProperty(query, 'stream', { value: stream, enumerable: true }); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
export { command } from './command.svelte.js'; | ||
export { form } from './form.svelte.js'; | ||
export { prerender } from './prerender.svelte.js'; | ||
export { query, query_batch } from './query.svelte.js'; | ||
export { query, query_batch, query_stream } from './query.svelte.js'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ReadableStream
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream - the comment is meant as "these are not two network requests, it's one because they share the same stream instance"
I guess I could've written that instead ... 😅