-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make it possible to return more than JSON from server-only load
functions
#6008
Comments
Is it sensible to make it a build time configuration concern whether to use JSON or devalue for a given page? (We obviously don't want to be doing this analysis at runtime and the client would have no way of knowing the result of this analysis ahead of time.) Explicitly opting in to the more performant JSON might be nice. |
…g]/+page.server.ts fixes error reported in sveltejs/kit#6093 might be fixed upstream in sveltejs/kit#6008
This comment was marked as off-topic.
This comment was marked as off-topic.
My last comment was on-topic. devalue doesn't support ObjectIds and they get blocked despite passing a |
Currently, What I'm not sure of is if there's a reason for that. Is this due to concerns over deserialization or typings? |
We're currently specifically not checking for |
That makes sense. While a bit of a performance hit, would it not make sense to run serverside through a serde pass-through? If so, the need for edit: I guess this could cause an issue with typings. |
The current plan is to use |
@Conduitry thanks for your immediate feedback.
This is really nice and congrats to Rich's work on devalue but both reasons do not address the interests of your users and shouldn't be considered.
How can devalue have better perf if it doesn't support all the types nor recipes as superjson does? My feeling is that you hesitate to integrate a lib from a competing stack (Blitz) for whatever reason. This is ok and I do not care for now. But, I'd welcome some outlook and more that we address this post 1.0.0 ETA. The users need to migrate to the latest next version now because the longer they wait the harder the migration will get bc of the major routing overhaul. This silent change and intro of Hence, I'd appreciate if you guys rethink you decision for/against superjson and/or commit to some ETA for the integration of devalue in SvelteKit + integration of all lacking types. Would be also great if @Rich-Harris could give his take, I mean what is your recommendation now for users like us, wait and hope? |
As you know from my other issue, i'd also like to see the possibility to customize serialization/deserialization to accommodate for custom user needs. (like cbor, superjson, devalue, ...) Seems like an easy abstraction to apply. |
This sounds helpful. I've just bumped into the situation where serializing the A potential fix could be borrowed from I'd like to see something similar in SvelteKit for page endpoints. This could be added to the
The When the hook is set, SvelteKit would automatically The advantage over providing a blessed implementation like My own use case involves passing If this is useful, I can create a pull request. |
+1 for an approach that allows plugging a serializer like superjson. I already use it with trpc to serialize Temporal date objects and it's a dream. |
@Rich-Harris why do you want wait so long? Users face breaking/blocking changes now and shall wait for some months? Because devalue isn't ready? @ifiokjr offered already a PR/help, for what are you waiting? Looking fwd to your feedback! |
Alright, since I'm getting called out via childish memes let me articulate why we are planning to use These are our options:
The one limitation of Bear in mind that you already have the ability to (de)serialize data however you want by returning JSON-able data from Given all these trade-offs, |
Where did I say anything about delaying?
People have been using Not sure what you hope to achieve by sending a spammy GitHub notification to my boss, but maybe rethink that? |
Does it need to emit JSON in order to be a serializer? I thought an arbitrary string / byte array could be the result of the serialization, so devalue would in my opinion fit as a default choice when allowing a custom serializer. You get:
|
Since As for byte arrays: yes, if you can take the whole |
@Rich-Harris, to make it clear, I love what you're doing with svelte. As a maintainer of much smaller open-source projects, I'm amazed at how well you've been able to communicate and implement such an ambitious vision. Regarding the childish memes, I've found it's always the people who produce nothing of value that resort to such pettiness.
I was not aware of this. I'll try it out. The new routing API means I'm still getting to grips with what's possible. |
Since you intro'd the serializability checker, responses containing ObjectIds don't go through anymore. All SSR endpoints including ObjectIds broke, every single one. Now, we would need to filter out or transform ObjectId values on app or Mongo level before sending them. This creates unnecessary run(s) and perf hits. I wouldn't even need to have ObjectId deserialized back to ObejctIds, they work as string well (if you always wrap them in a new ObjectId()). Just allow ObjectId's in the checker.
Few days ago before the checker was introduced, everything was working fine. I've the feeling you are not even aware that you created a blocking change.
Sounds like a bigger hassle and wouldn't I lose types?
Then we'd lose SSR and what's the point of SvelteKit anymore?
In your og post, I quoted this already: "we address this post 1.0.0 ETA"
Aren't cyclical references an anti-pattern anyway? To move forward: Just make devalue the default but allow custom serializers. And latter pls ASAP or remove the checker or allow ObjectIds in the checker. |
Considering devalue will serialize functions, are you handing folks a footgun with this? eval(`(${devalue_serialized})`) Sorry the question is offtopic. The reason I'm asking is because that right now, folks who use devalue had to go looking for it and thus (hopefully) have some inclination as to how to implement it. Even if that knowledge comes at the expense of debugging. I'm not sure that will be the case if its baked into a framework. |
I've just added this to my project, and it works perfectly! 🎉 For those who bump into this issue. Here's some sample code.
// src/routes/[name]/+page.server.ts
import superjson from 'superjson';
import fetchSomething from '$lib/fetch-something';
import type { PageServerLoadEvent } from './$types.js';
export async function load(event: PageServerLoadEvent) {
const { name } = event.params;
const something = await fetchSomething(name);
// Serialize the data that is returned.
return superjson.serialize(something);
}
// src/routes/[name]/+page.ts
import superjson, { type SomethingType } from 'superjson';
import type { PageLoadEvent } from './$types.js';
export async function load(event: PageLoadEvent) {
// Deserialize the data provided
const something: SomethingType = superjson.deserialize(event.data);
// Return the deserialized data.
return { something }
}
<script lang="ts">
// src/routes/[name]/+page.svelte
import type { PageData } from './$types.js';
export let data: PageData;
const decoder = new TextDecoder('utf8');
$: base64Image = btoa(decoder.decode(data.something.image));
</script>
<h1>{data.something.name}</h1>
<p>{data.something.description}</p>
<img src="data:image/jpeg;charset=utf-8;base64, ${base64Image}" alt="{data.something.alt}" /> Knowing that this is possible, I completely understand your desire to use |
@ifiokjr you created additional runs (=perf hits), code gets boilerplate-y/bloated and not sure if you keep TS types (can you check pls?). idk if that's the solution we're looking for... |
@2foo dude, he gave you the code. Check for yourself. |
No you do not. |
wdym? |
@2foo Ugh. Here we go, one by one:
Prior to this change, people were returning
It's a hassle if you insist on trying to return non-serializable data. As I said, you can create a helper function that does it for you. No, you wouldn't lose types, unless you're using
The only person on this thread who said anything along those lines is you. What the hell are you talking about? (Actually, don't respond — I genuinely don't care.)
No. But if you're going to continue to behave like this — making demands and (I'm guessing from contextual evidence that it was you) making memes deriding the makers of the open source software you use — then I would prefer that you go and use a different framework. The community doesn't need your negative energy, and I personally have better things to do than waste time engaging with you.
It's the same steps that SvelteKit would be doing on your behalf 🤦 @ifiokjr thanks, this is an awesome example! Note that devalue's 1.9kb is only added to your server-side code — on the client-side it's completely free (because it's just JavaScript)
It won't — it'll fail if you hand it a non-serializable object. I just threw this quick REPL together so you can get a feel for how it works: https://svelte.dev/repl/138d70def7a748ce9eda736ef1c71239?version=3.49.0 |
You're awesome. Keep up the good work. |
I should have read the devalue readme more closely. 🤦♂️ I saw this: devalue(obj); // '(function(a){a.a=1;a.b=2;a.c=3;a.self=a;return a}({}))' and assumed it was serializing functions. What I didn't read was
Anyway, thank you for taking the time to put together the example despite my egregious RTFM fail on my part. |
I was wondering if context: And then, few months ago, very naively, we attempted to use those semantic errors on the browser side ( edit: I realize it may be more a |
Errors are serialized as POJOs using this logic: kit/packages/kit/src/runtime/server/utils.js Lines 41 to 70 in b30d2b6
As such, the deserialized objects won't pass any if ($page.error.name === 'DatabaseError') { |
Mongo user here: You use stringified ObjectIds for URL params in the client without the need to deserialize them back, e.g. you get a list of bikes and want to link each entry to their detail pages: // Mongo sends this `result`:
[ { _id: new ObjectId("00000020f51bb4362eee2a4d"), name: 'fast bike'},
{ _id: new ObjectId("00000020f51bb4362eee2a5d"), name: 'turbo bike'} ]
// JSON.stringify(result) creates:
'[{"_id":"00000020f51bb4362eee2a4d","name":"fast bike"},{"_id":"00000020f51bb4362eee2a5d","name":"turbo bike"}]'
// in .svelte you can use both results with the same code
{#each bikes as bike}
<a href="/bikes/{bike._id.toString()}" />
{/each} So, you actually never deserialize All solutions for dealing with ObjectIds I can think of are subpar (but maybe there's a better way):
It's actually a tricky situation for SEO sites pulling tons of data from Mongo and need to be server-side rendered. |
I know Rich likely doesn't agree, but part of the solution in my opinion is to make the error a warning instead - you are warned that this doesn't deserialize correctly, but if you know what you are doing, you should be allowed to. |
@mostrecent I imagine warn if the |
@chanced right, while |
Kinda feels like the best thing to do (if you don't need to reconstruct the if (node._id) node._id = node._id.toString(); That logic could presumably live in a small wrapper around the mongo client. It's a very small amount of extra work but it prevents all the type safety issues etc that you'd inevitably run into by relying on the fact that (Seems like a better solution still would be for mongo to return serializable data in the first place, but like I say, I'm not a mongo user...) |
There could be In both go and rust, you can serde an I guess using something like superjson would make sense for people in this predicament. There really isn't a need to deserialize into a uuid/objectid though. |
@Rich-Harris definitely an option and I've been exploring various solutions since you posted this hint, from doing that in Mongo with For now, I'd like to put myself into your/Rich's shoes. You can imagine that I'm biased (we heavily use Mongo on a very large scale) but let's give it a try (this is not about Mongo vs other DBs!): First, I assume that Rich's (and Vercel's) goals are following—please correct me if I'm wrong:
Now, let's look at MongoDB. Mongo doesn't have the best reputation but it still shines in a few areas (I make it short just to give some background): It's pretty easy to scale writes as replica sets (multi-master), you can run it everywhere keeping costs low + flexibility high without being locked into one vendor (self-host, AWS, Google Cloud, Azure) and schemas, e.g. json-schemas, can bring you type-safety-wise quite far if you know what you're doing. Mongo has for sure drawbacks and please, let's not discuss what's the best DB is, it's more to give context to following data. How many users are using Mongo now? How many of them are potential SvelteKit users? Looking at this thread, not so many. But maybe this is because of the (currently) smaller user base of SvelteKit. If we look at npmtrends[1], we see that's it's not just popular but outperforms many others, indicated by driver downloads. This ignores users who write raw SQL queries or those from Dynamo and Firebase but should give a notion of how many users use Mongo. This could be also legacy users but we would need to dig deeper into this to have full clarity. At least we know, it's definitely not a small user base. Back to SvelteKit: This current ObjectId issue is definitely solvable and people who are already invested will invest further to solve this problem somehow. The question is though, will new users see over this ObjectId issue and invest the same time and energy to make ObjectIds work? The solution is not that straight-forward as we have seen in this thread and could create enough headache to let users give up (because they aren't invested yet). Or would they just switch to a "safer" choice? Because of SEO? We know SSR is still highly crucial, if not the key requirement for non-app sites. The trade-off is:
Most of us will have a quick and good answer ready in their mind. But every decision will come with a sacrifice and we should be aware of its impact and maybe sleep a night over it. The next chart is from db-engines which has a specific ranking algorithm which can be questioned. I wanted just to add some non-scientific market share data quickly to prove that there's a huge user base and not that it's a better DB than others: |
Again, we're not going to start shoving hacks in the framework just because some people use mongo. All that would happen is people would start wondering why their |
I agree, here's the gist. I could get it fast but it messes up TS types: When using this as standard wrapper all ObjectId props need to be typed as string. Then, you can't save ObjectId props as such anymore, e.g., foreign keys, because Mongo expects type ObjectId and you can't give just strings. And no you can't convert them automatically back to ObjectIds before entering the DB again or do other clever type hacks. This is even more sad, considering that Mongo's native driver has excellent TS support. You might think that these are just nuanced problems of some users, but no, it's makes using SvelteKit/SSR + Mongo impossible. |
I don't understand why converting back to ObjectId before entering the DB is not possible, intentionally in mongodb you wouldn't want to modify ObjectID properties since they are immutable. You can create new ObjectId from string in an object if you want mongodb to find it based on that object ID. As for serialization/deserialization, there is the EJSON component from the bson package that mongodb is using that contains a serialize and deserialize function. This works for me at least to solve the issue of getting an error with the _id and keeps the functions of toHexString() etc intact. Unless I'm misunderstanding something then please correct me
|
@dhr-nl thanks for the hint, bson's EJSON which is pretty much the perfect de-/serializer which handles everything (ObjectId, Date, etc.) @Rich-Harris & @dummdidumm is it technically possible to let the user set the de-/serializer and use EJSON instead of devalue? |
I think the EJSON over-head and then running it through devalue shouldn't be a real problem, so my example above could be the only boilerplate code required to completely use EJSON on backend and front-end alike. Devalue actually works great for majority of use cases and the additional complexity when de-serializers are customizable might not be worth pursuing it. |
This issue would definately be a nice to have. I'm trying to return a typescript class and I need to convert it into an interface or a plain object when I return it, making me need to make an ugly pojo serialization wrapper for a nice class-based api wrapper. |
For MongoDB users, this issue now affects the possibilities to use Streamed responses. If anyone found a solution, would love to know! |
…g]/+page.server.ts fixes error reported in sveltejs/kit#6093 might be fixed upstream in sveltejs/kit#6008
@Rich-Harris I think you should take another look at supporting custom class serialisation. We also have this issue with SurrealDB that uses The core of the issue here is that there is a type mismatch from the load functions.The IDE expects a class, but the client receives the JSON format of |
I would also like to use https://deepkit.io/library/type. |
Describe the problem
Right now, the output of
load
in+layout.server.js
or+page.server.js
has to be serialized withJSON.stringify
so that it can be transported to the client.Occasionally it's useful to transport values that can't be serialized as JSON.
Date
springs to mind, along with things like this, where anything expectingselected
to be a member ofoptions
post-deserialization will be disappointed:Describe the proposed solution
We have a proven technique for this: https://github.com/rich-harris/devalue. It's fast, and doesn't involve a deserialization step. (It doesn't currently support BigInt though, we'd need to add that.)
To work with CSP, we can't
eval
the result (including by creating a<script>
), we need toimport
it. Since we need to get fresh data when navigating back to a previously-visited page, we need to use a cachebusting mechanism; since modules live in a cache that we have no way of purging, we need to be careful to avoid memory leaks.We can't therefore do this...
...and must instead do this, so that the data can be garbage collected once it's been used:
On the client side, it would look something like this:
Alternatives considered
There's a variety of alternatives to devalue listed here: https://github.com/rich-harris/devalue#see-also, though I'm not aware of any reasons to favour them.
Or, we could just stick to JSON, which has better performance characteristics if you have a huge amount of data.
Importance
nice to have
Additional Information
No response
The text was updated successfully, but these errors were encountered: