@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.
- package/package.json +2 -2
- package/src/exports/internal/remote-functions.js +1 -1
- package/src/exports/public.d.ts +107 -16
- package/src/runtime/app/paths/client.js +7 -0
- package/src/runtime/app/paths/server.js +7 -0
- 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/prerender.js +3 -4
- package/src/runtime/app/server/remote/query.js +327 -113
- package/src/runtime/app/server/remote/requested.js +127 -32
- package/src/runtime/app/server/remote/shared.js +89 -20
- 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 +8 -3
- package/src/runtime/client/remote-functions/form.svelte.js +24 -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 +48 -148
- package/src/runtime/client/remote-functions/shared.svelte.js +76 -23
- package/src/runtime/form-utils.js +33 -12
- package/src/runtime/server/page/index.js +26 -16
- package/src/runtime/server/page/load_data.js +4 -2
- package/src/runtime/server/page/render.js +21 -18
- package/src/runtime/server/remote.js +117 -9
- package/src/runtime/server/respond.js +11 -8
- package/src/runtime/server/utils.js +10 -0
- package/src/runtime/shared.js +3 -3
- package/src/runtime/utils.js +0 -1
- package/src/types/internal.d.ts +54 -14
- package/src/utils/page_nodes.js +1 -0
- package/src/utils/url.js +3 -3
- package/src/version.js +1 -1
- package/types/index.d.ts +182 -30
- package/types/index.d.ts.map +8 -4
|
@@ -1,53 +1,133 @@
|
|
|
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';
|
|
6
|
-
import { mark_argument_validated } from './query.js';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* In the context of a remote `command` or `form` request, returns an iterable
|
|
10
|
-
* of the
|
|
11
|
-
*
|
|
9
|
+
* of `{ arg, query }` entries for the refreshes requested by the client, up to
|
|
10
|
+
* the supplied `limit`. Each `query` is a `RemoteQuery` bound to the original
|
|
11
|
+
* client-side cache key, so `refresh()` / `set()` propagate correctly even when
|
|
12
|
+
* the query's schema transforms the input. `arg` is the *validated* argument,
|
|
13
|
+
* i.e. the value after the schema has run (so `InferOutput<Schema>` for queries
|
|
14
|
+
* declared with a Standard Schema).
|
|
15
|
+
*
|
|
16
|
+
* Arguments that fail validation or exceed `limit` are recorded as failures in
|
|
12
17
|
* the response to the client.
|
|
13
18
|
*
|
|
14
19
|
* @example
|
|
15
20
|
* ```ts
|
|
16
21
|
* import { requested } from '$app/server';
|
|
17
22
|
*
|
|
18
|
-
* for (const arg of requested(getPost, 5)) {
|
|
19
|
-
* //
|
|
20
|
-
* //
|
|
21
|
-
* //
|
|
22
|
-
* void
|
|
23
|
+
* for (const { arg, query } of requested(getPost, 5)) {
|
|
24
|
+
* // `arg` is the validated argument; `query` is bound to the client's
|
|
25
|
+
* // cache key. It's safe to throw away this promise -- SvelteKit will
|
|
26
|
+
* // await it and forward any errors to the client.
|
|
27
|
+
* void query.refresh();
|
|
23
28
|
* }
|
|
24
29
|
* ```
|
|
25
30
|
*
|
|
26
31
|
* As a shorthand for the above, you can also call `refreshAll` on the result:
|
|
27
32
|
*
|
|
33
|
+
* @example
|
|
28
34
|
* ```ts
|
|
29
35
|
* import { requested } from '$app/server';
|
|
30
36
|
*
|
|
31
37
|
* await requested(getPost, 5).refreshAll();
|
|
32
38
|
* ```
|
|
33
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
|
+
*
|
|
45
|
+
* @template Input
|
|
46
|
+
* @template Output
|
|
47
|
+
* @template [Validated=Input]
|
|
48
|
+
* @overload
|
|
49
|
+
* @param {RemoteQueryFunction<Input, Output, Validated>} query
|
|
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
|
+
/**
|
|
34
90
|
* @template Input
|
|
35
91
|
* @template Output
|
|
36
|
-
* @
|
|
37
|
-
* @param {
|
|
38
|
-
* @
|
|
92
|
+
* @template [Validated=Input]
|
|
93
|
+
* @param {RemoteQueryFunction<Input, Output, Validated> | RemoteLiveQueryFunction<Input, Output, Validated>} query
|
|
94
|
+
* @param {number} limit
|
|
95
|
+
* @returns {RequestedResult<Validated, Output>}
|
|
39
96
|
*/
|
|
40
|
-
export function requested(query, limit
|
|
97
|
+
export function requested(query, limit) {
|
|
41
98
|
const { state } = get_request_store();
|
|
42
|
-
const internals = /** @type {
|
|
99
|
+
const internals = /** @type {RemoteAnyQueryInternals | undefined} */ (
|
|
100
|
+
/** @type {any} */ (query).__
|
|
101
|
+
);
|
|
43
102
|
|
|
44
|
-
if (
|
|
45
|
-
|
|
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
|
+
);
|
|
46
111
|
}
|
|
47
112
|
|
|
113
|
+
// narrow-stable alias so generator closures below don't lose the narrowing
|
|
114
|
+
const __ = internals;
|
|
115
|
+
|
|
48
116
|
const requested = state.remote.requested;
|
|
49
|
-
const payloads = requested?.get(
|
|
50
|
-
|
|
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
|
+
}
|
|
51
131
|
const [selected, skipped] = split_limit(payloads, limit);
|
|
52
132
|
|
|
53
133
|
/**
|
|
@@ -58,34 +138,34 @@ export function requested(query, limit = Infinity) {
|
|
|
58
138
|
const promise = Promise.reject(error);
|
|
59
139
|
promise.catch(noop);
|
|
60
140
|
|
|
61
|
-
const key = create_remote_key(
|
|
62
|
-
|
|
141
|
+
const key = create_remote_key(__.id, payload);
|
|
142
|
+
store.set(key, promise);
|
|
63
143
|
};
|
|
64
144
|
|
|
65
145
|
for (const payload of skipped) {
|
|
66
146
|
record_failure(
|
|
67
147
|
payload,
|
|
68
148
|
new Error(
|
|
69
|
-
`Requested refresh was rejected because it exceeded requested(${
|
|
149
|
+
`Requested refresh was rejected because it exceeded requested(${__.name}, ${limit}) limit`
|
|
70
150
|
)
|
|
71
151
|
);
|
|
72
152
|
}
|
|
73
153
|
|
|
74
|
-
|
|
154
|
+
const result = {
|
|
75
155
|
*[Symbol.iterator]() {
|
|
76
156
|
for (const payload of selected) {
|
|
77
157
|
try {
|
|
78
158
|
const parsed = parse_remote_arg(payload, state.transport);
|
|
79
|
-
const validated =
|
|
159
|
+
const validated = __.validate(parsed);
|
|
80
160
|
|
|
81
161
|
if (is_thenable(validated)) {
|
|
82
162
|
throw new Error(
|
|
83
163
|
// TODO improve
|
|
84
|
-
`requested(${
|
|
164
|
+
`requested(${__.name}, ${limit}) cannot be used with synchronous iteration because the query validator is async. Use \`for await ... of\` instead`
|
|
85
165
|
);
|
|
86
166
|
}
|
|
87
167
|
|
|
88
|
-
yield
|
|
168
|
+
yield { arg: validated, query: __.bind(payload, validated) };
|
|
89
169
|
} catch (error) {
|
|
90
170
|
record_failure(payload, error);
|
|
91
171
|
continue;
|
|
@@ -96,20 +176,35 @@ export function requested(query, limit = Infinity) {
|
|
|
96
176
|
yield* race_all(selected, async (payload) => {
|
|
97
177
|
try {
|
|
98
178
|
const parsed = parse_remote_arg(payload, state.transport);
|
|
99
|
-
const validated = await
|
|
100
|
-
return
|
|
179
|
+
const validated = await __.validate(parsed);
|
|
180
|
+
return { arg: validated, query: __.bind(payload, validated) };
|
|
101
181
|
} catch (error) {
|
|
102
182
|
record_failure(payload, error);
|
|
103
|
-
throw new Error(`Skipping ${
|
|
183
|
+
throw new Error(`Skipping ${__.name}(${payload})`, { cause: error });
|
|
104
184
|
}
|
|
105
185
|
});
|
|
106
186
|
},
|
|
107
187
|
async refreshAll() {
|
|
108
|
-
|
|
109
|
-
|
|
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());
|
|
110
203
|
}
|
|
111
204
|
}
|
|
112
205
|
};
|
|
206
|
+
|
|
207
|
+
return /** @type {RequestedResult<Validated, Output>} */ (/** @type {unknown} */ (result));
|
|
113
208
|
}
|
|
114
209
|
|
|
115
210
|
/**
|
|
@@ -157,7 +252,7 @@ async function* race_all(array, fn) {
|
|
|
157
252
|
value: result
|
|
158
253
|
}));
|
|
159
254
|
|
|
160
|
-
promise.catch(
|
|
255
|
+
promise.catch(() => pending.delete(promise));
|
|
161
256
|
pending.add(promise);
|
|
162
257
|
}
|
|
163
258
|
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/** @import { RequestEvent } from '@sveltejs/kit' */
|
|
2
|
-
/** @import { ServerHooks, MaybePromise, RequestState, RemoteInternals, RequestStore } from 'types' */
|
|
2
|
+
/** @import { ServerHooks, MaybePromise, RequestState, RemoteInternals, RequestStore, RemoteLiveQueryUserFunctionReturnType } from 'types' */
|
|
3
3
|
import { parse } from 'devalue';
|
|
4
4
|
import { error } from '@sveltejs/kit';
|
|
5
5
|
import { with_request_store, get_request_store } from '@sveltejs/kit/internal/server';
|
|
6
6
|
import { noop } from '../../../../utils/functions.js';
|
|
7
|
-
import {
|
|
8
|
-
stringify_remote_arg,
|
|
9
|
-
create_remote_key,
|
|
10
|
-
stringify,
|
|
11
|
-
unfriendly_hydratable
|
|
12
|
-
} from '../../../shared.js';
|
|
7
|
+
import { create_remote_key, stringify, unfriendly_hydratable } from '../../../shared.js';
|
|
13
8
|
|
|
14
9
|
/**
|
|
15
10
|
* @param {any} validate_or_fn
|
|
16
|
-
* @param {(arg?: any) => any} [maybe_fn]
|
|
11
|
+
* @param {((arg?: any) => any) | undefined} [maybe_fn]
|
|
17
12
|
* @returns {(arg?: any) => MaybePromise<any>}
|
|
18
13
|
*/
|
|
19
14
|
export function create_validator(validate_or_fn, maybe_fn) {
|
|
@@ -36,6 +31,7 @@ export function create_validator(validate_or_fn, maybe_fn) {
|
|
|
36
31
|
return async (arg) => {
|
|
37
32
|
// Get event before async validation to ensure it's available in server environments without AsyncLocalStorage, too
|
|
38
33
|
const { event, state } = get_request_store();
|
|
34
|
+
|
|
39
35
|
// access property and call method in one go to preserve potential this context
|
|
40
36
|
const result = await validate_or_fn['~standard'].validate(arg);
|
|
41
37
|
|
|
@@ -68,19 +64,18 @@ export function create_validator(validate_or_fn, maybe_fn) {
|
|
|
68
64
|
*
|
|
69
65
|
* @template {MaybePromise<any>} T
|
|
70
66
|
* @param {RemoteInternals} internals
|
|
71
|
-
* @param {
|
|
67
|
+
* @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
|
|
72
68
|
* @param {RequestState} state
|
|
73
69
|
* @param {() => Promise<T>} get_result
|
|
74
70
|
* @returns {Promise<T>}
|
|
75
71
|
*/
|
|
76
|
-
export async function get_response(internals,
|
|
72
|
+
export async function get_response(internals, payload, state, get_result) {
|
|
77
73
|
// wait a beat, in case `myQuery().set(...)` or `myQuery().refresh()` is immediately called
|
|
78
74
|
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
79
75
|
await 0;
|
|
80
76
|
|
|
81
77
|
const cache = get_cache(internals, state);
|
|
82
|
-
const
|
|
83
|
-
const entry = (cache[key] ??= {
|
|
78
|
+
const entry = (cache[payload] ??= {
|
|
84
79
|
serialize: false,
|
|
85
80
|
data: get_result()
|
|
86
81
|
});
|
|
@@ -88,7 +83,7 @@ export async function get_response(internals, arg, state, get_result) {
|
|
|
88
83
|
entry.serialize ||= !!state.is_in_universal_load;
|
|
89
84
|
|
|
90
85
|
if (state.is_in_render && internals.id) {
|
|
91
|
-
const remote_key = create_remote_key(internals.id,
|
|
86
|
+
const remote_key = create_remote_key(internals.id, payload);
|
|
92
87
|
|
|
93
88
|
Promise.resolve(entry.data)
|
|
94
89
|
.then((value) => {
|
|
@@ -115,17 +110,13 @@ export function parse_remote_response(data, transport) {
|
|
|
115
110
|
}
|
|
116
111
|
|
|
117
112
|
/**
|
|
118
|
-
* Like `with_event` but removes things from `event` you cannot see/call in remote functions, such as `setHeaders`.
|
|
119
|
-
* @template T
|
|
120
113
|
* @param {RequestEvent} event
|
|
121
114
|
* @param {RequestState} state
|
|
122
115
|
* @param {boolean} allow_cookies
|
|
123
|
-
* @
|
|
124
|
-
* @param {(arg?: any) => T} fn
|
|
116
|
+
* @returns {RequestStore}
|
|
125
117
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const store = {
|
|
118
|
+
function derive_remote_function_event(event, state, allow_cookies) {
|
|
119
|
+
return {
|
|
129
120
|
event: {
|
|
130
121
|
...event,
|
|
131
122
|
setHeaders: () => {
|
|
@@ -162,12 +153,90 @@ export async function run_remote_function(event, state, allow_cookies, get_input
|
|
|
162
153
|
is_in_remote_function: true
|
|
163
154
|
}
|
|
164
155
|
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Like `with_event` but removes things from `event` you cannot see/call in remote functions, such as `setHeaders`.
|
|
160
|
+
* @template T
|
|
161
|
+
* @param {RequestEvent} event
|
|
162
|
+
* @param {RequestState} state
|
|
163
|
+
* @param {boolean} allow_cookies
|
|
164
|
+
* @param {() => any} get_input
|
|
165
|
+
* @param {(arg?: any) => T} fn
|
|
166
|
+
*/
|
|
167
|
+
export async function run_remote_function(event, state, allow_cookies, get_input, fn) {
|
|
168
|
+
const store = derive_remote_function_event(event, state, allow_cookies);
|
|
165
169
|
|
|
166
170
|
// In two parts, each with_event, so that runtimes without async local storage can still get the event at the start of the function
|
|
167
171
|
const input = await with_request_store(store, get_input);
|
|
168
172
|
return with_request_store(store, () => fn(input));
|
|
169
173
|
}
|
|
170
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Like `with_event` but removes things from `event` you cannot see/call in remote functions, such as `setHeaders`.
|
|
177
|
+
* @template T
|
|
178
|
+
* @param {RequestEvent} event
|
|
179
|
+
* @param {RequestState} state
|
|
180
|
+
* @param {boolean} allow_cookies
|
|
181
|
+
* @param {() => any} get_input
|
|
182
|
+
* @param {(arg?: any) => RemoteLiveQueryUserFunctionReturnType<T>} fn
|
|
183
|
+
* @param {string} name
|
|
184
|
+
*/
|
|
185
|
+
export async function* run_remote_generator(event, state, allow_cookies, get_input, fn, name) {
|
|
186
|
+
const store = derive_remote_function_event(event, state, allow_cookies);
|
|
187
|
+
|
|
188
|
+
// In two parts, each with_event, so that runtimes without async local storage can still get the event at the start of the function / calls to next
|
|
189
|
+
const input = await with_request_store(store, get_input);
|
|
190
|
+
const source = await with_request_store(store, () => fn(input));
|
|
191
|
+
const iterator = to_iterator(source, name);
|
|
192
|
+
let done = false;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
while (true) {
|
|
196
|
+
// the code of a generator function is basically chopped apart at each
|
|
197
|
+
// yield, and each part is an invocation of `.next`. So, to provide
|
|
198
|
+
// access to the request context in generator functions, we have to
|
|
199
|
+
// provide it to every invocation of `.next`. (It's more obvious that
|
|
200
|
+
// this is necessary with plain iterators.)
|
|
201
|
+
const result = await with_request_store(store, () => iterator.next());
|
|
202
|
+
if (result.done) {
|
|
203
|
+
done = true;
|
|
204
|
+
return result.value;
|
|
205
|
+
}
|
|
206
|
+
yield result.value;
|
|
207
|
+
}
|
|
208
|
+
} finally {
|
|
209
|
+
if (!done && typeof iterator.return === 'function') {
|
|
210
|
+
await with_request_store(store, () => iterator.return?.(undefined));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @template T
|
|
217
|
+
* @param {Awaited<RemoteLiveQueryUserFunctionReturnType<T>>} source
|
|
218
|
+
* @param {string} name
|
|
219
|
+
* @returns {Iterator<T> | AsyncIterator<T>}
|
|
220
|
+
*/
|
|
221
|
+
function to_iterator(source, name) {
|
|
222
|
+
// intentionally using `in` because these could be inherited
|
|
223
|
+
if ('next' in source && typeof source.next === 'function') {
|
|
224
|
+
return source;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (Symbol.asyncIterator in source && typeof source[Symbol.asyncIterator] === 'function') {
|
|
228
|
+
return source[Symbol.asyncIterator]();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (Symbol.iterator in source && typeof source[Symbol.iterator] === 'function') {
|
|
232
|
+
return source[Symbol.iterator]();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
throw new Error(
|
|
236
|
+
`query.live '${name}' must return an Iterator, Iterable, AsyncIterator or AsyncIterable`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
171
240
|
/**
|
|
172
241
|
* @param {RemoteInternals} internals
|
|
173
242
|
* @param {RequestState} state
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** @import { RemoteQueryCacheEntry } from './remote-functions/query.svelte.js' */
|
|
2
|
+
/** @import { RemoteLiveQueryCacheEntry } from './remote-functions/query-live.svelte.js' */
|
|
2
3
|
import { BROWSER, DEV } from 'esm-env';
|
|
3
4
|
import * as svelte from 'svelte';
|
|
4
5
|
import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal';
|
|
@@ -44,6 +45,7 @@ import { compact } from '../../utils/array.js';
|
|
|
44
45
|
import {
|
|
45
46
|
INVALIDATED_PARAM,
|
|
46
47
|
TRAILING_SLASH_PARAM,
|
|
48
|
+
create_remote_key,
|
|
47
49
|
validate_depends,
|
|
48
50
|
validate_load_response
|
|
49
51
|
} from '../shared.js';
|
|
@@ -52,7 +54,7 @@ import { writable } from 'svelte/store';
|
|
|
52
54
|
import { page, update, navigating } from './state.svelte.js';
|
|
53
55
|
import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
|
|
54
56
|
import { noop_span } from '../telemetry/noop.js';
|
|
55
|
-
import {
|
|
57
|
+
import { read_ndjson } from './ndjson.js';
|
|
56
58
|
|
|
57
59
|
export { load_css };
|
|
58
60
|
const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
|
|
@@ -305,6 +307,12 @@ export let pending_invalidate;
|
|
|
305
307
|
*/
|
|
306
308
|
export const query_map = new Map();
|
|
307
309
|
|
|
310
|
+
/**
|
|
311
|
+
* @type {Map<string, Map<string, RemoteLiveQueryCacheEntry<any>>>}
|
|
312
|
+
* A map of id -> payload -> live query internals for all active queries.
|
|
313
|
+
*/
|
|
314
|
+
export const live_query_map = new Map();
|
|
315
|
+
|
|
308
316
|
/**
|
|
309
317
|
* @param {import('./types.js').SvelteKitApp} _app
|
|
310
318
|
* @param {HTMLElement} _target
|
|
@@ -409,12 +417,23 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
|
|
|
409
417
|
discard_load_cache();
|
|
410
418
|
|
|
411
419
|
// Rerun queries
|
|
420
|
+
/** @type {Map<string, Promise<void>>} */
|
|
421
|
+
const live_query_reconnects = new Map();
|
|
412
422
|
if (force_invalidation) {
|
|
413
|
-
query_map.
|
|
414
|
-
|
|
415
|
-
void resource.refresh
|
|
416
|
-
}
|
|
417
|
-
}
|
|
423
|
+
for (const entries of query_map.values()) {
|
|
424
|
+
for (const { resource } of entries.values()) {
|
|
425
|
+
void resource.refresh();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
for (const [query_id, entries] of live_query_map) {
|
|
430
|
+
for (const [payload, { resource }] of entries) {
|
|
431
|
+
const key = create_remote_key(query_id, payload);
|
|
432
|
+
const promise = resource.reconnect();
|
|
433
|
+
promise.catch(noop);
|
|
434
|
+
live_query_reconnects.set(key, promise);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
418
437
|
}
|
|
419
438
|
|
|
420
439
|
if (include_load_functions) {
|
|
@@ -443,12 +462,26 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
|
|
|
443
462
|
reset_invalidation();
|
|
444
463
|
}
|
|
445
464
|
|
|
465
|
+
// only wait for promises that are connected to queries that still exist
|
|
466
|
+
/** @type {Promise<any>[]} */
|
|
467
|
+
const promises = [];
|
|
468
|
+
for (const entries of query_map.values()) {
|
|
469
|
+
for (const { resource } of entries.values()) {
|
|
470
|
+
promises.push(resource);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
for (const [query_id, entries] of live_query_map) {
|
|
474
|
+
for (const payload of entries.keys()) {
|
|
475
|
+
const key = create_remote_key(query_id, payload);
|
|
476
|
+
const promise = live_query_reconnects.get(key);
|
|
477
|
+
if (promise) {
|
|
478
|
+
promises.push(promise);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
446
483
|
// Don't use allSettled yet because it's too new
|
|
447
|
-
await Promise.all(
|
|
448
|
-
[...query_map.values()].flatMap((entries) =>
|
|
449
|
-
[...entries.values()].map(({ resource }) => resource)
|
|
450
|
-
)
|
|
451
|
-
).catch(noop);
|
|
484
|
+
await Promise.all(promises).catch(noop);
|
|
452
485
|
}
|
|
453
486
|
|
|
454
487
|
function reset_invalidation() {
|
|
@@ -485,8 +518,10 @@ function persist_state() {
|
|
|
485
518
|
* @param {{}} [nav_token]
|
|
486
519
|
*/
|
|
487
520
|
export async function _goto(url, options, redirect_count, nav_token) {
|
|
488
|
-
/** @type {string
|
|
521
|
+
/** @type {Set<string>} */
|
|
489
522
|
let query_keys;
|
|
523
|
+
/** @type {Set<string>} */
|
|
524
|
+
let live_query_keys;
|
|
490
525
|
|
|
491
526
|
// Clear preload cache when invalidateAll is true to ensure fresh data
|
|
492
527
|
// after form submissions or explicit invalidations
|
|
@@ -506,12 +541,18 @@ export async function _goto(url, options, redirect_count, nav_token) {
|
|
|
506
541
|
accept: () => {
|
|
507
542
|
if (options.invalidateAll) {
|
|
508
543
|
force_invalidation = true;
|
|
509
|
-
query_keys =
|
|
510
|
-
|
|
544
|
+
query_keys = new Set();
|
|
545
|
+
for (const [id, entries] of query_map) {
|
|
511
546
|
for (const payload of entries.keys()) {
|
|
512
|
-
query_keys.
|
|
547
|
+
query_keys.add(create_remote_key(id, payload));
|
|
513
548
|
}
|
|
514
|
-
}
|
|
549
|
+
}
|
|
550
|
+
live_query_keys = new Set();
|
|
551
|
+
for (const [id, entries] of live_query_map) {
|
|
552
|
+
for (const payload of entries.keys()) {
|
|
553
|
+
live_query_keys.add(create_remote_key(id, payload));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
515
556
|
}
|
|
516
557
|
|
|
517
558
|
if (options.invalidate) {
|
|
@@ -527,13 +568,20 @@ export async function _goto(url, options, redirect_count, nav_token) {
|
|
|
527
568
|
.tick()
|
|
528
569
|
.then(svelte.tick)
|
|
529
570
|
.then(() => {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (query_keys?.
|
|
533
|
-
void resource.refresh
|
|
571
|
+
for (const [id, entries] of query_map) {
|
|
572
|
+
for (const [payload, { resource }] of entries) {
|
|
573
|
+
if (query_keys?.has(create_remote_key(id, payload))) {
|
|
574
|
+
void resource.refresh();
|
|
534
575
|
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
for (const [id, entries] of live_query_map) {
|
|
579
|
+
for (const [payload, { resource }] of entries) {
|
|
580
|
+
if (live_query_keys?.has(create_remote_key(id, payload))) {
|
|
581
|
+
void resource.reconnect();
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
537
585
|
});
|
|
538
586
|
}
|
|
539
587
|
}
|
|
@@ -3035,49 +3083,31 @@ async function load_data(url, invalid) {
|
|
|
3035
3083
|
});
|
|
3036
3084
|
}
|
|
3037
3085
|
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
const { done, value } = await reader.read();
|
|
3043
|
-
if (done && !text) break;
|
|
3044
|
-
|
|
3045
|
-
text += !value && text ? '\n' : text_decoder.decode(value, { stream: true }); // no value -> final chunk -> add a new line to trigger the last parse
|
|
3046
|
-
|
|
3047
|
-
while (true) {
|
|
3048
|
-
const split = text.indexOf('\n');
|
|
3049
|
-
if (split === -1) {
|
|
3050
|
-
break;
|
|
3051
|
-
}
|
|
3052
|
-
|
|
3053
|
-
const node = JSON.parse(text.slice(0, split));
|
|
3054
|
-
text = text.slice(split + 1);
|
|
3086
|
+
for await (const node of read_ndjson(reader)) {
|
|
3087
|
+
if (node.type === 'redirect') {
|
|
3088
|
+
return resolve(node);
|
|
3089
|
+
}
|
|
3055
3090
|
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
}
|
|
3091
|
+
if (node.type === 'data') {
|
|
3092
|
+
// This is the first (and possibly only, if no pending promises) chunk
|
|
3093
|
+
node.nodes?.forEach((/** @type {any} */ node) => {
|
|
3094
|
+
if (node?.type === 'data') {
|
|
3095
|
+
node.uses = deserialize_uses(node.uses);
|
|
3096
|
+
node.data = deserialize(node.data);
|
|
3097
|
+
}
|
|
3098
|
+
});
|
|
3059
3099
|
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
}
|
|
3067
|
-
});
|
|
3100
|
+
resolve(node);
|
|
3101
|
+
} else if (node.type === 'chunk') {
|
|
3102
|
+
// This is a subsequent chunk containing deferred data
|
|
3103
|
+
const { id, data, error } = node;
|
|
3104
|
+
const deferred = /** @type {import('types').Deferred} */ (deferreds.get(id));
|
|
3105
|
+
deferreds.delete(id);
|
|
3068
3106
|
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
const deferred = /** @type {import('types').Deferred} */ (deferreds.get(id));
|
|
3074
|
-
deferreds.delete(id);
|
|
3075
|
-
|
|
3076
|
-
if (error) {
|
|
3077
|
-
deferred.reject(deserialize(error));
|
|
3078
|
-
} else {
|
|
3079
|
-
deferred.fulfil(deserialize(data));
|
|
3080
|
-
}
|
|
3107
|
+
if (error) {
|
|
3108
|
+
deferred.reject(deserialize(error));
|
|
3109
|
+
} else {
|
|
3110
|
+
deferred.fulfil(deserialize(data));
|
|
3081
3111
|
}
|
|
3082
3112
|
}
|
|
3083
3113
|
}
|