@sveltejs/kit 2.26.0 → 2.27.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/README.md +1 -1
- package/package.json +3 -2
- package/src/core/adapt/builder.js +6 -1
- package/src/core/config/options.js +4 -0
- package/src/core/generate_manifest/index.js +3 -0
- package/src/core/postbuild/analyse.js +25 -1
- package/src/core/postbuild/fallback.js +2 -1
- package/src/core/postbuild/prerender.js +41 -10
- package/src/core/sync/create_manifest_data/index.js +35 -1
- package/src/core/sync/write_server.js +4 -2
- package/src/core/sync/write_types/index.js +12 -5
- package/src/exports/index.js +1 -1
- package/src/exports/internal/index.js +3 -1
- package/src/exports/internal/remote-functions.js +21 -0
- package/src/exports/public.d.ts +162 -2
- package/src/exports/vite/build/build_remote.js +129 -0
- package/src/exports/vite/dev/index.js +7 -0
- package/src/exports/vite/index.js +123 -8
- package/src/exports/vite/module_ids.js +3 -2
- package/src/exports/vite/preview/index.js +3 -1
- package/src/runtime/app/navigation.js +1 -0
- package/src/runtime/app/server/index.js +2 -0
- package/src/runtime/app/server/remote/command.js +91 -0
- package/src/runtime/app/server/remote/form.js +124 -0
- package/src/runtime/app/server/remote/index.js +4 -0
- package/src/runtime/app/server/remote/prerender.js +163 -0
- package/src/runtime/app/server/remote/query.js +115 -0
- package/src/runtime/app/server/remote/shared.js +153 -0
- package/src/runtime/client/client.js +107 -39
- package/src/runtime/client/fetcher.js +1 -1
- package/src/runtime/client/remote-functions/command.js +71 -0
- package/src/runtime/client/remote-functions/form.svelte.js +312 -0
- package/src/runtime/client/remote-functions/index.js +4 -0
- package/src/runtime/client/remote-functions/prerender.svelte.js +166 -0
- package/src/runtime/client/remote-functions/query.svelte.js +219 -0
- package/src/runtime/client/remote-functions/shared.svelte.js +143 -0
- package/src/runtime/client/types.d.ts +2 -0
- package/src/runtime/server/data/index.js +6 -4
- package/src/runtime/server/event-state.js +41 -0
- package/src/runtime/server/index.js +12 -3
- package/src/runtime/server/page/actions.js +1 -1
- package/src/runtime/server/page/index.js +10 -3
- package/src/runtime/server/page/load_data.js +18 -12
- package/src/runtime/server/page/render.js +31 -5
- package/src/runtime/server/page/serialize_data.js +1 -1
- package/src/runtime/server/remote.js +237 -0
- package/src/runtime/server/respond.js +57 -36
- package/src/runtime/shared.js +61 -0
- package/src/types/global-private.d.ts +2 -0
- package/src/types/internal.d.ts +51 -4
- package/src/types/synthetic/$env+static+private.md +1 -1
- package/src/utils/routing.js +1 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +266 -3
- package/types/index.d.ts.map +14 -1
- /package/src/{runtime → utils}/hash.js +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/** @import { RemoteQueryFunction } from '@sveltejs/kit' */
|
|
2
|
+
import { app_dir } from '__sveltekit/paths';
|
|
3
|
+
import { remote_responses, started } from '../client.js';
|
|
4
|
+
import { tick } from 'svelte';
|
|
5
|
+
import { create_remote_function, remote_request } from './shared.svelte.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} id
|
|
9
|
+
* @returns {RemoteQueryFunction<any, any>}
|
|
10
|
+
*/
|
|
11
|
+
export function query(id) {
|
|
12
|
+
return create_remote_function(id, (cache_key, payload) => {
|
|
13
|
+
return new Query(cache_key, async () => {
|
|
14
|
+
if (!started) {
|
|
15
|
+
const result = remote_responses[cache_key];
|
|
16
|
+
if (result) {
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const url = `/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`;
|
|
22
|
+
|
|
23
|
+
return await remote_request(url);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @template T
|
|
30
|
+
* @implements {Partial<Promise<T>>}
|
|
31
|
+
*/
|
|
32
|
+
export class Query {
|
|
33
|
+
/** @type {string} */
|
|
34
|
+
_key;
|
|
35
|
+
|
|
36
|
+
#init = false;
|
|
37
|
+
/** @type {() => Promise<T>} */
|
|
38
|
+
#fn;
|
|
39
|
+
#loading = $state(true);
|
|
40
|
+
/** @type {Array<() => void>} */
|
|
41
|
+
#latest = [];
|
|
42
|
+
|
|
43
|
+
/** @type {boolean} */
|
|
44
|
+
#ready = $state(false);
|
|
45
|
+
/** @type {T | undefined} */
|
|
46
|
+
#raw = $state.raw();
|
|
47
|
+
/** @type {Promise<void>} */
|
|
48
|
+
#promise;
|
|
49
|
+
/** @type {Array<(old: T) => T>} */
|
|
50
|
+
#overrides = $state([]);
|
|
51
|
+
|
|
52
|
+
/** @type {T | undefined} */
|
|
53
|
+
#current = $derived.by(() => {
|
|
54
|
+
// don't reduce undefined value
|
|
55
|
+
if (!this.#ready) return undefined;
|
|
56
|
+
|
|
57
|
+
return this.#overrides.reduce((v, r) => r(v), /** @type {T} */ (this.#raw));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
#error = $state.raw(undefined);
|
|
61
|
+
|
|
62
|
+
/** @type {Promise<T>['then']} */
|
|
63
|
+
// @ts-expect-error TS doesn't understand that the promise returns something
|
|
64
|
+
#then = $derived.by(() => {
|
|
65
|
+
const p = this.#promise;
|
|
66
|
+
this.#overrides.length;
|
|
67
|
+
|
|
68
|
+
return async (resolve, reject) => {
|
|
69
|
+
try {
|
|
70
|
+
await p;
|
|
71
|
+
// svelte-ignore await_reactivity_loss
|
|
72
|
+
await tick();
|
|
73
|
+
resolve?.(/** @type {T} */ (this.#current));
|
|
74
|
+
} catch (error) {
|
|
75
|
+
reject?.(error);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {string} key
|
|
82
|
+
* @param {() => Promise<T>} fn
|
|
83
|
+
*/
|
|
84
|
+
constructor(key, fn) {
|
|
85
|
+
this._key = key;
|
|
86
|
+
this.#fn = fn;
|
|
87
|
+
this.#promise = $state.raw(this.#run());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#run() {
|
|
91
|
+
// Prevent state_unsafe_mutation error on first run when the resource is created within the template
|
|
92
|
+
if (this.#init) {
|
|
93
|
+
this.#loading = true;
|
|
94
|
+
} else {
|
|
95
|
+
this.#init = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Don't use Promise.withResolvers, it's too new still
|
|
99
|
+
/** @type {() => void} */
|
|
100
|
+
let resolve;
|
|
101
|
+
/** @type {(e?: any) => void} */
|
|
102
|
+
let reject;
|
|
103
|
+
/** @type {Promise<void>} */
|
|
104
|
+
const promise = new Promise((res, rej) => {
|
|
105
|
+
resolve = res;
|
|
106
|
+
reject = rej;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.#latest.push(
|
|
110
|
+
// @ts-expect-error it's defined at this point
|
|
111
|
+
resolve
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
Promise.resolve(this.#fn())
|
|
115
|
+
.then((value) => {
|
|
116
|
+
// Skip the response if resource was refreshed with a later promise while we were waiting for this one to resolve
|
|
117
|
+
const idx = this.#latest.indexOf(resolve);
|
|
118
|
+
if (idx === -1) return;
|
|
119
|
+
|
|
120
|
+
this.#latest.splice(0, idx).forEach((r) => r());
|
|
121
|
+
this.#ready = true;
|
|
122
|
+
this.#loading = false;
|
|
123
|
+
this.#raw = value;
|
|
124
|
+
this.#error = undefined;
|
|
125
|
+
|
|
126
|
+
resolve();
|
|
127
|
+
})
|
|
128
|
+
.catch((e) => {
|
|
129
|
+
const idx = this.#latest.indexOf(resolve);
|
|
130
|
+
if (idx === -1) return;
|
|
131
|
+
|
|
132
|
+
this.#latest.splice(0, idx).forEach((r) => r());
|
|
133
|
+
this.#error = e;
|
|
134
|
+
this.#loading = false;
|
|
135
|
+
reject(e);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return promise;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
get then() {
|
|
142
|
+
return this.#then;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
get catch() {
|
|
146
|
+
this.#then;
|
|
147
|
+
return (/** @type {any} */ reject) => {
|
|
148
|
+
return this.#then(undefined, reject);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get finally() {
|
|
153
|
+
this.#then;
|
|
154
|
+
return (/** @type {any} */ fn) => {
|
|
155
|
+
return this.#then(
|
|
156
|
+
() => fn(),
|
|
157
|
+
() => fn()
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
get current() {
|
|
163
|
+
return this.#current;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
get error() {
|
|
167
|
+
return this.#error;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Returns true if the resource is loading or reloading.
|
|
172
|
+
*/
|
|
173
|
+
get loading() {
|
|
174
|
+
return this.#loading;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Returns true once the resource has been loaded for the first time.
|
|
179
|
+
*/
|
|
180
|
+
get ready() {
|
|
181
|
+
return this.#ready;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @returns {Promise<void>}
|
|
186
|
+
*/
|
|
187
|
+
refresh() {
|
|
188
|
+
return (this.#promise = this.#run());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @param {T} value
|
|
193
|
+
*/
|
|
194
|
+
set(value) {
|
|
195
|
+
this.#ready = true;
|
|
196
|
+
this.#loading = false;
|
|
197
|
+
this.#error = undefined;
|
|
198
|
+
this.#raw = value;
|
|
199
|
+
this.#promise = Promise.resolve();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @param {(old: T) => T} fn
|
|
204
|
+
*/
|
|
205
|
+
withOverride(fn) {
|
|
206
|
+
this.#overrides.push(fn);
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
_key: this._key,
|
|
210
|
+
release: () => {
|
|
211
|
+
const i = this.#overrides.indexOf(fn);
|
|
212
|
+
|
|
213
|
+
if (i !== -1) {
|
|
214
|
+
this.#overrides.splice(i, 1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/** @import { RemoteQueryOverride } from '@sveltejs/kit' */
|
|
2
|
+
/** @import { RemoteFunctionResponse } from 'types' */
|
|
3
|
+
/** @import { Query } from './query.svelte.js' */
|
|
4
|
+
import * as devalue from 'devalue';
|
|
5
|
+
import { app, goto, invalidateAll, query_map } from '../client.js';
|
|
6
|
+
import { HttpError, Redirect } from '@sveltejs/kit/internal';
|
|
7
|
+
import { tick } from 'svelte';
|
|
8
|
+
import { create_remote_cache_key, stringify_remote_arg } from '../../shared.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param {string} url
|
|
13
|
+
*/
|
|
14
|
+
export async function remote_request(url) {
|
|
15
|
+
const response = await fetch(url);
|
|
16
|
+
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
throw new HttpError(500, 'Failed to execute remote function');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = /** @type {RemoteFunctionResponse} */ (await response.json());
|
|
22
|
+
|
|
23
|
+
if (result.type === 'redirect') {
|
|
24
|
+
// resource_cache.delete(cache_key);
|
|
25
|
+
// version++;
|
|
26
|
+
// await goto(result.location);
|
|
27
|
+
// /** @type {Query<any>} */ (resource).refresh();
|
|
28
|
+
// TODO double-check this
|
|
29
|
+
await goto(result.location);
|
|
30
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
31
|
+
throw new Redirect(307, result.location);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (result.type === 'error') {
|
|
35
|
+
throw new HttpError(result.status ?? 500, result.error);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return devalue.parse(result.result, app.decoders);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Client-version of the `query`/`prerender`/`cache` function from `$app/server`.
|
|
43
|
+
* @param {string} id
|
|
44
|
+
* @param {(key: string, args: string) => any} create
|
|
45
|
+
*/
|
|
46
|
+
export function create_remote_function(id, create) {
|
|
47
|
+
return (/** @type {any} */ arg) => {
|
|
48
|
+
const payload = stringify_remote_arg(arg, app.hooks.transport);
|
|
49
|
+
const cache_key = create_remote_cache_key(id, payload);
|
|
50
|
+
let entry = query_map.get(cache_key);
|
|
51
|
+
|
|
52
|
+
let tracking = true;
|
|
53
|
+
try {
|
|
54
|
+
$effect.pre(() => {
|
|
55
|
+
if (entry) entry.count++;
|
|
56
|
+
return () => {
|
|
57
|
+
const entry = query_map.get(cache_key);
|
|
58
|
+
if (entry) {
|
|
59
|
+
entry.count--;
|
|
60
|
+
void tick().then(() => {
|
|
61
|
+
if (!entry.count && entry === query_map.get(cache_key)) {
|
|
62
|
+
query_map.delete(cache_key);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
} catch {
|
|
69
|
+
tracking = false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let resource = entry?.resource;
|
|
73
|
+
if (!resource) {
|
|
74
|
+
resource = create(cache_key, payload);
|
|
75
|
+
|
|
76
|
+
Object.defineProperty(resource, '_key', {
|
|
77
|
+
value: cache_key
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
query_map.set(
|
|
81
|
+
cache_key,
|
|
82
|
+
(entry = {
|
|
83
|
+
count: tracking ? 1 : 0,
|
|
84
|
+
resource
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
resource
|
|
89
|
+
.then(() => {
|
|
90
|
+
void tick().then(() => {
|
|
91
|
+
if (
|
|
92
|
+
!(/** @type {NonNullable<typeof entry>} */ (entry).count) &&
|
|
93
|
+
entry === query_map.get(cache_key)
|
|
94
|
+
) {
|
|
95
|
+
// If no one is tracking this resource anymore, we can delete it from the cache
|
|
96
|
+
query_map.delete(cache_key);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
})
|
|
100
|
+
.catch(() => {
|
|
101
|
+
// error delete the resource from the cache
|
|
102
|
+
// TODO is that correct?
|
|
103
|
+
query_map.delete(cache_key);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return resource;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {Array<Query<any> | RemoteQueryOverride>} updates
|
|
113
|
+
*/
|
|
114
|
+
export function release_overrides(updates) {
|
|
115
|
+
for (const update of updates) {
|
|
116
|
+
if ('release' in update) {
|
|
117
|
+
update.release();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {string} stringified_refreshes
|
|
124
|
+
* @param {Array<Query<any> | RemoteQueryOverride>} updates
|
|
125
|
+
*/
|
|
126
|
+
export function refresh_queries(stringified_refreshes, updates = []) {
|
|
127
|
+
const refreshes = Object.entries(devalue.parse(stringified_refreshes, app.decoders));
|
|
128
|
+
if (refreshes.length > 0) {
|
|
129
|
+
// `refreshes` is a superset of `updates`
|
|
130
|
+
for (const [key, value] of refreshes) {
|
|
131
|
+
// If there was an optimistic update, release it right before we update the query
|
|
132
|
+
const update = updates.find((u) => u._key === key);
|
|
133
|
+
if (update && 'release' in update) {
|
|
134
|
+
update.release();
|
|
135
|
+
}
|
|
136
|
+
// Update the query with the new value
|
|
137
|
+
const entry = query_map.get(key);
|
|
138
|
+
entry?.resource.set(value);
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
void invalidateAll();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -125,4 +125,6 @@ export interface HydrateOptions {
|
|
|
125
125
|
server_route?: CSRRouteServer;
|
|
126
126
|
data: Array<ServerDataNode | null>;
|
|
127
127
|
form: Record<string, any> | null;
|
|
128
|
+
/** The results of all remote functions executed during SSR so that they can be reused during hydration */
|
|
129
|
+
remote: Record<string, any>;
|
|
128
130
|
}
|
|
@@ -176,10 +176,12 @@ function json_response(json, status = 200) {
|
|
|
176
176
|
* @param {Redirect} redirect
|
|
177
177
|
*/
|
|
178
178
|
export function redirect_json_response(redirect) {
|
|
179
|
-
return json_response(
|
|
180
|
-
type
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
return json_response(
|
|
180
|
+
/** @type {import('types').ServerRedirectNode} */ ({
|
|
181
|
+
type: 'redirect',
|
|
182
|
+
location: redirect.location
|
|
183
|
+
})
|
|
184
|
+
);
|
|
183
185
|
}
|
|
184
186
|
|
|
185
187
|
/**
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** @import { RequestEvent } from '@sveltejs/kit' */
|
|
2
|
+
/** @import { PrerenderOptions, ServerHooks, SSROptions, SSRState } from 'types' */
|
|
3
|
+
|
|
4
|
+
export const EVENT_STATE = Symbol('remote');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal state associated with the current `RequestEvent`,
|
|
8
|
+
* used for tracking things like remote function calls
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* prerendering: PrerenderOptions | undefined
|
|
11
|
+
* transport: ServerHooks['transport'];
|
|
12
|
+
* handleValidationError: ServerHooks['handleValidationError'];
|
|
13
|
+
* form_instances?: Map<any, any>;
|
|
14
|
+
* form_result?: [key: any, value: any];
|
|
15
|
+
* remote_data?: Record<string, Promise<any>>;
|
|
16
|
+
* refreshes?: Record<string, any>;
|
|
17
|
+
* }} RequestEventState
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {SSRState} state
|
|
22
|
+
* @param {SSROptions} options
|
|
23
|
+
* @returns {RequestEventState}
|
|
24
|
+
*/
|
|
25
|
+
export function create_event_state(state, options) {
|
|
26
|
+
return {
|
|
27
|
+
prerendering: state.prerendering,
|
|
28
|
+
transport: options.hooks.transport,
|
|
29
|
+
handleValidationError: options.hooks.handleValidationError
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns internal state associated with the current `RequestEvent`
|
|
35
|
+
* @param {RequestEvent} event
|
|
36
|
+
* @returns {RequestEventState}
|
|
37
|
+
*/
|
|
38
|
+
export function get_event_state(event) {
|
|
39
|
+
// @ts-expect-error the symbol isn't exposed on the public `RequestEvent` type
|
|
40
|
+
return event[EVENT_STATE];
|
|
41
|
+
}
|
|
@@ -111,6 +111,12 @@ export class Server {
|
|
|
111
111
|
(({ status, error }) =>
|
|
112
112
|
console.error((status === 404 && /** @type {Error} */ (error)?.message) || error)),
|
|
113
113
|
handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)),
|
|
114
|
+
handleValidationError:
|
|
115
|
+
module.handleValidationError ||
|
|
116
|
+
(({ issues }) => {
|
|
117
|
+
console.error('Remote function schema validation failed:', issues);
|
|
118
|
+
return { message: 'Bad Request' };
|
|
119
|
+
}),
|
|
114
120
|
reroute: module.reroute || (() => {}),
|
|
115
121
|
transport: module.transport || {}
|
|
116
122
|
};
|
|
@@ -124,14 +130,17 @@ export class Server {
|
|
|
124
130
|
if (module.init) {
|
|
125
131
|
await module.init();
|
|
126
132
|
}
|
|
127
|
-
} catch (
|
|
133
|
+
} catch (e) {
|
|
128
134
|
if (DEV) {
|
|
129
135
|
this.#options.hooks = {
|
|
130
136
|
handle: () => {
|
|
131
|
-
throw
|
|
137
|
+
throw e;
|
|
132
138
|
},
|
|
133
139
|
handleError: ({ error }) => console.error(error),
|
|
134
140
|
handleFetch: ({ request, fetch }) => fetch(request),
|
|
141
|
+
handleValidationError: () => {
|
|
142
|
+
return { message: 'Bad Request' };
|
|
143
|
+
},
|
|
135
144
|
reroute: () => {},
|
|
136
145
|
transport: {}
|
|
137
146
|
};
|
|
@@ -140,7 +149,7 @@ export class Server {
|
|
|
140
149
|
decoders: {}
|
|
141
150
|
});
|
|
142
151
|
} else {
|
|
143
|
-
throw
|
|
152
|
+
throw e;
|
|
144
153
|
}
|
|
145
154
|
}
|
|
146
155
|
})());
|
|
@@ -104,7 +104,7 @@ export async function handle_action_json_request(event, options, server) {
|
|
|
104
104
|
/**
|
|
105
105
|
* @param {HttpError | Error} error
|
|
106
106
|
*/
|
|
107
|
-
function check_incorrect_fail_use(error) {
|
|
107
|
+
export function check_incorrect_fail_use(error) {
|
|
108
108
|
return error instanceof ActionFailure
|
|
109
109
|
? new Error('Cannot "throw fail()". Use "return fail()"')
|
|
110
110
|
: error;
|
|
@@ -15,6 +15,7 @@ import { render_response } from './render.js';
|
|
|
15
15
|
import { respond_with_error } from './respond_with_error.js';
|
|
16
16
|
import { get_data_json } from '../data/index.js';
|
|
17
17
|
import { DEV } from 'esm-env';
|
|
18
|
+
import { get_remote_action, handle_remote_form_post } from '../remote.js';
|
|
18
19
|
import { PageNodes } from '../../../utils/page_nodes.js';
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -54,9 +55,15 @@ export async function render_page(event, page, options, manifest, state, nodes,
|
|
|
54
55
|
let action_result = undefined;
|
|
55
56
|
|
|
56
57
|
if (is_action_request(event)) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
const remote_id = get_remote_action(event.url);
|
|
59
|
+
if (remote_id) {
|
|
60
|
+
action_result = await handle_remote_form_post(event, manifest, remote_id);
|
|
61
|
+
} else {
|
|
62
|
+
// for action requests, first call handler in +page.server.js
|
|
63
|
+
// (this also determines status code)
|
|
64
|
+
action_result = await handle_action_request(event, leaf_node.server);
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
if (action_result?.type === 'redirect') {
|
|
61
68
|
return redirect_response(action_result.status, action_result.location);
|
|
62
69
|
}
|
|
@@ -197,21 +197,27 @@ export async function load_data({
|
|
|
197
197
|
}) {
|
|
198
198
|
const server_data_node = await server_data_promise;
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
const load = node?.universal?.load;
|
|
201
|
+
|
|
202
|
+
if (!load) {
|
|
201
203
|
return server_data_node?.data ?? null;
|
|
202
204
|
}
|
|
203
205
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
206
|
+
// We're adding getRequestEvent context to the universal load function
|
|
207
|
+
// in order to be able to use remote calls within it.
|
|
208
|
+
const result = await with_event(event, () =>
|
|
209
|
+
load.call(null, {
|
|
210
|
+
url: event.url,
|
|
211
|
+
params: event.params,
|
|
212
|
+
data: server_data_node?.data ?? null,
|
|
213
|
+
route: event.route,
|
|
214
|
+
fetch: create_universal_fetch(event, state, fetched, csr, resolve_opts),
|
|
215
|
+
setHeaders: event.setHeaders,
|
|
216
|
+
depends: () => {},
|
|
217
|
+
parent,
|
|
218
|
+
untrack: (fn) => fn()
|
|
219
|
+
})
|
|
220
|
+
);
|
|
215
221
|
|
|
216
222
|
if (__SVELTEKIT_DEV__) {
|
|
217
223
|
validate_load_response(result, node.universal_id);
|
|
@@ -3,7 +3,7 @@ import { readable, writable } from 'svelte/store';
|
|
|
3
3
|
import { DEV } from 'esm-env';
|
|
4
4
|
import { text } from '@sveltejs/kit';
|
|
5
5
|
import * as paths from '__sveltekit/paths';
|
|
6
|
-
import { hash } from '
|
|
6
|
+
import { hash } from '../../../utils/hash.js';
|
|
7
7
|
import { serialize_data } from './serialize_data.js';
|
|
8
8
|
import { s } from '../../../utils/misc.js';
|
|
9
9
|
import { Csp } from './csp.js';
|
|
@@ -15,6 +15,8 @@ import { SVELTE_KIT_ASSETS } from '../../../constants.js';
|
|
|
15
15
|
import { SCHEME } from '../../../utils/url.js';
|
|
16
16
|
import { create_server_routing_response, generate_route_object } from './server_routing.js';
|
|
17
17
|
import { add_resolution_suffix } from '../../pathname.js';
|
|
18
|
+
import { with_event } from '../../app/server/event.js';
|
|
19
|
+
import { get_event_state } from '../event-state.js';
|
|
18
20
|
|
|
19
21
|
// TODO rename this function/module
|
|
20
22
|
|
|
@@ -189,14 +191,14 @@ export async function render_response({
|
|
|
189
191
|
};
|
|
190
192
|
|
|
191
193
|
try {
|
|
192
|
-
rendered = options.root.render(props, render_opts);
|
|
194
|
+
rendered = with_event(event, () => options.root.render(props, render_opts));
|
|
193
195
|
} finally {
|
|
194
196
|
globalThis.fetch = fetch;
|
|
195
197
|
paths.reset();
|
|
196
198
|
}
|
|
197
199
|
} else {
|
|
198
200
|
try {
|
|
199
|
-
rendered = options.root.render(props, render_opts);
|
|
201
|
+
rendered = with_event(event, () => options.root.render(props, render_opts));
|
|
200
202
|
} finally {
|
|
201
203
|
paths.reset();
|
|
202
204
|
}
|
|
@@ -386,7 +388,7 @@ export async function render_response({
|
|
|
386
388
|
blocks.push('const element = document.currentScript.parentElement;');
|
|
387
389
|
|
|
388
390
|
if (page_config.ssr) {
|
|
389
|
-
const serialized = { form: 'null', error: 'null' };
|
|
391
|
+
const serialized = { form: 'null', error: 'null', remote: 'null' };
|
|
390
392
|
|
|
391
393
|
if (form_value) {
|
|
392
394
|
serialized.form = uneval_action_response(
|
|
@@ -400,11 +402,35 @@ export async function render_response({
|
|
|
400
402
|
serialized.error = devalue.uneval(error);
|
|
401
403
|
}
|
|
402
404
|
|
|
405
|
+
const { remote_data } = get_event_state(event);
|
|
406
|
+
|
|
407
|
+
if (remote_data) {
|
|
408
|
+
/** @type {Record<string, any>} */
|
|
409
|
+
const remote = {};
|
|
410
|
+
|
|
411
|
+
for (const key in remote_data) {
|
|
412
|
+
remote[key] = await remote_data[key];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// TODO this is repeated in a few places — dedupe it
|
|
416
|
+
const replacer = (/** @type {any} */ thing) => {
|
|
417
|
+
for (const key in options.hooks.transport) {
|
|
418
|
+
const encoded = options.hooks.transport[key].encode(thing);
|
|
419
|
+
if (encoded) {
|
|
420
|
+
return `app.decode('${key}', ${devalue.uneval(encoded, replacer)})`;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
serialized.remote = devalue.uneval(remote, replacer);
|
|
426
|
+
}
|
|
427
|
+
|
|
403
428
|
const hydrate = [
|
|
404
429
|
`node_ids: [${branch.map(({ node }) => node.index).join(', ')}]`,
|
|
405
430
|
`data: ${data}`,
|
|
406
431
|
`form: ${serialized.form}`,
|
|
407
|
-
`error: ${serialized.error}
|
|
432
|
+
`error: ${serialized.error}`,
|
|
433
|
+
`remote: ${serialized.remote}`
|
|
408
434
|
];
|
|
409
435
|
|
|
410
436
|
if (status !== 200) {
|