@sveltejs/kit 3.0.0-next.2 → 3.0.0-next.4
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 -8
- package/src/core/postbuild/prerender.js +2 -0
- package/src/exports/public.d.ts +1 -1
- package/src/exports/vite/build/build_server.js +43 -58
- package/src/exports/vite/build/remote.js +18 -11
- package/src/exports/vite/build/utils.js +0 -8
- package/src/exports/vite/index.js +220 -216
- 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 -19
- package/src/runtime/client/client.js +25 -15
- package/src/runtime/client/remote-functions/command.svelte.js +5 -30
- 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 +37 -8
- 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 +21 -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/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 +6 -5
- package/types/index.d.ts.map +1 -1
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
/** @import { ServerHooks, MaybePromise, RequestState, RemoteInternals, RequestStore, RemoteLiveQueryUserFunctionReturnType } from 'types' */
|
|
3
3
|
import { parse } from 'devalue';
|
|
4
4
|
import { error } from '@sveltejs/kit';
|
|
5
|
-
import { hydratable } from 'svelte';
|
|
6
5
|
import { with_request_store, get_request_store } from '@sveltejs/kit/internal/server';
|
|
7
|
-
import { create_remote_key, stringify } from '../../../shared.js';
|
|
8
|
-
import { noop } from '../../../../utils/functions.js';
|
|
9
6
|
|
|
10
7
|
/**
|
|
11
8
|
* @param {any} validate_or_fn
|
|
@@ -76,24 +73,13 @@ export async function get_response(internals, payload, state, get_result) {
|
|
|
76
73
|
await 0;
|
|
77
74
|
|
|
78
75
|
const cache = get_cache(internals, state);
|
|
79
|
-
const entry = (cache[payload] ??= {
|
|
80
|
-
serialize: false,
|
|
81
|
-
data: get_result()
|
|
82
|
-
});
|
|
83
76
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const remote_key = create_remote_key(internals.id, payload);
|
|
88
|
-
|
|
89
|
-
void Promise.resolve(entry.data)
|
|
90
|
-
.then((value) => {
|
|
91
|
-
void hydratable(remote_key, () => stringify(value, state.transport));
|
|
92
|
-
})
|
|
93
|
-
.catch(noop);
|
|
77
|
+
if (!state.is_in_remote_query) {
|
|
78
|
+
// if this is a top-level (not nested) `await myQuery()`, include it in the serialized response
|
|
79
|
+
get_implicit_lookup(internals, state)[payload] = get_result;
|
|
94
80
|
}
|
|
95
81
|
|
|
96
|
-
return
|
|
82
|
+
return (cache[payload] ??= get_result());
|
|
97
83
|
}
|
|
98
84
|
|
|
99
85
|
/**
|
|
@@ -239,10 +225,15 @@ function to_iterator(source, name) {
|
|
|
239
225
|
}
|
|
240
226
|
|
|
241
227
|
/**
|
|
228
|
+
* Note that `state` is deliberately not optional: resources that capture the request
|
|
229
|
+
* state at creation must pass it explicitly, because reading it from the request store
|
|
230
|
+
* at call time is only equivalent on runtimes with `AsyncLocalStorage` support.
|
|
231
|
+
* Callers without a captured state (such as the module-level `form` instance getters)
|
|
232
|
+
* should pass `get_request_store().state` themselves.
|
|
242
233
|
* @param {RemoteInternals} internals
|
|
243
234
|
* @param {RequestState} state
|
|
244
235
|
*/
|
|
245
|
-
export function get_cache(internals, state
|
|
236
|
+
export function get_cache(internals, state) {
|
|
246
237
|
let cache = state.remote.data?.get(internals);
|
|
247
238
|
|
|
248
239
|
if (cache === undefined) {
|
|
@@ -252,3 +243,18 @@ export function get_cache(internals, state = get_request_store().state) {
|
|
|
252
243
|
|
|
253
244
|
return cache;
|
|
254
245
|
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @param {RemoteInternals} internals
|
|
249
|
+
* @param {RequestState} state
|
|
250
|
+
*/
|
|
251
|
+
export function get_implicit_lookup(internals, state) {
|
|
252
|
+
let cache = state.remote.implicit?.get(internals);
|
|
253
|
+
|
|
254
|
+
if (cache === undefined) {
|
|
255
|
+
cache = {};
|
|
256
|
+
(state.remote.implicit ??= new Map()).set(internals, cache);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return cache;
|
|
260
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { ServerNodesResponse, ServerRedirectNode } from 'types' */
|
|
1
|
+
/** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */
|
|
2
2
|
/** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
|
|
3
3
|
/** @import { Query } from './remote-functions/query/instance.svelte.js' */
|
|
4
4
|
/** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
|
|
@@ -202,18 +202,20 @@ let target;
|
|
|
202
202
|
export let app;
|
|
203
203
|
|
|
204
204
|
/**
|
|
205
|
-
* Data that was serialized during SSR for queries/forms/commands
|
|
206
|
-
*
|
|
207
|
-
*
|
|
205
|
+
* Data that was serialized during SSR for queries/forms/commands, stored as
|
|
206
|
+
* `{ v }` (value) or `{ e }` (error) nodes so that failed states survive hydration.
|
|
207
|
+
* Entries are deleted as they are consumed (when the corresponding resource is created).
|
|
208
|
+
* @type {Record<string, RemoteFunctionDataNode>}
|
|
208
209
|
*/
|
|
209
|
-
export
|
|
210
|
+
export const query_responses = {};
|
|
210
211
|
|
|
211
212
|
/**
|
|
212
|
-
* Data that was serialized during SSR for prerender functions
|
|
213
|
+
* Data that was serialized during SSR for prerender functions, stored as
|
|
214
|
+
* `{ v }` (value) or `{ e }` (error) nodes.
|
|
213
215
|
* This persists across client-side navigations.
|
|
214
|
-
* @type {Record<string,
|
|
216
|
+
* @type {Record<string, RemoteFunctionDataNode>}
|
|
215
217
|
*/
|
|
216
|
-
export
|
|
218
|
+
export const prerender_responses = {};
|
|
217
219
|
|
|
218
220
|
/** @type {Array<((url: URL) => boolean)>} */
|
|
219
221
|
const invalidated = [];
|
|
@@ -328,9 +330,15 @@ export async function start(_app, _target, hydrate) {
|
|
|
328
330
|
);
|
|
329
331
|
}
|
|
330
332
|
|
|
331
|
-
if (__SVELTEKIT_PAYLOAD__) {
|
|
332
|
-
|
|
333
|
-
|
|
333
|
+
if (__SVELTEKIT_PAYLOAD__.data) {
|
|
334
|
+
const { q = {}, p = {}, l = {}, f = {} } = __SVELTEKIT_PAYLOAD__.data;
|
|
335
|
+
|
|
336
|
+
// store the whole nodes — error records seed the corresponding
|
|
337
|
+
// resources in a failed state when they are created during hydration
|
|
338
|
+
for (const k in q) query_responses[k] = q[k];
|
|
339
|
+
for (const k in l) query_responses[k] = l[k];
|
|
340
|
+
for (const k in f) query_responses[k] = f[k];
|
|
341
|
+
for (const k in p) prerender_responses[k] = p[k];
|
|
334
342
|
}
|
|
335
343
|
|
|
336
344
|
// detect basic auth credentials in the current URL
|
|
@@ -546,7 +554,11 @@ export async function _goto(url, options, redirect_count, nav_token) {
|
|
|
546
554
|
force_invalidation = true;
|
|
547
555
|
query_keys = new Set();
|
|
548
556
|
for (const [id, entries] of query_map) {
|
|
549
|
-
for (const payload of entries
|
|
557
|
+
for (const [payload, entry] of entries) {
|
|
558
|
+
// don't refresh yet, as some queries will be unrendered,
|
|
559
|
+
// but clear caches so that newly rendered queries
|
|
560
|
+
// don't use stale data. TODO same for `live_query_map`
|
|
561
|
+
entry.resource?.reset();
|
|
550
562
|
query_keys.add(create_remote_key(id, payload));
|
|
551
563
|
}
|
|
552
564
|
}
|
|
@@ -574,7 +586,7 @@ export async function _goto(url, options, redirect_count, nav_token) {
|
|
|
574
586
|
for (const [id, entries] of query_map) {
|
|
575
587
|
for (const [payload, { resource }] of entries) {
|
|
576
588
|
if (query_keys?.has(create_remote_key(id, payload))) {
|
|
577
|
-
void resource.
|
|
589
|
+
void resource.start();
|
|
578
590
|
}
|
|
579
591
|
}
|
|
580
592
|
}
|
|
@@ -3028,8 +3040,6 @@ async function _hydrate(
|
|
|
3028
3040
|
|
|
3029
3041
|
target.textContent = '';
|
|
3030
3042
|
hydrate = false;
|
|
3031
|
-
} finally {
|
|
3032
|
-
query_responses = {};
|
|
3033
3043
|
}
|
|
3034
3044
|
|
|
3035
3045
|
if (result.props.page) {
|
|
@@ -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
4
|
import { stringify_command_arg } from '../../shared.js';
|
|
8
|
-
import {
|
|
9
|
-
get_remote_request_headers,
|
|
10
|
-
apply_refreshes,
|
|
11
|
-
categorize_updates,
|
|
12
|
-
apply_reconnections
|
|
13
|
-
} from './shared.svelte.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,7 +45,7 @@ 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
51
|
payload: await stringify_command_arg(arg, app.hooks.transport),
|
|
@@ -62,30 +54,13 @@ export function command(id) {
|
|
|
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,
|
|
@@ -59,18 +58,25 @@ export function form(id) {
|
|
|
59
58
|
const action_id = id + (key != undefined ? `/${JSON.stringify(key)}` : '');
|
|
60
59
|
const action = '?/remote=' + encodeURIComponent(action_id);
|
|
61
60
|
|
|
61
|
+
// the output of a non-enhanced submission that resulted in this page —
|
|
62
|
+
// consume it so the form's state survives hydration (form outputs are
|
|
63
|
+
// always value nodes; the server never serializes them as errors)
|
|
64
|
+
/** @type {{ input?: Record<string, any>, issues?: InternalRemoteFormIssue[], result?: any } | undefined} */
|
|
65
|
+
const initial = query_responses[action_id]?.v;
|
|
66
|
+
delete query_responses[action_id];
|
|
67
|
+
|
|
62
68
|
/**
|
|
63
69
|
* @type {Record<string, string | string[] | File | File[]>}
|
|
64
70
|
*/
|
|
65
|
-
let input = $state({});
|
|
71
|
+
let input = $state(initial?.input ?? {});
|
|
66
72
|
|
|
67
73
|
/** @type {InternalRemoteFormIssue[]} */
|
|
68
|
-
let raw_issues = $state.raw([]);
|
|
74
|
+
let raw_issues = $state.raw(initial?.issues ?? []);
|
|
69
75
|
|
|
70
76
|
const issues = $derived(flatten_issues(raw_issues));
|
|
71
77
|
|
|
72
78
|
/** @type {any} */
|
|
73
|
-
let result = $state.raw(
|
|
79
|
+
let result = $state.raw(initial?.result);
|
|
74
80
|
|
|
75
81
|
/** @type {number} */
|
|
76
82
|
let pending_count = $state(0);
|
|
@@ -181,77 +187,54 @@ export function form(id) {
|
|
|
181
187
|
remote_refreshes: Array.from(refreshes ?? [])
|
|
182
188
|
});
|
|
183
189
|
|
|
184
|
-
const response = await
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
'
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (!response.ok) {
|
|
196
|
-
// We only end up here in case of a network error or if the server has an internal error
|
|
197
|
-
// (which shouldn't happen because we handle errors on the server and always send a 200 response)
|
|
198
|
-
throw new Error('Failed to execute remote function');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const form_result = /** @type { RemoteFunctionResponse} */ (await response.json());
|
|
202
|
-
|
|
203
|
-
// reset issues in case it's a redirect or error (but issues passed in that case)
|
|
204
|
-
raw_issues = [];
|
|
205
|
-
result = undefined;
|
|
206
|
-
|
|
207
|
-
if (form_result.type === 'result') {
|
|
208
|
-
({ issues: raw_issues = [], result } = devalue.parse(form_result.result, app.decoders));
|
|
209
|
-
const succeeded = raw_issues.length === 0;
|
|
210
|
-
|
|
211
|
-
if (succeeded) {
|
|
212
|
-
if (refreshes === null && !form_result.refreshes && !form_result.reconnects) {
|
|
213
|
-
void invalidateAll();
|
|
214
|
-
} else {
|
|
215
|
-
if (form_result.refreshes) {
|
|
216
|
-
apply_refreshes(form_result.refreshes);
|
|
217
|
-
}
|
|
218
|
-
if (form_result.reconnects) {
|
|
219
|
-
apply_reconnections(form_result.reconnects);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
} else {
|
|
223
|
-
if (DEV) {
|
|
224
|
-
warn_on_missing_issue_reads();
|
|
225
|
-
}
|
|
190
|
+
const response = await remote_request(
|
|
191
|
+
`${base}/${app_dir}/remote/${action_id_without_key}`,
|
|
192
|
+
{
|
|
193
|
+
method: 'POST',
|
|
194
|
+
headers: {
|
|
195
|
+
'Content-Type': BINARY_FORM_CONTENT_TYPE,
|
|
196
|
+
// Forms cannot be called during rendering, so it's save to use location here
|
|
197
|
+
'x-sveltekit-pathname': location.pathname,
|
|
198
|
+
'x-sveltekit-search': location.search
|
|
199
|
+
},
|
|
200
|
+
body: blob
|
|
226
201
|
}
|
|
202
|
+
);
|
|
227
203
|
|
|
228
|
-
|
|
229
|
-
} else if (form_result.type === 'redirect') {
|
|
230
|
-
const stringified_refreshes = form_result.refreshes ?? '';
|
|
231
|
-
const stringified_reconnects = form_result.reconnects ?? '';
|
|
232
|
-
if (stringified_refreshes) {
|
|
233
|
-
apply_refreshes(stringified_refreshes);
|
|
234
|
-
}
|
|
204
|
+
({ issues: raw_issues = [], result } = response._ ?? {});
|
|
235
205
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
206
|
+
// if the developer took control of updates via `.updates(...)` (even with
|
|
207
|
+
// no arguments), or the server performed explicit refreshes, don't invalidateAll
|
|
208
|
+
const should_invalidate = refreshes === null && !response.r;
|
|
239
209
|
|
|
210
|
+
if (response.redirect) {
|
|
240
211
|
// Use internal version to allow redirects to external URLs
|
|
241
212
|
void _goto(
|
|
242
|
-
|
|
213
|
+
response.redirect,
|
|
243
214
|
{
|
|
244
|
-
invalidateAll:
|
|
245
|
-
refreshes === null && !stringified_refreshes && !stringified_reconnects
|
|
215
|
+
invalidateAll: should_invalidate
|
|
246
216
|
},
|
|
247
217
|
0
|
|
248
218
|
);
|
|
249
219
|
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const succeeded = raw_issues.length === 0;
|
|
223
|
+
|
|
224
|
+
if (succeeded) {
|
|
225
|
+
if (should_invalidate) {
|
|
226
|
+
void invalidateAll();
|
|
227
|
+
}
|
|
250
228
|
} else {
|
|
251
|
-
|
|
229
|
+
if (DEV) {
|
|
230
|
+
warn_on_missing_issue_reads();
|
|
231
|
+
}
|
|
252
232
|
}
|
|
233
|
+
|
|
234
|
+
return succeeded;
|
|
253
235
|
} catch (e) {
|
|
254
236
|
result = undefined;
|
|
237
|
+
raw_issues = [];
|
|
255
238
|
throw e;
|
|
256
239
|
} finally {
|
|
257
240
|
overrides?.forEach((fn) => fn());
|
|
@@ -630,30 +613,27 @@ export function form(id) {
|
|
|
630
613
|
if (validated?.issues) {
|
|
631
614
|
array = validated.issues.map((issue) => normalize_issue(issue, false));
|
|
632
615
|
} else if (!preflightOnly) {
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
'
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
616
|
+
const result = await remote_request(
|
|
617
|
+
`${base}/${app_dir}/remote/${action_id_without_key}`,
|
|
618
|
+
{
|
|
619
|
+
method: 'POST',
|
|
620
|
+
headers: {
|
|
621
|
+
'Content-Type': BINARY_FORM_CONTENT_TYPE,
|
|
622
|
+
// Validation should not be and will not be called during rendering, so it's save to use location here
|
|
623
|
+
'x-sveltekit-pathname': location.pathname,
|
|
624
|
+
'x-sveltekit-search': location.search
|
|
625
|
+
},
|
|
626
|
+
body: serialize_binary_form(data, {
|
|
627
|
+
validate_only: true
|
|
628
|
+
}).blob
|
|
629
|
+
}
|
|
630
|
+
);
|
|
647
631
|
|
|
648
632
|
if (validate_id !== id) {
|
|
649
633
|
return;
|
|
650
634
|
}
|
|
651
635
|
|
|
652
|
-
|
|
653
|
-
array = /** @type {InternalRemoteFormIssue[]} */ (
|
|
654
|
-
devalue.parse(result.result, app.decoders)
|
|
655
|
-
);
|
|
656
|
-
}
|
|
636
|
+
array = /** @type {InternalRemoteFormIssue[]} */ (result._);
|
|
657
637
|
}
|
|
658
638
|
|
|
659
639
|
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 { hydratable } from 'svelte';
|
|
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 { tick, untrack } from 'svelte';
|
|
@@ -63,6 +64,17 @@ export class Query {
|
|
|
63
64
|
constructor(key, fn) {
|
|
64
65
|
this.#key = key;
|
|
65
66
|
this.#fn = fn;
|
|
67
|
+
|
|
68
|
+
if (Object.hasOwn(query_responses, key)) {
|
|
69
|
+
const node = query_responses[key];
|
|
70
|
+
delete query_responses[key];
|
|
71
|
+
|
|
72
|
+
if (node.e) {
|
|
73
|
+
this.fail(new HttpError(node.e[0] ?? 500, node.e[1]));
|
|
74
|
+
} else {
|
|
75
|
+
this.set(/** @type {T} */ (node.v));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
66
78
|
}
|
|
67
79
|
|
|
68
80
|
#get_promise() {
|
|
@@ -70,7 +82,7 @@ export class Query {
|
|
|
70
82
|
return /** @type {Promise<T>} */ (this.#promise);
|
|
71
83
|
}
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
start() {
|
|
74
86
|
// there is a really weird bug with untrack and writes and initializations
|
|
75
87
|
// every time you see this comment, try removing the `tick.then` here and see
|
|
76
88
|
// if all the tests still pass with the latest svelte version
|
|
@@ -131,12 +143,12 @@ export class Query {
|
|
|
131
143
|
get then() {
|
|
132
144
|
// TODO this should be unnecessary but due to the bug described
|
|
133
145
|
// in #start, we need to do this in some circumstances
|
|
134
|
-
this
|
|
146
|
+
this.start();
|
|
135
147
|
return this.#then;
|
|
136
148
|
}
|
|
137
149
|
|
|
138
150
|
get catch() {
|
|
139
|
-
this
|
|
151
|
+
this.start();
|
|
140
152
|
this.#then;
|
|
141
153
|
return (/** @type {any} */ reject) => {
|
|
142
154
|
return this.#then(undefined, reject);
|
|
@@ -144,7 +156,7 @@ export class Query {
|
|
|
144
156
|
}
|
|
145
157
|
|
|
146
158
|
get finally() {
|
|
147
|
-
this
|
|
159
|
+
this.start();
|
|
148
160
|
this.#then;
|
|
149
161
|
return (/** @type {any} */ fn) => {
|
|
150
162
|
return this.#then(
|
|
@@ -161,12 +173,12 @@ export class Query {
|
|
|
161
173
|
}
|
|
162
174
|
|
|
163
175
|
get current() {
|
|
164
|
-
this
|
|
176
|
+
this.start();
|
|
165
177
|
return this.#current;
|
|
166
178
|
}
|
|
167
179
|
|
|
168
180
|
get error() {
|
|
169
|
-
this
|
|
181
|
+
this.start();
|
|
170
182
|
return this.#error;
|
|
171
183
|
}
|
|
172
184
|
|
|
@@ -174,7 +186,7 @@ export class Query {
|
|
|
174
186
|
* Returns true if the resource is loading or reloading.
|
|
175
187
|
*/
|
|
176
188
|
get loading() {
|
|
177
|
-
this
|
|
189
|
+
this.start();
|
|
178
190
|
return this.#loading;
|
|
179
191
|
}
|
|
180
192
|
|
|
@@ -182,7 +194,7 @@ export class Query {
|
|
|
182
194
|
* Returns true once the resource has been loaded for the first time.
|
|
183
195
|
*/
|
|
184
196
|
get ready() {
|
|
185
|
-
this
|
|
197
|
+
this.start();
|
|
186
198
|
return this.#ready;
|
|
187
199
|
}
|
|
188
200
|
|
|
@@ -198,6 +210,10 @@ export class Query {
|
|
|
198
210
|
* @param {T} value
|
|
199
211
|
*/
|
|
200
212
|
set(value) {
|
|
213
|
+
// normally consumed in the constructor, but make sure a leftover
|
|
214
|
+
// SSR record can never shadow the newly-set value
|
|
215
|
+
delete query_responses[this.#key];
|
|
216
|
+
|
|
201
217
|
this.#clear_pending();
|
|
202
218
|
this.#ready = true;
|
|
203
219
|
this.#loading = false;
|
|
@@ -210,6 +226,10 @@ export class Query {
|
|
|
210
226
|
* @param {unknown} error
|
|
211
227
|
*/
|
|
212
228
|
fail(error) {
|
|
229
|
+
// normally consumed in the constructor, but make sure a leftover
|
|
230
|
+
// SSR record can never shadow the newly-set error
|
|
231
|
+
delete query_responses[this.#key];
|
|
232
|
+
|
|
213
233
|
this.#clear_pending();
|
|
214
234
|
this.#loading = false;
|
|
215
235
|
this.#error = error;
|
|
@@ -242,6 +262,15 @@ export class Query {
|
|
|
242
262
|
return release;
|
|
243
263
|
}
|
|
244
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Reset ahead of a navigation that invalidates all, to force newly
|
|
267
|
+
* rendered queries to get fresh data
|
|
268
|
+
*/
|
|
269
|
+
reset() {
|
|
270
|
+
this.#promise = null;
|
|
271
|
+
delete query_responses[this.#key];
|
|
272
|
+
}
|
|
273
|
+
|
|
245
274
|
get [Symbol.toStringTag]() {
|
|
246
275
|
return 'Query';
|
|
247
276
|
}
|
|
@@ -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);
|