Skip to content

Commit c5a9794

Browse files
authored
fix: serialize server load data before passing to universal load, to handle mutations (#14268)
* failing test * fix: serialize server `load` data before passing to universal `load`, to handle mutations * ugh
1 parent f09de07 commit c5a9794

File tree

3 files changed

+46
-7
lines changed

3 files changed

+46
-7
lines changed

.changeset/clever-boxes-feel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: serialize server `load` data before passing to universal `load`, to handle mutations

packages/kit/src/runtime/server/page/load_data.js

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { DEV } from 'esm-env';
2+
import * as devalue from 'devalue';
23
import { disable_search, make_trackable } from '../../../utils/url.js';
34
import { validate_depends, validate_load_response } from '../../shared.js';
45
import { with_request_store, merge_tracing } from '@sveltejs/kit/internal/server';
56
import { record_span } from '../../telemetry/record_span.js';
6-
import { get_node_type } from '../utils.js';
7+
import { clarify_devalue_error, get_node_type } from '../utils.js';
78
import { base64_encode, text_decoder } from '../../utils.js';
89

910
/**
@@ -232,20 +233,47 @@ export async function load_data({
232233
},
233234
fn: async (current) => {
234235
const traced_event = merge_tracing(event, current);
235-
return await with_request_store({ event: traced_event, state: event_state }, () =>
236-
load.call(null, {
236+
return await with_request_store({ event: traced_event, state: event_state }, () => {
237+
/** @type {Record<string, any> | null} */
238+
let data = null;
239+
240+
return load.call(null, {
237241
url: event.url,
238242
params: event.params,
239-
data: server_data_node?.data ?? null,
243+
get data() {
244+
if (data === null && server_data_node?.data != null) {
245+
/** @type {Record<string, (value: any) => any>} */
246+
const reducers = {};
247+
248+
/** @type {Record<string, (value: any) => any>} */
249+
const revivers = {};
250+
251+
for (const key in event_state.transport) {
252+
reducers[key] = event_state.transport[key].encode;
253+
revivers[key] = event_state.transport[key].decode;
254+
}
255+
256+
// run it through devalue so that the developer can't accidentally mutate it
257+
try {
258+
data = devalue.parse(devalue.stringify(server_data_node.data, reducers), revivers);
259+
} catch (e) {
260+
// @ts-expect-error
261+
e.path = e.path.slice(1);
262+
throw new Error(clarify_devalue_error(event, /** @type {any} */ (e)));
263+
}
264+
}
265+
266+
return data;
267+
},
240268
route: event.route,
241269
fetch: create_universal_fetch(event, state, fetched, csr, resolve_opts),
242270
setHeaders: event.setHeaders,
243271
depends: () => {},
244272
parent,
245273
untrack: (fn) => fn(),
246274
tracing: traced_event.tracing
247-
})
248-
);
275+
});
276+
});
249277
}
250278
});
251279

packages/kit/test/apps/basics/src/routes/load/serialization/+page.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,11 @@ export async function load({ fetch, data, url }) {
55
const res = await fetch(new URL('/load/serialization/fetched-from-shared.json', url.origin));
66
const { b } = await res.json();
77

8-
return { a, b, c: a + b };
8+
// check that this doesn't mutate the original object
9+
// and make the server data unserializable
10+
// @ts-expect-error
11+
data.sum = () => a + b;
12+
13+
// @ts-expect-error
14+
return { a, b, c: data.sum() };
915
}

0 commit comments

Comments
 (0)