@sveltejs/kit 2.63.1 → 2.65.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/core/postbuild/analyse.js +0 -5
- package/src/core/postbuild/prerender.js +2 -0
- package/src/exports/public.d.ts +1 -1
- package/src/exports/vite/build/build_server.js +47 -57
- package/src/exports/vite/build/utils.js +0 -8
- package/src/exports/vite/index.js +226 -178
- package/src/runtime/app/server/remote/command.js +0 -3
- package/src/runtime/app/server/remote/form.js +18 -13
- package/src/runtime/app/server/remote/prerender.js +28 -34
- package/src/runtime/app/server/remote/query.js +105 -94
- package/src/runtime/app/server/remote/requested.js +14 -10
- package/src/runtime/app/server/remote/shared.js +25 -18
- package/src/runtime/client/client.js +19 -13
- package/src/runtime/client/remote-functions/command.svelte.js +7 -32
- package/src/runtime/client/remote-functions/form.svelte.js +62 -82
- package/src/runtime/client/remote-functions/prerender.svelte.js +14 -6
- package/src/runtime/client/remote-functions/query/index.js +6 -14
- package/src/runtime/client/remote-functions/query/instance.svelte.js +20 -0
- package/src/runtime/client/remote-functions/query/proxy.js +3 -3
- package/src/runtime/client/remote-functions/query-batch.svelte.js +59 -68
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +20 -6
- package/src/runtime/client/remote-functions/shared.svelte.js +76 -59
- package/src/runtime/server/page/render.js +20 -80
- package/src/runtime/server/page/server_routing.js +20 -15
- package/src/runtime/server/remote.js +296 -204
- package/src/runtime/server/respond.js +4 -2
- package/src/runtime/shared.js +83 -28
- package/src/types/global-private.d.ts +3 -3
- package/src/types/internal.d.ts +53 -34
- package/src/version.js +1 -1
- package/types/index.d.ts +4 -4
- package/types/index.d.ts.map +1 -1
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
/** @import { RemoteCommand, RemoteQueryUpdate } from '@sveltejs/kit' */
|
|
2
|
-
/** @import { RemoteFunctionResponse } from 'types' */
|
|
3
2
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
4
|
-
import * as devalue from 'devalue';
|
|
5
|
-
import { HttpError } from '@sveltejs/kit/internal';
|
|
6
3
|
import { app } from '../client.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
get_remote_request_headers,
|
|
10
|
-
apply_refreshes,
|
|
11
|
-
categorize_updates,
|
|
12
|
-
apply_reconnections
|
|
13
|
-
} from './shared.svelte.js';
|
|
4
|
+
import { stringify_command_arg } from '../../shared.js';
|
|
5
|
+
import { get_remote_request_headers, categorize_updates, remote_request } from './shared.svelte.js';
|
|
14
6
|
|
|
15
7
|
/**
|
|
16
8
|
* Client-version of the `command` function from `$app/server`.
|
|
@@ -53,39 +45,22 @@ export function command(id) {
|
|
|
53
45
|
throw updates_error;
|
|
54
46
|
}
|
|
55
47
|
|
|
56
|
-
const response = await
|
|
48
|
+
const response = await remote_request(`${base}/${app_dir}/remote/${id}`, {
|
|
57
49
|
method: 'POST',
|
|
58
50
|
body: JSON.stringify({
|
|
59
|
-
payload:
|
|
51
|
+
payload: await stringify_command_arg(arg, app.hooks.transport),
|
|
60
52
|
refreshes: Array.from(refreshes ?? [])
|
|
61
53
|
}),
|
|
62
54
|
headers
|
|
63
55
|
});
|
|
64
56
|
|
|
65
|
-
if (
|
|
66
|
-
// We only end up here in case of a network error or if the server has an internal error
|
|
67
|
-
// (which shouldn't happen because we handle errors on the server and always send a 200 response)
|
|
68
|
-
throw new Error('Failed to execute remote function');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const result = /** @type {RemoteFunctionResponse} */ (await response.json());
|
|
72
|
-
if (result.type === 'redirect') {
|
|
57
|
+
if (response.redirect) {
|
|
73
58
|
throw new Error(
|
|
74
59
|
'Redirects are not allowed in commands. Return a result instead and use goto on the client'
|
|
75
60
|
);
|
|
76
|
-
} else if (result.type === 'error') {
|
|
77
|
-
throw new HttpError(result.status ?? 500, result.error);
|
|
78
|
-
} else {
|
|
79
|
-
if (result.refreshes) {
|
|
80
|
-
apply_refreshes(result.refreshes);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (result.reconnects) {
|
|
84
|
-
apply_reconnections(result.reconnects);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return devalue.parse(result.result, app.decoders);
|
|
88
61
|
}
|
|
62
|
+
|
|
63
|
+
return response._;
|
|
89
64
|
} finally {
|
|
90
65
|
overrides?.forEach((fn) => fn());
|
|
91
66
|
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
|
|
2
2
|
/** @import { RemoteFormInput, RemoteForm, RemoteQueryUpdate } from '@sveltejs/kit' */
|
|
3
|
-
/** @import { InternalRemoteFormIssue
|
|
3
|
+
/** @import { InternalRemoteFormIssue } from 'types' */
|
|
4
4
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
5
|
-
import * as devalue from 'devalue';
|
|
6
5
|
import { DEV } from 'esm-env';
|
|
7
6
|
import { HttpError } from '@sveltejs/kit/internal';
|
|
8
|
-
import {
|
|
7
|
+
import { query_responses, _goto, set_nearest_error_page, invalidateAll } from '../client.js';
|
|
9
8
|
import { tick } from 'svelte';
|
|
10
|
-
import {
|
|
9
|
+
import { categorize_updates, remote_request } from './shared.svelte.js';
|
|
11
10
|
import { createAttachmentKey } from 'svelte/attachments';
|
|
12
11
|
import {
|
|
13
12
|
convert_formdata,
|
|
@@ -60,18 +59,25 @@ export function form(id) {
|
|
|
60
59
|
const action_id = id + (key != undefined ? `/${JSON.stringify(key)}` : '');
|
|
61
60
|
const action = '?/remote=' + encodeURIComponent(action_id);
|
|
62
61
|
|
|
62
|
+
// the output of a non-enhanced submission that resulted in this page —
|
|
63
|
+
// consume it so the form's state survives hydration (form outputs are
|
|
64
|
+
// always value nodes; the server never serializes them as errors)
|
|
65
|
+
/** @type {{ input?: Record<string, any>, issues?: InternalRemoteFormIssue[], result?: any } | undefined} */
|
|
66
|
+
const initial = query_responses[action_id]?.v;
|
|
67
|
+
delete query_responses[action_id];
|
|
68
|
+
|
|
63
69
|
/**
|
|
64
70
|
* @type {Record<string, string | string[] | File | File[]>}
|
|
65
71
|
*/
|
|
66
|
-
let input = $state({});
|
|
72
|
+
let input = $state(initial?.input ?? {});
|
|
67
73
|
|
|
68
74
|
/** @type {InternalRemoteFormIssue[]} */
|
|
69
|
-
let raw_issues = $state.raw([]);
|
|
75
|
+
let raw_issues = $state.raw(initial?.issues ?? []);
|
|
70
76
|
|
|
71
77
|
const issues = $derived(flatten_issues(raw_issues));
|
|
72
78
|
|
|
73
79
|
/** @type {any} */
|
|
74
|
-
let result = $state.raw(
|
|
80
|
+
let result = $state.raw(initial?.result);
|
|
75
81
|
|
|
76
82
|
/** @type {number} */
|
|
77
83
|
let pending_count = $state(0);
|
|
@@ -182,77 +188,54 @@ export function form(id) {
|
|
|
182
188
|
remote_refreshes: Array.from(refreshes ?? [])
|
|
183
189
|
});
|
|
184
190
|
|
|
185
|
-
const response = await
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
'
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (!response.ok) {
|
|
197
|
-
// We only end up here in case of a network error or if the server has an internal error
|
|
198
|
-
// (which shouldn't happen because we handle errors on the server and always send a 200 response)
|
|
199
|
-
throw new Error('Failed to execute remote function');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const form_result = /** @type { RemoteFunctionResponse} */ (await response.json());
|
|
203
|
-
|
|
204
|
-
// reset issues in case it's a redirect or error (but issues passed in that case)
|
|
205
|
-
raw_issues = [];
|
|
206
|
-
result = undefined;
|
|
207
|
-
|
|
208
|
-
if (form_result.type === 'result') {
|
|
209
|
-
({ issues: raw_issues = [], result } = devalue.parse(form_result.result, app.decoders));
|
|
210
|
-
const succeeded = raw_issues.length === 0;
|
|
211
|
-
|
|
212
|
-
if (succeeded) {
|
|
213
|
-
if (refreshes === null && !form_result.refreshes && !form_result.reconnects) {
|
|
214
|
-
void invalidateAll();
|
|
215
|
-
} else {
|
|
216
|
-
if (form_result.refreshes) {
|
|
217
|
-
apply_refreshes(form_result.refreshes);
|
|
218
|
-
}
|
|
219
|
-
if (form_result.reconnects) {
|
|
220
|
-
apply_reconnections(form_result.reconnects);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
} else {
|
|
224
|
-
if (DEV) {
|
|
225
|
-
warn_on_missing_issue_reads();
|
|
226
|
-
}
|
|
191
|
+
const response = await remote_request(
|
|
192
|
+
`${base}/${app_dir}/remote/${action_id_without_key}`,
|
|
193
|
+
{
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: {
|
|
196
|
+
'Content-Type': BINARY_FORM_CONTENT_TYPE,
|
|
197
|
+
// Forms cannot be called during rendering, so it's save to use location here
|
|
198
|
+
'x-sveltekit-pathname': location.pathname,
|
|
199
|
+
'x-sveltekit-search': location.search
|
|
200
|
+
},
|
|
201
|
+
body: blob
|
|
227
202
|
}
|
|
203
|
+
);
|
|
228
204
|
|
|
229
|
-
|
|
230
|
-
} else if (form_result.type === 'redirect') {
|
|
231
|
-
const stringified_refreshes = form_result.refreshes ?? '';
|
|
232
|
-
const stringified_reconnects = form_result.reconnects ?? '';
|
|
233
|
-
if (stringified_refreshes) {
|
|
234
|
-
apply_refreshes(stringified_refreshes);
|
|
235
|
-
}
|
|
205
|
+
({ issues: raw_issues = [], result } = response._ ?? {});
|
|
236
206
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
207
|
+
// if the developer took control of updates via `.updates(...)` (even with
|
|
208
|
+
// no arguments), or the server performed explicit refreshes, don't invalidateAll
|
|
209
|
+
const should_invalidate = refreshes === null && !response.r;
|
|
240
210
|
|
|
211
|
+
if (response.redirect) {
|
|
241
212
|
// Use internal version to allow redirects to external URLs
|
|
242
213
|
void _goto(
|
|
243
|
-
|
|
214
|
+
response.redirect,
|
|
244
215
|
{
|
|
245
|
-
invalidateAll:
|
|
246
|
-
refreshes === null && !stringified_refreshes && !stringified_reconnects
|
|
216
|
+
invalidateAll: should_invalidate
|
|
247
217
|
},
|
|
248
218
|
0
|
|
249
219
|
);
|
|
250
220
|
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const succeeded = raw_issues.length === 0;
|
|
224
|
+
|
|
225
|
+
if (succeeded) {
|
|
226
|
+
if (should_invalidate) {
|
|
227
|
+
void invalidateAll();
|
|
228
|
+
}
|
|
251
229
|
} else {
|
|
252
|
-
|
|
230
|
+
if (DEV) {
|
|
231
|
+
warn_on_missing_issue_reads();
|
|
232
|
+
}
|
|
253
233
|
}
|
|
234
|
+
|
|
235
|
+
return succeeded;
|
|
254
236
|
} catch (e) {
|
|
255
237
|
result = undefined;
|
|
238
|
+
raw_issues = [];
|
|
256
239
|
throw e;
|
|
257
240
|
} finally {
|
|
258
241
|
overrides?.forEach((fn) => fn());
|
|
@@ -661,30 +644,27 @@ export function form(id) {
|
|
|
661
644
|
if (validated?.issues) {
|
|
662
645
|
array = validated.issues.map((issue) => normalize_issue(issue, false));
|
|
663
646
|
} else if (!preflightOnly) {
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
'
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
647
|
+
const result = await remote_request(
|
|
648
|
+
`${base}/${app_dir}/remote/${action_id_without_key}`,
|
|
649
|
+
{
|
|
650
|
+
method: 'POST',
|
|
651
|
+
headers: {
|
|
652
|
+
'Content-Type': BINARY_FORM_CONTENT_TYPE,
|
|
653
|
+
// Validation should not be and will not be called during rendering, so it's save to use location here
|
|
654
|
+
'x-sveltekit-pathname': location.pathname,
|
|
655
|
+
'x-sveltekit-search': location.search
|
|
656
|
+
},
|
|
657
|
+
body: serialize_binary_form(data, {
|
|
658
|
+
validate_only: true
|
|
659
|
+
}).blob
|
|
660
|
+
}
|
|
661
|
+
);
|
|
678
662
|
|
|
679
663
|
if (validate_id !== id) {
|
|
680
664
|
return;
|
|
681
665
|
}
|
|
682
666
|
|
|
683
|
-
|
|
684
|
-
array = /** @type {InternalRemoteFormIssue[]} */ (
|
|
685
|
-
devalue.parse(result.result, app.decoders)
|
|
686
|
-
);
|
|
687
|
-
}
|
|
667
|
+
array = /** @type {InternalRemoteFormIssue[]} */ (result._);
|
|
688
668
|
}
|
|
689
669
|
|
|
690
670
|
if (!includeUntouched && !submitted) {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
3
3
|
import { version } from '$app/env';
|
|
4
4
|
import * as devalue from 'devalue';
|
|
5
|
-
import { app, prerender_responses } from '../client.js';
|
|
6
|
-
import { get_remote_request_headers, remote_request } from './shared.svelte.js';
|
|
5
|
+
import { app, goto, prerender_responses } from '../client.js';
|
|
6
|
+
import { get_remote_request_headers, remote_request, unwrap_node } from './shared.svelte.js';
|
|
7
7
|
import { create_remote_key, stringify_remote_arg } from '../../shared.js';
|
|
8
8
|
|
|
9
9
|
// Initialize Cache API for prerender functions
|
|
@@ -67,7 +67,7 @@ export function prerender(id) {
|
|
|
67
67
|
const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ''}`;
|
|
68
68
|
|
|
69
69
|
if (Object.hasOwn(prerender_responses, cache_key)) {
|
|
70
|
-
const data = prerender_responses[cache_key];
|
|
70
|
+
const data = unwrap_node(prerender_responses[cache_key]);
|
|
71
71
|
|
|
72
72
|
if (prerender_cache) {
|
|
73
73
|
void put(url, devalue.stringify(data, app.encoders));
|
|
@@ -77,6 +77,7 @@ export function prerender(id) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// Do this here, after await Svelte' reactivity context is gone.
|
|
80
|
+
// TODO we really don't want to be sending these specific headers here?
|
|
80
81
|
const headers = get_remote_request_headers();
|
|
81
82
|
|
|
82
83
|
// Check the Cache API first
|
|
@@ -93,14 +94,21 @@ export function prerender(id) {
|
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
const
|
|
97
|
+
const result = await remote_request(url, { headers });
|
|
98
|
+
|
|
99
|
+
if (result.redirect) {
|
|
100
|
+
void goto(result.redirect);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const data = result._;
|
|
97
105
|
|
|
98
106
|
// For successful prerender requests, save to cache
|
|
99
107
|
if (prerender_cache) {
|
|
100
|
-
void put(url,
|
|
108
|
+
void put(url, devalue.stringify(data, app.encoders));
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
return
|
|
111
|
+
return data;
|
|
104
112
|
});
|
|
105
113
|
|
|
106
114
|
prerender_resources.set(cache_key, new WeakRef(resource));
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/** @import { RemoteQueryFunction } from '@sveltejs/kit' */
|
|
2
2
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
3
|
-
import {
|
|
3
|
+
import { goto, query_map } from '../../client.js';
|
|
4
4
|
import { get_remote_request_headers, QUERY_FUNCTION_ID, remote_request } from '../shared.svelte.js';
|
|
5
|
-
import * as devalue from 'devalue';
|
|
6
5
|
import { DEV } from 'esm-env';
|
|
7
|
-
import { unfriendly_hydratable } from '../../../shared.js';
|
|
8
6
|
import { QueryProxy } from './proxy.js';
|
|
9
7
|
|
|
10
8
|
/**
|
|
@@ -25,20 +23,14 @@ export function query(id) {
|
|
|
25
23
|
|
|
26
24
|
/** @type {RemoteQueryFunction<any, any>} */
|
|
27
25
|
const wrapper = (arg) => {
|
|
28
|
-
return new QueryProxy(id, arg, async (
|
|
29
|
-
if (Object.hasOwn(query_responses, key)) {
|
|
30
|
-
const value = query_responses[key];
|
|
31
|
-
delete query_responses[key];
|
|
32
|
-
return value;
|
|
33
|
-
}
|
|
34
|
-
|
|
26
|
+
return new QueryProxy(id, arg, async (payload) => {
|
|
35
27
|
const url = `${base}/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
|
|
36
28
|
|
|
37
|
-
const
|
|
38
|
-
remote_request(url, get_remote_request_headers())
|
|
39
|
-
);
|
|
29
|
+
const result = await remote_request(url, { headers: get_remote_request_headers() });
|
|
40
30
|
|
|
41
|
-
|
|
31
|
+
if (result.redirect) {
|
|
32
|
+
await goto(result.redirect);
|
|
33
|
+
}
|
|
42
34
|
});
|
|
43
35
|
};
|
|
44
36
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { query_responses } from '../../client.js';
|
|
2
|
+
import { HttpError } from '@sveltejs/kit/internal';
|
|
2
3
|
import { QUERY_OVERRIDE_KEY } from '../shared.svelte.js';
|
|
3
4
|
import { noop } from '../../../../utils/functions.js';
|
|
4
5
|
import { with_resolvers } from '../../../../utils/promise.js';
|
|
@@ -64,6 +65,17 @@ export class Query {
|
|
|
64
65
|
constructor(key, fn) {
|
|
65
66
|
this.#key = key;
|
|
66
67
|
this.#fn = fn;
|
|
68
|
+
|
|
69
|
+
if (Object.hasOwn(query_responses, key)) {
|
|
70
|
+
const node = query_responses[key];
|
|
71
|
+
delete query_responses[key];
|
|
72
|
+
|
|
73
|
+
if (node.e) {
|
|
74
|
+
this.fail(new HttpError(node.e[0] ?? 500, node.e[1]));
|
|
75
|
+
} else {
|
|
76
|
+
this.set(/** @type {T} */ (node.v));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
#get_promise() {
|
|
@@ -199,6 +211,10 @@ export class Query {
|
|
|
199
211
|
* @param {T} value
|
|
200
212
|
*/
|
|
201
213
|
set(value) {
|
|
214
|
+
// normally consumed in the constructor, but make sure a leftover
|
|
215
|
+
// SSR record can never shadow the newly-set value
|
|
216
|
+
delete query_responses[this.#key];
|
|
217
|
+
|
|
202
218
|
this.#clear_pending();
|
|
203
219
|
this.#ready = true;
|
|
204
220
|
this.#loading = false;
|
|
@@ -211,6 +227,10 @@ export class Query {
|
|
|
211
227
|
* @param {unknown} error
|
|
212
228
|
*/
|
|
213
229
|
fail(error) {
|
|
230
|
+
// normally consumed in the constructor, but make sure a leftover
|
|
231
|
+
// SSR record can never shadow the newly-set error
|
|
232
|
+
delete query_responses[this.#key];
|
|
233
|
+
|
|
214
234
|
this.#clear_pending();
|
|
215
235
|
this.#loading = false;
|
|
216
236
|
this.#error = error;
|
|
@@ -25,7 +25,7 @@ export class QueryProxy {
|
|
|
25
25
|
/**
|
|
26
26
|
* @param {string} id
|
|
27
27
|
* @param {any} arg
|
|
28
|
-
* @param {(
|
|
28
|
+
* @param {(payload: string) => Promise<T>} fn
|
|
29
29
|
*/
|
|
30
30
|
constructor(id, arg, fn) {
|
|
31
31
|
this.#id = id;
|
|
@@ -41,7 +41,7 @@ export class QueryProxy {
|
|
|
41
41
|
this.#payload,
|
|
42
42
|
// IMPORTANT: This cannot close over `this` or it becomes impossible to
|
|
43
43
|
// garbage collect the QueryProxy and thus impossible to evict cache entries.
|
|
44
|
-
() => new Query(key, () => fn(
|
|
44
|
+
() => new Query(key, () => fn(payload))
|
|
45
45
|
);
|
|
46
46
|
|
|
47
47
|
cache.ref(this, entry, this.#id, this.#payload);
|
|
@@ -98,7 +98,7 @@ export class QueryProxy {
|
|
|
98
98
|
this.#payload,
|
|
99
99
|
// IMPORTANT: This cannot close over `this` or it becomes impossible to
|
|
100
100
|
// garbage collect the QueryProxy and thus impossible to evict cache entries.
|
|
101
|
-
() => new Query(key_ref, () => fn_ref(
|
|
101
|
+
() => new Query(key_ref, () => fn_ref(payload_ref))
|
|
102
102
|
);
|
|
103
103
|
|
|
104
104
|
const deref = cache.manual_ref(entry, this.#id, this.#payload);
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
/** @import { RemoteQueryFunction } from '@sveltejs/kit' */
|
|
2
|
-
/** @import { RemoteFunctionResponse } from 'types' */
|
|
3
2
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
4
|
-
import {
|
|
5
|
-
import { get_remote_request_headers, QUERY_FUNCTION_ID } from './shared.svelte.js';
|
|
3
|
+
import { goto } from '../client.js';
|
|
4
|
+
import { get_remote_request_headers, QUERY_FUNCTION_ID, remote_request } from './shared.svelte.js';
|
|
6
5
|
import { QueryProxy } from './query/proxy.js';
|
|
7
|
-
import
|
|
8
|
-
import { HttpError, Redirect } from '@sveltejs/kit/internal';
|
|
9
|
-
import { unfriendly_hydratable } from '../../shared.js';
|
|
6
|
+
import { HttpError } from '@sveltejs/kit/internal';
|
|
10
7
|
|
|
11
8
|
/**
|
|
12
9
|
* @param {string} id
|
|
@@ -18,84 +15,78 @@ export function query_batch(id) {
|
|
|
18
15
|
|
|
19
16
|
/** @type {RemoteQueryFunction<any, any>} */
|
|
20
17
|
const wrapper = (arg) => {
|
|
21
|
-
return new QueryProxy(id, arg, async (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
batching.set(payload, entry);
|
|
18
|
+
return new QueryProxy(id, arg, async (payload) => {
|
|
19
|
+
return await new Promise((resolve, reject) => {
|
|
20
|
+
// create_remote_function caches identical calls, but in case a refresh to the same query is called multiple times this function
|
|
21
|
+
// is invoked multiple times with the same payload, so we need to deduplicate here
|
|
22
|
+
const entry = batching.get(payload) ?? [];
|
|
23
|
+
entry.push({ resolve, reject });
|
|
24
|
+
batching.set(payload, entry);
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
if (batching.size > 1) return;
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
// Do this here, after await Svelte' reactivity context is gone.
|
|
29
|
+
// TODO is it possible to have batches of the same key
|
|
30
|
+
// but in different forks/async contexts and in the same macrotask?
|
|
31
|
+
// If so this would potentially be buggy
|
|
32
|
+
const headers = {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
...get_remote_request_headers()
|
|
35
|
+
};
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
// Wait for the next macrotask - don't use microtask as Svelte runtime uses these to collect changes and flush them,
|
|
38
|
+
// and flushes could reveal more queries that should be batched.
|
|
39
|
+
setTimeout(async () => {
|
|
40
|
+
const batched = batching;
|
|
41
|
+
batching = new Map();
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
try {
|
|
44
|
+
const response = await remote_request(`${base}/${app_dir}/remote/${id}`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
payloads: Array.from(batched.keys())
|
|
48
|
+
}),
|
|
49
|
+
headers
|
|
50
|
+
});
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
52
|
+
if (response.redirect) {
|
|
53
|
+
await goto(response.redirect);
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
// settle all batched promises (with `undefined`, like a redirect
|
|
56
|
+
// from a non-batched query) so that callers don't hang forever
|
|
57
|
+
for (const resolvers of batched.values()) {
|
|
58
|
+
for (const { resolve } of resolvers) {
|
|
59
|
+
resolve(undefined);
|
|
60
|
+
}
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
throw new Redirect(307, result.location);
|
|
68
|
-
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
69
65
|
|
|
70
|
-
|
|
66
|
+
const results = response._;
|
|
67
|
+
let i = 0;
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
let i = 0;
|
|
69
|
+
for (const resolvers of batched.values()) {
|
|
70
|
+
const result = results[i];
|
|
75
71
|
|
|
76
|
-
for (const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
resolve(results[i].data);
|
|
82
|
-
}
|
|
72
|
+
for (const { resolve, reject } of resolvers) {
|
|
73
|
+
if (result.type === 'error') {
|
|
74
|
+
reject(new HttpError(result.status, result.error));
|
|
75
|
+
} else {
|
|
76
|
+
resolve(result.data);
|
|
83
77
|
}
|
|
84
|
-
i++;
|
|
85
78
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
79
|
+
i++;
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
for (const resolvers of batched.values()) {
|
|
83
|
+
for (const { reject } of resolvers) {
|
|
84
|
+
reject(e);
|
|
92
85
|
}
|
|
93
86
|
}
|
|
94
|
-
}
|
|
95
|
-
});
|
|
87
|
+
}
|
|
88
|
+
}, 0);
|
|
96
89
|
});
|
|
97
|
-
|
|
98
|
-
return devalue.parse(serialized, app.decoders);
|
|
99
90
|
});
|
|
100
91
|
};
|
|
101
92
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/** @import { PromiseWithResolvers } from '../../../../utils/promise.js' */
|
|
2
|
-
import {
|
|
3
|
-
import * as devalue from 'devalue';
|
|
2
|
+
import { query_responses } from '../../client.js';
|
|
4
3
|
import { HttpError, Redirect } from '@sveltejs/kit/internal';
|
|
5
4
|
import { noop, once } from '../../../../utils/functions.js';
|
|
6
5
|
import { with_resolvers } from '../../../../utils/promise.js';
|
|
7
6
|
import { SharedIterator } from '../../../../utils/shared-iterator.js';
|
|
8
7
|
import { tick } from 'svelte';
|
|
9
|
-
import { unfriendly_hydratable } from '../../../shared.js';
|
|
10
8
|
import { create_live_iterator } from './iterator.js';
|
|
11
9
|
|
|
12
10
|
/**
|
|
@@ -87,9 +85,25 @@ export class LiveQuery {
|
|
|
87
85
|
this.#resolve_first = resolve;
|
|
88
86
|
this.#reject_first = reject;
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
if (Object.hasOwn(query_responses, key)) {
|
|
89
|
+
const node = query_responses[key];
|
|
90
|
+
delete query_responses[key];
|
|
91
|
+
|
|
92
|
+
if (node.e) {
|
|
93
|
+
// the query failed during SSR — seed the failed state (mirroring `fail()`,
|
|
94
|
+
// minus its terminal `#done`), so the main loop still connects as usual
|
|
95
|
+
// and the query can recover
|
|
96
|
+
const error = new HttpError(node.e[0] ?? 500, node.e[1]);
|
|
97
|
+
this.#loading = false;
|
|
98
|
+
this.#error = error;
|
|
99
|
+
|
|
100
|
+
promise.catch(noop);
|
|
101
|
+
this.#reject_first?.(error);
|
|
102
|
+
this.#resolve_first = null;
|
|
103
|
+
this.#reject_first = null;
|
|
104
|
+
} else {
|
|
105
|
+
this.set(node.v);
|
|
106
|
+
}
|
|
93
107
|
}
|
|
94
108
|
}
|
|
95
109
|
|