Speaker 1: Hello, everyone. Welcome to another episode of Svelte Radio. I'm here today with Simon
Speaker 1: from the Svelte team.
Speaker 1: Hello.
Speaker 1: Hey, welcome for having me.
Speaker 1: Thank you
Speaker 2: for having me.
Speaker 2: Not welcome.
Speaker 3: Great intro.
Speaker 2: Stumbling upon
Speaker 3: my words right
Speaker 2: away.
Speaker 1: When it's digital,
Speaker 1: am I visiting you
Speaker 1: or are you visiting me?
Speaker 1: You know?
Speaker 3: I'd say I
Speaker 1: visit you.
Speaker 1: Oh, okay.
Speaker 1: I was trying to save you here
Speaker 1: with the welcome.
Speaker 3: Yeah.
Speaker 1: Yeah, it's beyond repair.
Speaker 1: Yeah.
Speaker 1: All right, all right.
Speaker 1: So before we start, Anthony and Brittany were supposed to be here, but we ran into a couple of scheduling issues.
Speaker 1: And I decided I'm just going to do this on my own because it's easier.
Speaker 1: And we want to get this out there because it's really interesting and fun.
Speaker 1: The stuff that we're going to talk about today.
Speaker 1: Remote functions, async, svelte, I guess that's what you would call it?
Speaker 1: Yes.
Speaker 1: Kind of two parts, yeah.
Speaker 1: So maybe start off with what are remote functions in SvelteKit?
Speaker 1: So remote functions
Speaker 2: are a new way to interacting with data in your SvelteKit applications.
Speaker 2: Today, you have load functions and form actions to do that.
Speaker 2: So load functions you have in your plus page TS or plus page.server TS file,
Speaker 2: export a load function and you do things in there.
Speaker 2: You can also have layout load functions.
Speaker 2: And that's how you can get your data.
Speaker 2: And if you want to interact with that data, not just read it, but also manipulate it,
Speaker 2: the first class way to do that is forum actions, which live in plus page.server.ts files.
Speaker 2: And basically, it's a nice way of writing regular forum posts,
Speaker 2: just like basic web form posts.
Speaker 2: And this has served us very well so far.
Speaker 2: But over the years, a few things have become apparent
Speaker 2: that it just doesn't like scale as much as
Speaker 1: you'd like,
Speaker 2: like both scale up, but also scale down.
Speaker 2: I know.
Speaker 2: I know one--
Speaker 1: sorry, I didn't mean to cut you off.
Speaker 1: But I know one thing I've always gotten a bit irritated
Speaker 1: is that
Speaker 3: when you have
Speaker 1: one page
Speaker 1: and you have a bunch of different kinds of data
Speaker 1: that you want to bring into the page
on first load,
but then you might want to refresh just part of the data.
At the moment, with load functions,
you have to rerun the whole thing, right?
And then you would fetch everything from...
Correct.
That's one of the drawbacks
Speaker 2: that you can only get as granular with loading
Speaker 2: or reloading as a load function.
Speaker 2: So I don't know if you want to only reload a certain thing,
Speaker 2: then maybe you, I don't know, need to introduce a layout
Speaker 2: that is UI
Speaker 3: -wise empty
Speaker 2: just to split it apart.
Speaker 2: And that's like really clunky.
Speaker 2: So yeah, you're
Speaker 3: forced to...
Speaker 3: You can use groups,
Speaker 2: right?
Speaker 2: You could use
Speaker 1: groups.
Speaker 3: Nested groups.
Speaker 2: Yes.
Speaker 2: And yeah, so there are ways around it with enough like painkillers besides you.
Speaker 2: But yeah, it's not good.
Speaker 2: It also, another problem with loading data like that is that the co-location is not great.
Speaker 2: So you're loading your data in all these load functions.
Speaker 2: And then maybe you're using part of that data only very deep inside your page, like, I don't know, five components deep.
Speaker 2: And then you
Speaker 3: have either
Speaker 2: the choice to do prop drilling or go with the page store, which basically is a collection of all the data on that page.
Speaker 2: But that is no longer really type safe.
Speaker 2: So, yeah, that's another problem here.
Speaker 2: And also keeping those things in sync isn't as nice
Speaker 2: because maybe you want to refactor something
Speaker 2: or delete that data,
Speaker 2: but then you have to always think about the other end
Speaker 2: where you also need to change or delete your code.
Speaker 2: And speaking of TypeScript,
Speaker 2: the same is true for forum actions.
Speaker 2: So they work really nicely the way you author them,
Speaker 2: but it's not exactly type safe
Speaker 2: because the way you do it is you write basically the action path for the form.
Speaker 2: You write yourself and then you have to be sure that it's in sync
Speaker 2: with whatever you have in your plus page.server.ts file.
Speaker 2: And we have also heard people saying like,
Speaker 2: hey, I have this in my plus page.server.ts file,
Speaker 2: but maybe I want to use this on several pages.
Speaker 2: And so how can I do this?
Speaker 2: And so there are multiple, basically, problems to that too.
Speaker 2: And so also, what if you don't want to use form actions?
Speaker 2: So, I mean, we, of course, advocate for progressive enhancement,
Speaker 2: which means everything ideally should also work without JavaScript.
Speaker 2: But maybe you're doing some internal business app
Speaker 2: or the thing you're interacting with requires JavaScript?
Speaker 2: And then why would you want to use forms in that case?
Speaker 2: And SvelteKit basically offers you nothing in that regard.
Speaker 2: So you're completely on your own, just using
Speaker 1: regular fetch.
Speaker 1: And so I've had to use, I mean, I would use,
Speaker 1: so I always use SuperForms to do forms in SvelteKit before.
Speaker 3: And that
Speaker 1: kind of works in the SPA way as well.
Speaker 1: But it's still, like, it's a bit weird.
Speaker 1: You're kind of, what's it called?
Speaker 1: Faking forms in a way.
Speaker 1: Because you're using super forms and it kind of looks like you're using form actions, but you're not really.
Speaker 1: Because it's all SPA networks.
Speaker 1: But it would be better if it was easier, better DX.
Speaker 2: Right. And so we were thinking, okay, how can we basically keep the niceness about data loading and make it more granular, more type safe, give a better first class integration with when you don't want to use forms and so on.
Speaker 2: And this is how remote functions came to be.
Speaker 2: And it all started back in May when we had our offsite, basically.
Speaker 2: So prior to the Svelte Summit, many of the maintainers came together and we did a brainstorming.
Speaker 2: And previously, we already had written up some discussions.
Speaker 2: But yeah, the bulk of the design happened in those two to three days prior to the conference.
Speaker 2: like it's it's amazing what you can do when when you get people in a room yeah and and talk to each
Speaker 2: other live so i mean i'm a proponent of remote
Speaker 1: uh first i was gonna say the exact same thing
Speaker 2: yeah right like remote first
Speaker 1: is nice but it's hard things like this it's
Speaker 2: hard to beat in person
Speaker 2: right especially when you consider that many of us are not working on this full-time and to have
Speaker 2: these voices in the same room still and not having to wait on them like I don't know four days until
Speaker 2: they give a response that that is so valuable and so yeah we we got out with remote functions and
Speaker 2: that is basically I would say our take on RPC so it looks like you write regular functions and call
Speaker 2: regular functions and on the server that is what you are doing but on the client you're actually
Speaker 2: doing a fetch call to the server and the server knows how to basically deal with this special call
Speaker 2: call the actual function and then come back to the client where it can unpack the serialized
Speaker 2: response. And more specifically, you write your remote functions in.remote.ts or.remote.js
Speaker 2: files. So for example, I don't know, if you have a blog post and then maybe you do like a
Speaker 2: blog.remote.ts file. And in there you can then have a so-called query function,
Speaker 2: query remote function. So you import all these functions from $app.server.
Speaker 2: and for get posts you would do a query and in there you would query your database and return
Speaker 2: the list of posts when you are creating a new blog posts you would use the form function which
Speaker 2: yeah you give a schema standard schema like zod or valley bot and then that parses the form
Speaker 2: gives you back an object from that and then you can interact with that and i don't know do a few
Speaker 2: checks like are you authenticated are you authorized to do a blog post to create a new entry and so on
Speaker 2: and then call the database.
Speaker 2: And you can even right then and there
Speaker 2: do something like a single flight mutation
Speaker 2: where you're saying,
Speaker 2: okay, I want to refresh get posts.
Speaker 2: So as part of the response,
Speaker 2: you're also already getting the new data.
Speaker 2: And you could maybe also have a toggle like in there.
Speaker 2: So as a reader, you could press the like button
Speaker 2: and maybe that is for some reason
Speaker 2: not a form under the hood, but it's a so-called command.
Speaker 2: So that's the third one you have.
Speaker 2: And the command works very similar to the form.
Speaker 2: You also have a schema if you have arguments.
Speaker 2: In case of toggle like, you probably don't have arguments and so on.
Speaker 2: And so basically you have all these different functions,
Speaker 2: which all are called remote functions,
Speaker 2: and you have them inside this.remote.ts file.
Speaker 2: And then in your plus page, Svelte or any other Svelte file or regular file,
Speaker 2: you can just, you just can interact with them like regular functions, which return promises.
Speaker 1: So things
Speaker 2: that
Speaker 1: come out of this is like, we get full type safety, right?
Speaker 1: Yes.
Speaker 1: Without any magical kind of things that, because before we had, you had to do some
Speaker 1: magic under the hood, right, for the load functions,
Speaker 1: or am I misremembering?
Speaker 2: Yes, so, right.
Speaker 2: So one of the,
Speaker 2: that's another thing that gets better here.
Speaker 2: So for load functions,
Speaker 2: to make them type safe,
Speaker 2: we had to do a bit of TypeScript voodoo.
Speaker 2: Like if you do let brackets data equals dollar props,
Speaker 2: Like there's zero types, type safety.
Speaker 2: Yeah.
Speaker 2: So you don't write the types yourself.
Speaker 2: It's done in the background for you by the language service.
Speaker 2: You could also write it explicitly by importing page props from a relative file called dollar types, which doesn't
Speaker 3: exist.
Speaker 3: And
Speaker 2: at this position, it actually exists somewhere in the generated files, which is another TypeScript trick which you can employ to make that
Speaker 3: work.
Speaker 2: And it works and it's nice, it's clever, but wouldn't it be better if we didn't have to do this?
Speaker 2: Because
Speaker 3: it's
Speaker 2: something we have to maintain.
Speaker 2: It may break down in some places and so on.
Speaker 2: And if we can just rely on regular TypeScript, so to speak, that would be even better.
Speaker 2: And yeah, with remote functions, from a TypeScript perspective, it's just that.
Speaker 2: It's just TypeScript functions.
Speaker 2: And so that's why TypeScript will have a much easier time with this and why type safety will
Speaker 2: be increased with this with less effort.
Speaker 2: Because it's not just that your load functions will have type safety.
Speaker 2: Also, your forms now will have type safety.
Speaker 2: And your commands will also have type safety.
Speaker 2: It's not actually functions, though, and that's because, like, on the client, you're actually doing a fetch call to the back end.
Speaker 2: And that's why we encourage people to use a schema
Speaker 2: whenever they require arguments for the commands or queries or forms.
Speaker 2: Because in theory, everyone could call this endpoint with rubbish
Speaker 2: and could try to break it.
Speaker 2: And
Speaker 1: so that's why schema is required.
Speaker 1: Yeah, they're not hidden or anything.
Speaker 2: Right.
Speaker 2: It's auto-generated and it has funky names.
Speaker 2: So it's not exactly predictable or anything, but it's still public after all.
Speaker 2: And so you have to be careful there.
Speaker 2: And that's why we encourage you
Speaker 1: to do this.
Speaker 1: So speaking of schemas, then, it's a bit interesting that we ended up, because I have some vague memory of the Svelte core maintainers not wanting to deal with validation in any sense, because it's like an extra thing to have to do.
Speaker 1: But I think since then, there's this, what's it called, standard schema that's come out from all of these validation libraries.
Speaker 3: So these remote
Speaker 1: functions, they have support for passing in, well, support for, you have to pass in something if you're taking a parameter, right? You have to pass in a schema.
Speaker 1: You
Speaker 2: could also do a quote as a string unchecked.
Speaker 2: That way you can bypass it.
Speaker 2: Like if you know what you're doing or you're using some very custom built validation library
Speaker 2: that is not standard schema compliant, then you can bypass it.
Speaker 2: But the name unchecked should already clue you in that this goes unchecked.
Speaker 2: Like the types say this is a string, but it's not necessarily a string.
Speaker 2: And so that's where the encouragement to use it comes in,
Speaker 2: but
Speaker 1: we don't force you to do it.
Speaker 1: So what does it look like?
Speaker 1: Let's say I have a blog and I want to fetch one blog post.
Speaker 1: So I do const get post equals query.
Speaker 1: And then what do I do to get the
Speaker 3: post?
Speaker 3: Yeah, if you're
Speaker 2: using ValleyBot, for example,
Speaker 2: then you would do v.string as the first argument,
Speaker 2: which is the first argument is the schema
Speaker 2: or the string unchecked.
Speaker 2: And you do v.string.
Speaker 2: And then the second argument is the function
Speaker 2: that is then called.
Speaker 3: And
Speaker 2: in that case, it's, I don't know, probably the ID.
Speaker 2: And
Speaker 3: ID would
Speaker 2: already be type checked in that sense.
Speaker 2: So you don't have to do like colon string yourself anymore.
Speaker 2: That's inferred
Speaker 3: from the schema.
Speaker 3: So in a sense,
Speaker 2: most of the time you're actually not typing anything more
Speaker 2: compared to not using a schema, which is kind of nice.
Speaker 2: Yeah, and then in there you would, I don't know, query
Speaker 3: your database,
Speaker 2: fetch fetch the blog post from wherever and return it in a way that svelte kit can
Speaker 2: then serialize it you serialize it and so on so as long as you use something that is
Speaker 2: devalue compliant then it works so like everything about json is okay dates are okay map is okay
Speaker 2: and you can even
Speaker 2: so SvelteKit has this transport
Speaker 2: transport hook
Speaker 2: where you can basically define
Speaker 2: custom classes
Speaker 2: and how they should be
Speaker 2: serialized, deserialized. You could even use that
Speaker 2: in there.
Speaker 2: I was going
Speaker 1: to ask about
Speaker 1: the types of data that you can
Speaker 1: send back and forth. I assume you can't
Speaker 1: do functions.
Speaker 1: Correct. You could do
Speaker 1: functions. You can do
Speaker 1: functions? No, you couldn't.
Speaker 1: okay I
Speaker 2: mean you could maybe you could have a special class that represents a function or
Speaker 2: in some
Speaker 3: way and then you
Speaker 2: have knowledge domain knowledge about how this should be deserialized
Speaker 2: and serialized and then
Speaker 1: but not like a generic function yeah but
Speaker 2: no generic functions so
Speaker 1: that
Speaker 1: I've tried the
Speaker 1: query functions a bit
Speaker 1: and I find that
Speaker 1: having to
Speaker 1: use a schema for
Speaker 1: my queries also makes
Speaker 1: it kind of encourages using
Speaker 1: schemas in the rest of the application
Speaker 1: as well, which is kind of nice.
Speaker 1: So that's another side effect, I guess,
Speaker 1: of remote functions,
Speaker 1: if you will, for me at least.
Speaker 2: Yeah,
Speaker 2: I think so too, that in general
Speaker 2: will encourage better practices
Speaker 2: and ultimately make your app more resilient.
Speaker 1: Yeah.
Speaker 1: Okay, so remote functions do rely on this other feature
Speaker 1: that was built for Svelte called,
Speaker 1: I don't know if you call it async Svelte or async?
Speaker 1: Yeah, async.
Speaker 3: Top level await.
Speaker 1: Async Svelte, okay, yeah.
Speaker 1: But maybe we can talk a bit about that.
Speaker 1: It was introduced, was it introduced at Svelte Summit or a bit before?
Speaker 1: And then Rich did a talk about, I don't remember.
Speaker 2: Yeah, it was introduced at Svelte Summit.
Speaker 2: That's correct.
Speaker 2: And remote functions also got like an outlook there.
Speaker 2: And yeah, so async Svelte, for those who don't know yet,
Speaker 2: basically for the longest time, people have asked,
Speaker 2: I want to use the await keyword in my components,
Speaker 2: preferably at the top level, but maybe also somewhere else.
Speaker 2: And so far that didn't work.
Speaker 2: The only way you could do that is either by using the await block,
Speaker 2: the curly braces hash await.
Speaker 2: And that's a bit like clumsy because it only works
Speaker 3: for
Speaker 2: one promise at a time.
Speaker 2: so you have multiple then you got to repeat this again and again and again and each of them are
Speaker 2: basically in their own life cycle of showing a loading screen and then showing the data and if
Speaker 2: you have i don't know 10 of this uh these on your page and it's like 10 loading spinners and it's
Speaker 2: like it makes for a horrible experience
Speaker 3: so so
Speaker 2: you want to coordinate that somehow ideally the other
Speaker 2: way you could work around it is by flattening the async into something synchronous by having like a
Speaker 2: resource like object where you have loading and current and error properties on an object and
Speaker 2: then you could use that but that also like it could be clumsy sometimes and could make it so that
Speaker 2: the coordination problem is still there like ideally you want to somehow have maybe define
Speaker 2: okay this is my boundary basically and that's where I want the one loading screen to appear
Speaker 2: and then once that's done I like I want to show that loading screen until all async functions
Speaker 2: are resolved and then go from showing that to showing the the end result and not have 10
Speaker 2: spinners i only have one spin spinner so to speak and that's what async spelled basically gives you
Speaker 2: it gives you the ability to use a weight inside the template and at the top level you can also
Speaker 2: use it inside derived so basically like asynchronous derivations that then also works
Speaker 2: um and you can wrap these in a boundary and give that boundary a pending snippet which then shows
Speaker 2: up on first render for as long as anything inside the boundary is still loading and that that doesn't
Speaker 2: have to be in the same component it could be 10 components deep so it's a runtime concept like the
Speaker 2: runtime knows where the nearest boundary is and what async work is still out pending and yeah and
Speaker 2: that that way you you can coordinate it so
Speaker 1: you mentioned uh you mentioned the word resources
Speaker 1: I've heard this word kind of in some places.
Speaker 1: Is that something special?
Speaker 3: No, I think it's just like a term
Speaker 1: that is
Speaker 2: more or less common in the async.
Speaker 2: When talking about async, you're talking about resources,
Speaker 2: or at least we in the team are calling it resources.
Speaker 2: Okay.
Speaker 2: In fact, we, so with remote functions, especially with queries and so on, like they give you nice ergonomics and people have rightfully asked us, hey, this requires a server because like the remote function lives on the server.
Speaker 2: So you have to have a server runtime.
Speaker 2: What about the SPA case?
Speaker 2: What if I want to use something like a query there too?
Speaker 2: And we are actually in the process of designing and implementing a resource-like API right now, which will bring the query-like capabilities to Svelte itself.
Speaker 2: And the client implementation of remote function queries will then likely use that under the hood.
Speaker 1: So you would kind of migrate the current remote functions
Speaker 1: to use that when
Speaker 2: the resource API is
Speaker 1: done.
Speaker 1: Right.
Speaker 1: Yeah, it's all connected.
Speaker 1: Yeah.
Speaker 1: That's kind of nice to hear that the SPA use case
Speaker 1: is getting some love
Speaker 1: because it's been a bit neglected, I feel like,
Speaker 1: in SvelteKit, at least.
Speaker 2: Yeah.
Speaker 2: Like you've always been able to use Svelte,
Speaker 1: right?
Speaker 2: That is true.
Speaker 2: Like, I...
Speaker 2: it felt a bit neglected
Speaker 2: and I appreciate that feedback
Speaker 2: and I get where it comes from.
Speaker 2: And I think with Async Svelte itself already,
Speaker 2: the SPA case already is easier,
Speaker 2: much easier to do
Speaker 2: because if you wanted to have proper data loading,
Speaker 2: a proper data loading story,
Speaker 2: you basically had to use SvelteKit.
Speaker 2: But now
Speaker 3: with async
Speaker 2: Svelte, where you can just slap in a weight in front of a fetch, for example, coordinating asynchronous work in SPA only applications, even applications that don't use SvelteKit, they just use Svelte.
Speaker 2: That's now much easier and much more doable compared to before.
Speaker 1: Yeah.
Speaker 2: We
Speaker 1: haven't had enough
Speaker 2: documentation on this topic though yet.
Speaker 2: So that's another part.
Speaker 2: The feeling comes from that many of our documentation is SSR and progressive enhancement centric.
Speaker 2: And I think we can do better here with having more for SPA use cases without like abandoning the progressive enhancement.
Speaker 1: yeah because that's an important part
Speaker 1: like the progressive enhancement and the
Speaker 1: kind of building for
Speaker 1: people that aren't necessarily
Speaker 1: using JavaScript in their
Speaker 1: browser
Speaker 2: or don't get it fast enough
Speaker 2: yeah
Speaker 1: for any reason that
Speaker 2: they don't have
Speaker 1: JavaScript
Speaker 1: working on the website on the page at the moment
Speaker 1: okay
Speaker 1: cool so
Speaker 1: that was ASIC Svelte
Speaker 1: there's talk
Speaker 1: and link. We talked a bit about queries. Anything else about queries that we should talk about? I
Speaker 1: know there's a query.batch where you can kind of fetch many things and then... Yeah, so now that we
Speaker 1: have this remote
Speaker 2: functions primitive, it's very fun to think about all the additional things we
Speaker 2: can bring to it on top.
Speaker 2: So
Speaker 2: query batch is
Speaker 2: one example.
Speaker 2: Just to quickly
Speaker 2: explain what query batch does, it's
Speaker 2: solving the so-called n
Speaker 2: plus one problem. So
Speaker 2: imagine you have a list of
Speaker 2: cities and for each city you want
Speaker 2: to get the
Speaker 2: weather data for that city.
Speaker 2: And
Speaker 2: I mean, you could have
Speaker 2: like a
Speaker 2: an endpoint which says okay let's get me the weather for all these cities and you could write
Speaker 2: it yourself but i don't know maybe the list changes and then you basically you you re-request
Speaker 2: the data for all the ones you already have and not just the i don't know five that are new or
Speaker 2: something so instead ideally what you want is to call on the client just get weather as a query
Speaker 2: for each of the cities but that means that you would in the worst case do um i don't know let's
Speaker 2: say it's a list of 20 and then you do 20 uh query requests and do therefore do query 10
Speaker 2: at 20 back-end requests and maybe each with i don't know in the case of whether it's probably
Speaker 2: easy but i don't know maybe this is behind some authentication and then for each thing you're
Speaker 2: doing the authentication dance doing some database request the database request maybe itself is
Speaker 2: already pretty complex and therefore taxing on the database and so on and you're doing that 20 times
Speaker 2: And instead, what you could do is basically do what the alternative is to do it, to do manually to bulk request 20 at one.
Speaker 2: But from the client, it looks like you're doing like 20 requests and query batch then batches them into one request.
Speaker 2: So on the client, you're calling this with one city each.
Speaker 2: And on the backend, you receive an array of cities.
Speaker 2: So you only do one request, only do one response.
Speaker 2: And on the client, it can then spread it out again to the right requests.
Speaker 2: And that's what query batch does.
Speaker 1: So in my example of getting a blog post, this would be get the list of blog posts for the front page.
Speaker 1: Basically, you would use query batch there, I assume.
Speaker 2: If you want to have the details of all the blog posts, then yes, you could do that.
Speaker 2: I guess for the front page, I would probably still do one get posts, which has a different structure.
Speaker 2: like it's only containing
Speaker 3: some short
Speaker 2: summary
Speaker 2: and the title or something
Speaker 2: but yeah
Speaker 2: like it's not common
Speaker 2: but when you run into this
Speaker 2: it's kind of
Speaker 2: annoying and
Speaker 2: now with remote functions
Speaker 2: with this nice
Speaker 2: primitive we have
Speaker 2: the ability to also
Speaker 2: provide more quality
Speaker 2: of life functions
Speaker 2: that way. And if you don't use it, it's basically
Speaker 2: it's tree shakable. So if you don't use that, then you're also not paying for this.
Speaker 2: That's the best scenario.
Speaker 1: Getting a lot of functionality.
Speaker 2: Right. There's going to be others.
Speaker 2: Like we have to think about what we want
Speaker 2: to do with caching. This is an ongoing design
Speaker 2: question. Ideally, it's
Speaker 2: more than just set headers
Speaker 2: because
Speaker 2: setting headers and getting that right
Speaker 2: that's like really
Speaker 2: it's almost rocket science
Speaker 2: like setting
Speaker 2: the correct cache headers in the correct ways
Speaker 2: that's not easy
Speaker 2: and like many
Speaker 3: cloud providers
Speaker 2: have now
Speaker 2: more elaborate
Speaker 2: ways of doing caching and
Speaker 2: ideally we can on a provider basis
Speaker 2: integrate with that
Speaker 2: but having an
Speaker 3: API that basically
Speaker 2: makes this write ISR for example.
Speaker 2: So that's one thing we're talking about.
Speaker 2: We're talking about query.stream
Speaker 2: which basically allows you to stream data
Speaker 2: live data from the backend to the frontend.
Speaker 2: There's people who want this both ways
Speaker 2: in both directions.
Speaker 2: So something around WebSockets
Speaker 2: We have to think about that, see how that goes.
Speaker 2: So yeah, there's lots of possibilities
Speaker 2: and it's all going to revolve around the same primitives
Speaker 2: and the same ideas and APIs.
Speaker 2: And so I feel like it's going to become a really nice set of APIs,
Speaker 2: which will give you a good way for many of your use cases
Speaker 2: without restricting you or something
Speaker 2: and also without forcing you to use it all at once.
Speaker 2: You can explore over time and grow with it.
Speaker 1: Yes,
Speaker 2: I've
Speaker 1: tried it out for a bit
Speaker 1: in preparation of this interview
Speaker 1: and I experimented with moving.
Speaker 3: I've only had
Speaker 1: time to try out the query one,
Speaker 1: but just converting a load function into a query
Speaker 1: remote function super easy. I
Speaker 3: really
Speaker 1: enjoyed it. Like it's very nice. And you also get that like
Speaker 1: one thing I ran into, because I stupidly didn't read the documentation that well, was the like,
Speaker 1: how do I get access to locals? And then I discovered, what do you call it? Like get request
Speaker 1: event for this, which is pretty nice. And it's what I discovered as well. I didn't realize this
Speaker 1: at first, but you can use getRequestEvent
Speaker 1: in a function that you're using inside of a remote
Speaker 1: function. So at first I thought
Speaker 1: I would chain
Speaker 1: I would call a remote function inside of another remote function
Speaker 1: but then I realized why would I do that when I can just call
Speaker 1: a regular function? For some reason I didn't realize
Speaker 1: that I could do that.
Speaker 2: Yeah, that's definitely another beauty of this.
Speaker 2: It's all functions and it looks like functions,
Speaker 2: but it actually is just functions without any gotchas.
Speaker 2: So you could use other queries from within queries.
Speaker 2: You could use regular functions from within queries.
Speaker 2: So yeah, you can compose and nest this as you like,
Speaker 2: Which is really nice.
Speaker 2: And at
Speaker 3: the same time,
Speaker 2: because it's so like,
Speaker 2: because the boundary is the file,
Speaker 2: you don't
Speaker 3: run into
Speaker 2: any weird hiccups where, I don't know,
Speaker 2: you accidentally close over a variable
Speaker 2: and then somehow end up having a security leak.
Speaker 2: That's one of
Speaker 3: the reasons why
Speaker 2: we went with file boundaries
Speaker 2: compared to use server, for example.
Speaker 1: Ah, right, like in React, I guess.
Speaker 1: I have no idea how use server and stuff works in React.
Speaker 1: So, all right.
Speaker 1: So the experience is nice.
Speaker 1: I really enjoyed it.
Speaker 1: I haven't tried the form stuff yet,
Speaker 1: but I am sure I'll have a great time.
Speaker 1: But there's also like,
Speaker 1: I've heard that you're not quite done
Speaker 1: with how the form stuff works yet?
Speaker 1: Like you're evaluating, maybe changing it a bit.
Speaker 1: Is that right?
Speaker 1: Or am I misremembering maybe?
Speaker 2: Yeah, so there's a couple of ongoing tweaks right now.
Speaker 3: So we
Speaker 2: started out with form functions
Speaker 2: retrieving the regular form data,
Speaker 2: and then you have to like pull out the data yourself from there.
Speaker 2: We since then have switched it to requiring a schema
Speaker 2: as the first argument and then doing conversion logic there
Speaker 2: so that you get a regular pojo from form data,
Speaker 2: which is already much nicer.
Speaker 2: and the way you interact with the form on the client
Speaker 2: when, for example, you have, I don't know,
Speaker 2: you return a list of issues via the schema
Speaker 2: and then you can show that in the client.
Speaker 2: You can show the current value on the client.
Speaker 2: And the way this works is still a bit rough
Speaker 2: and we're in the works of tweaking this
Speaker 2: to give a much nicer API.
Speaker 2: So it's probably going to be something like this
Speaker 2: that you have a fields property on your form.
Speaker 2: And from there on, you just use regular.notation
Speaker 2: to go wherever you want to go in your model.
Speaker 2: And it's type safe because through the schema,
Speaker 2: it knows which things are available at which level.
Speaker 2: so in other words you don't have to have flat forms like it your your object can be nested
Speaker 2: you don't have to have it only one level deep yeah and through the stop mutation and then you can get
Speaker 2: a much easier time connecting that to your input so that things are in sync with the value and so
Speaker 2: on and yeah it's it's it's a bit hard to explain just
Speaker 1: over
Speaker 3: voice but yeah for sure yeah
Speaker 2: just just
Speaker 2: stay tuned it's it's gonna be out soon
Speaker 3: and um yeah
Speaker 2: it will make forms uh even easier to work with
Speaker 2: um yeah yeah so
Speaker 1: uh some questions there um with with regards to forms like form handling forms
Speaker 1: from end to end is like a huge undertaking, right?
Speaker 1: If
Speaker 3: you
Speaker 1: look at super forms, it's super complicated
Speaker 1: because there are so many edge cases
Speaker 1: and so much functionality that people want.
Speaker 1: How far do you think you guys will go with implement?
Speaker 1: Because validation is the first step, right?
Speaker 1: Because then you have to handle error messages.
Speaker 1: How do you display the error?
Speaker 1: Like where do you get the error messages from?
Speaker 1: Where do you, how do you handle like if the form has been touched, et cetera, et cetera?
Speaker 1: Like there's just like a, like
Speaker 2: a rabbit hole of stuff that you
Speaker 3: can end up implementing.
Speaker 2: Yeah, I mean, we started out without having a way to get issues because we didn't have schema yet.
Speaker 2: Now that we have a schema, through the schema, you can tell when a field fails,
Speaker 2: you can basically return a message along with it that is then sent back to the client.
Speaker 2: You can also now have so-called pre-flight, which is basically validation on the client as you type.
Speaker 2: So yeah, we have added more stuff because the community said like,
Speaker 2: oh yeah this is nice but do you know what would be even nicer if you
Speaker 1: also had
Speaker 2: this
Speaker 1: yeah just i mean just just opening the super forms documentation
Speaker 2: is basically like that
Speaker 1: that's how you
Speaker 1: end up with super forms or like all the fresh now because you kind of need that the
Speaker 2: goal so the
Speaker 2: the goal will explicitly not be to rebuild super forms into
Speaker 3: svelte
Speaker 2: kit so there will still be room
Speaker 2: for super form to to exist but the the goal basically is to solve the 80 percent use case
Speaker 2: and leave the rest of the 20 percent which are the ones where like you get increasingly
Speaker 2: diminishing returns
Speaker 3: yeah with
Speaker 2: even more edge cases and so on and to leave those
Speaker 2: out in favor of a consistent, concise API that is easy to use, to understand, but at the same time
Speaker 2: leave enough room for people to build something on top of it. So you could imagine a world in which
Speaker 2: maybe at some point SuperForms is built as an extension on top of remote form functions.
Speaker 1: That makes sense. I mean, the complexity would just like skyrocket if you would add all of the functionality and features that you would need from, if you wanted to build a one-to-one kind of thing, proper full featured form library, if that makes sense.
Speaker 2: That's correct.
Speaker 1: But that sounds like a good, like, building for 80% of the use cases sounds good.
Speaker 1: Anything else about forms that we should talk about?
Speaker 1: That nothing springs to mind right now.
Speaker 2: Just that, like, you can still redirect from there.
Speaker 2: You can still throw an arrow in there.
Speaker 2: So basically all the existing ways in which you used forms so far, they still continue to exist.
Speaker 1: - Okay, cool.
Speaker 1: So let's say not having used the form remote function yet.
Speaker 1: What if I, after posting some data to,
Speaker 1: let's say it's a to-do list and I'm,
Speaker 1: I want to post a new to-do,
Speaker 1: and then I want to refresh the data that I have on,
Speaker 1: on the, from my query that,
Speaker 1: that I use to fetch the to-dos, right?
Speaker 1: So let's say I have a query,
Speaker 1: a query remote function that's called get to-dos.
Speaker 1: And then I have a create to-do form remote function.
Speaker 1: what would be like an easy way to refresh the data?
Speaker 1: Because in the old SvelteKit way,
Speaker 1: you would just form, you would submit the form
Speaker 1: and then the load function would rerun.
Speaker 1: So how does it work now?
Speaker 2: So by default, it will refresh everything on the
Speaker 1: page
Speaker 1: to mirror the...
Speaker 1: So just like before.
Speaker 2: Just like before to mirror the non-progressive
Speaker 2: and the Hampton page case,
Speaker 2: in which case you would basically get a full page reload,
Speaker 2: which basically means you reload everything.
Speaker 1: So does it, how does, yeah.
Speaker 1: Sorry, no, I was going to say,
Speaker 1: like, how does it, does it just know,
Speaker 1: like, exactly what remote or query remote functions
Speaker 1: that are on the page?
Speaker 1: That's correct.
Speaker 1: So there's
Speaker 2: a, basically there's a hidden client cache
Speaker 2: that knows about all queries that are on the page.
Speaker 2: That also means that if you are using,
Speaker 2: I don't know, get user,
Speaker 2: as a query in three different places.
Speaker 2: You're not actually doing three different fetches.
Speaker 2: You're sharing
Speaker 3: the
Speaker 2: same instance under the hood.
Speaker 2: So it's all shared, cached, client under the hood,
Speaker 2: which is another nice thing
Speaker 2: because you don't have to worry about like,
Speaker 2: I don't know, hoisting your data loading up, for example.
Speaker 2: Oh, I need get user, the user data.
Speaker 2: I need that in my layout as well now.
Speaker 2: So today you wouldn't hoist your
Speaker 3: getUserFetcher
Speaker 2: up into the layout function.
Speaker 2: And with remote functions,
Speaker 2: you just do like await getUser right then and there,
Speaker 2: and that's it.
Speaker 2: And you don't, yeah, as I said,
Speaker 2: you're not doing an extra request that way.
Speaker 2: It's just, it's deduplicated.
Speaker 2: It
Speaker 1: knows about that.
Speaker 1: So if I have a query function that's called getPost,
Speaker 1: and then I want to show two posts,
Speaker 1: As long as I assume, like, if I supply two different query parameters, it would run the
Speaker 1: query twice?
Speaker 1: Yes, so the cache
Speaker 2: key is basically the ID of the remote function plus the stringified
Speaker 2: payload.
Speaker 2: Okay, cool.
Speaker 2: And that way, we know about all the existing query functions on the page.
Speaker 2: And that means when a remote form function runs, it knows which things to refresh.
Speaker 2: And by default, it will refresh everything.
Speaker 2: But you can also opt into more granular refreshes by doing a so-called single flight mutation,
Speaker 2: which means you are not only returning data or doing the form post,
Speaker 2: you're also telling the client,
Speaker 2: hey, and these are the things that I wanted to have refreshed
Speaker 2: and this is the new data from those queries.
Speaker 2: So basically, in your form function,
Speaker 2: if you do like, I don't know, create post,
Speaker 2: you would then at the end of your inside your form function,
Speaker 2: would do something like get posts dot refresh
Speaker 1: and then get posts is the query remote function
Speaker 1: that exists so right right
Speaker 2: so you invoke the query function call dot refresh on it
Speaker 2: and that way the remote function knows okay the user is interested in getting the new data now
Speaker 2: of this thing so i'm going to request the get posts now and i'm going going to wait on its
Speaker 2: result and once it's there i'm going to put it into basically a hidden field on the return
Speaker 2: um on the response and then the client knows ah okay there's this hidden field and these are the
Speaker 2: hash keys i need to update and it's going to do that and that way you get both a more granular
Speaker 2: refresh because you're no longer refreshing anything everything but only this one and at
Speaker 2: the same time you get a single flight mutation which means you're faster because you're doing
Speaker 2: the mutation plus the refresh at the same time instead of having to do one round trip to the
Speaker 2: server to get the response uh like okay the form has succeeded to post there and then do another
Speaker 2: round trip to the server to say okay and now give me the refreshed data you're doing it in one
Speaker 2: single flight
Speaker 1: invitation yeah well that i mean that's because then you reduce like uh what's it
Speaker 1: called uh i forget what it's called like when you have to go multiple times
Speaker 2: multiple round trips yeah
Speaker 2: Yeah, the round trips, right?
Speaker 1: Yeah.
Speaker 1: So this brings up another interesting question that I have.
Speaker 1: Like, are you batching all of the queries into one request
Speaker 1: or are they done one at a time?
Speaker 1: This is actually an
Speaker 2: open question still,
Speaker 2: if we do that or not.
Speaker 2: It really depends.
Speaker 2: And that's also, I guess this is a,
Speaker 2: Please give us feedback which way you'd like us to go or rather.
Speaker 2: Give us feedback on how many requests you're having on your page.
Speaker 2: Like, do you have a few requests at once or do you have like,
Speaker 3: I don't
Speaker 2: know,
Speaker 2: 30 requests going on in your network tab at the same time you're starting to get a bit worried.
Speaker 2: We have it still open as a design question because
So what we're going to do is either have a way to specifically batch things across queries or have a way to say explicitly, I don't want to have
Speaker 3: this be
Speaker 2: automatically batched because for some reason this query needs to be isolated because maybe I want to set headers on this or something.
Speaker 1: And
Speaker 2: for the same reason why we haven't done this automatic batching yet, for the same reason, you cannot use set headers yet on queries.
Speaker 2: For the same reason, because we don't know, okay, do we disallow it only on like the dedicated batching query?
Speaker 2: Or do we disallow it by default?
Speaker 2: And then you're using something like query.isolated.
Speaker 2: And then you can do set headers on there because you know this is only ever going to be this one.
Speaker 2: And yeah, so it's still an open question.
Speaker 2: There's a few considerations here.
Speaker 2: So obviously less requests is better.
Speaker 2: At the same time, if we batch requests, then we have to do post requests because like we, or rather we cannot cache things between.
Speaker 3: Right,
Speaker 2: right.
Speaker 2: so yeah
Speaker 1: it's still an open question
Speaker 2: but we will solve it one way
Speaker 2: or the other at some point
Speaker 1: okay
Speaker 1: cool
Speaker 1: so then there's also like two more
Speaker 1: I think you briefly mentioned
Speaker 1: command which is kind of like the
Speaker 1: I don't want to use
Speaker 1: the web platform
Speaker 3: I want to
Speaker 1: use
Speaker 2: just fetch
Speaker 2: right I want to
Speaker 2: I require my people
Speaker 2: my users to have
Speaker 2: JavaScript enabled, right. And then you can use commands. And yeah, commands are basically
Speaker 2: where previously you were on your own and had to do regular fetches. Now you can use commands,
Speaker 2: which is integrated into the rest, similar to forms. So by default, commands will request
Speaker 2: nothing. So it's like inverse
Speaker 3: compared
Speaker 2: to forms because commands don't exist in the
Speaker 2: a non-progressive enhanced case and commands by its nature are probably much more granular.
Speaker 2: And so by default, they refresh nothing.
Speaker 2: And so basically you opt into what do you want to have refreshed by employing
Speaker 1: the same
Speaker 3: mechanism
Speaker 2: with the refresh.
Speaker 3: Oh, and what I
Speaker 2: didn't mention yet, you can also initiate this kind of single flight mutation
Speaker 2: from the client.
Speaker 2: So you can tell the command or the form,
Speaker 2: hey, please also refresh the following.
Speaker 2: And
Speaker 3: then basically
Speaker 2: we're passing in the remote function key
Speaker 2: from the client to the backend,
Speaker 2: and it then knows which thing to call and how to
Speaker 3: return it.
Speaker 3: And when you do it from the client,
Speaker 2: you can even do optimistic updates.
Speaker 2: So you can on the client say,
Speaker 2: okay, while this request is pending,
Speaker 2: I want to, I don't know,
Speaker 2: maybe you click the toggle button
Speaker 2: and then you're saying like,
Speaker 2: I'm sure this will work in 99.9% of the time.
Speaker 2: So I can just already do an optimistic update
Speaker 2: and I don't know,
Speaker 2: show that it's in thumbs up state already.
Speaker 3: Yeah.
Speaker 2: And that you can use, yeah.
Speaker 2: The specific APIs don't matter really,
Speaker 2: but you have the capability to do an optimistic update there,
Speaker 2: which exists as long as the
Speaker 1: mutation
Speaker 2: is pending.
Speaker 1: Yeah, no, that makes sense.
Speaker 1: So it's basically, it's like form, basically, command.
Speaker 1: It's form, but without using forms, in a sense.
Speaker 1: It's probably simplified a lot.
Speaker 1: But then there's also the last one, the pre-render one,
Speaker 3: which this
Speaker 1: one is pretty cool.
Speaker 1: I feel like you can pre-render parts of your site.
Speaker 1: Well, parts of your page, I guess,
Speaker 1: which you couldn't
Speaker 2: really...
Speaker 2: Parts of your data, yes.
Speaker 2: Yeah.
Speaker 2: So, yeah, that's definitely something
Speaker 2: which is really, really cool
Speaker 2: that before you basically had to do all or nothing,
Speaker 2: you could pre-render your whole page.
Speaker 3: Yeah.
Speaker 2: But what if your page is like 50-50 dynamic
Speaker 2: 50 and the other 50 percent is static um what do you do then you you could do it today by having
Speaker 2: like pre-rendered plus server ts endpoints and
Speaker 3: using this
Speaker 2: in your load function so
Speaker 2: there is a way today but it's not obvious it's it's clumsy so yeah with pre-render you're basically
Speaker 2: saying invoke this function at build time and then it's just like a blob that's lying around
Speaker 2: on your file system deployed
somewhere and then whenever a request is done you just serve that
Speaker 2: file
Speaker 1: yeah so an example and i think that the same example is that is done in the documentation is
Speaker 1: like if you have a blog site you could pre-render the the actual posts but then you could have
Speaker 1: the comments being fetched dynamically, for example.
Speaker 1: Correct.
Speaker 1: That'd be one example, I guess.
Speaker 2: Yeah.
Speaker 2: And the insight here generally is that
Speaker 2: the thing that makes your site slow
Speaker 2: is probably not going to be Svelte's server-side rendering.
Speaker 2: Right.
Speaker 2: It's going to be the data that's taking so long to load.
Speaker 2: And if you can pre-render as much as possible
Speaker 2: in as granular as possible beforehand,
Speaker 2: then this will speed up your sites.
Speaker 1: yeah
Speaker 1: very very very nice addition
Speaker 1: alright I think have I missed
Speaker 1: any of the remote functions are there
Speaker 1: secret remote functions there might be coming
Speaker 1: secret remote functions
Speaker 1: like if you do
Speaker 1: a cheat code do we get another
Speaker 1: one
Speaker 2: no hidden
Speaker 2: no hidden remote functions
Speaker 2: no
Speaker 3: April Fool's
Speaker 2: remote functions
Speaker 2: planned
Speaker 3: either
Speaker 2: yeah I mean I talked about batch caching so this is we are not sure if this will be like a another
Speaker 2: variant like query.cache or something where it's going to be part of the regular query we'll see so
Speaker 2: this may be another one but yeah no no immediate plans there for for
Speaker 3: more
so how how would we how
Speaker 1: How would we use remote functions today?
Speaker 1: We just enable it in the Svelte config, right,
Speaker 1: with experimental something?
Speaker 1: Right, so first
Speaker 2: you would--
Speaker 2: for remote functions, you would go in your Svelte.config.js
Speaker 2: and then inside the kit namespace,
Speaker 2: you would do experimental and then objects
Speaker 2: and then remote functions:true.
Speaker 2: And to actually make proper use of them,
Speaker 2: you would want to use that together with async svelte.
Speaker 2: So that means you also have to,
Speaker 2: at the top level, do another experimental colon
Speaker 2: and then object async
Speaker 1: colon true.
Speaker 1: We'll put that into notes as well.
Speaker 1: It wouldn't really make sense to use remote functions
Speaker 1: without the async function.
Speaker 1: Can you actually do that?
Speaker 2: I mean, you could.
Speaker 2: You could call them inside your load function.
Speaker 2: you could call them inside a
Speaker 3: weight block.
Speaker 2: You could also...
Speaker 2: So the nice thing about queries is
Speaker 2: they don't just return a promise.
Speaker 2: They also return an object with current and loading and error on them.
Speaker 2: So if you want to, you can use the flattened version of this,
Speaker 2: the non-blocking version, so to speak.
Speaker 2: And you could use that.
Speaker 2: So yes, theoretically, it's possible to use
Speaker 2: remote functions without async Svelte.
Speaker 2: But we will probably at some point
Speaker 2: basically require you to opt into that flag anyway
Speaker 2: because of how we will connect this
Speaker 2: with Svelte's resource
Speaker 3: API
Speaker 2: and so on.
Speaker 2: That makes sense.
Speaker 2: Yeah, and I mean, again, realistically,
Speaker 2: you're going to use this with await.
Speaker 1: Yeah.
Speaker 1: I mean, if you're going experimental,
Speaker 1: you might
Speaker 2: as well.
Speaker 2: Right,
Speaker 1: if you're going experimental,
Speaker 1: why not go all in?
Speaker 1: exactly
Speaker 1: all right
Speaker 1: I think that's
Speaker 1: unless you have something
Speaker 1: something else that you
Speaker 1: that you want to highlight
Speaker 1: I think that was a pretty good
Speaker 1: overview
Speaker 1: we should definitely mention that
Speaker 1: you've made
Speaker 1: I think you did mention it a bit
Speaker 1: the remote functions videos
Speaker 1: that you've done
Speaker 3: for the South
Speaker 1: Society website
Speaker 1: are really good
Speaker 1: So if you want to just have like a nice overview
Speaker 1: and you also do one where you talk about auth,
Speaker 1: like how to protect queries,
Speaker 1: because this might be something that we should talk about
Speaker 1: where in previously, like the load function,
Speaker 1: we kind of use the hooks to protect routes
Speaker 1: and protect stuff when it comes to auth.
Speaker 1: I mean,
Speaker 3: you could do it
Speaker 1: in the load function as well, I guess,
Speaker 1: But my experience was that you would just add an auth hook
Speaker 1: and then you would do protection in there.
Speaker 1: But now since the functions themselves are,
Speaker 1: you mentioned they were public, right?
Speaker 1: Anyone can call them.
Speaker 1: So you have to protect them somehow.
Speaker 1: Yes.
Speaker 1: So you could
Speaker 2: through it.
Speaker 2: I mean, the handle hook still runs before remote functions.
Speaker 3: Right.
Speaker 3: That will not
Speaker 2: go away.
Speaker 2: So you could still do it in the handle hook.
Speaker 2: But the difference is that remote functions are not tied to a page or a route.
Speaker 2: So that's the big difference to layout and page loaders
Speaker 2: because they are always tied to routes.
Speaker 2: And so you could in your handle hook be like,
Speaker 2: okay, everything under the path slash authenticated or something
Speaker 2: should be guarded.
Speaker 3: And that
Speaker 2: you can't really do with remote functions.
Speaker 2: And the way I would solve it is to have a, basically, I would say it's like a private query function, a shared query function, basically, which does all the authentication checks for you.
Speaker 2: And then you just call that inside your query function before proceeding to do
Speaker 3: the
Speaker 2: rest.
Speaker 2: And that's, again, that's the niceness of having this be just functions
Speaker 2: because you can just use regular function composition to get this.
Speaker 2: You could even create higher order functions from queries to, I don't know,
Speaker 2: call not query, but authenticated query or something, which does this for you.
Speaker 2: There's a few options you have.
Speaker 2: Is
Speaker 1: this the beginning of Svelte turning into React?
Speaker 1: No.
Speaker 1: Are you kidding?
Speaker 1: I mean
Speaker 3: the thing
Speaker 1: in React is all about functions
Speaker 2: right so and function composition
Speaker 2: I
Speaker 3: mean function composition
Speaker 2: is nice
Speaker 2: React hooks not so much
Speaker 2: throwing some shade there
Speaker 1: alright
Speaker 1: yeah thank you for coming on Simon
Speaker 1: thank you for having me
Speaker 1: do you have any picks?
Speaker 2: I don't think I prepared you for this
Speaker 2: sorry
Speaker 2: My pick is probably my new microphone. So after the first YouTube video I did,
Speaker 2: like before then, like every now and then I thought, oh, maybe I need a proper microphone. And I just
Speaker 2: not just like the built in crappy one in my headset. And then there was one YouTube comment,
Speaker 2: like, please get a better audio for next time or something. And I was like, yeah, you're right.
Speaker 2: I should finally do this.
Speaker 2: If I do more videos, then I probably should gear up a bit.
Speaker 2: And so, yeah, I got a...
Speaker 2: What is it called?
Speaker 2: Elgato Wave 3, I think it is.
Speaker 1: Sounds very nice.
Speaker 2: Yeah, I really like it so far.
Speaker 2: So, yeah.
Speaker 1: And I'm sure all your colleagues as well are super hyped about the audio being even better in meetings and stuff.
Speaker 1: yeah all right they can finally understand me yeah yeah okay so my pick is what i haven't prepared
Speaker 1: myself for this so i i think my pick is my new thunderbolt dock it has all sorts of fun
Speaker 1: functionalities i think it's like 20 ports or something just one cable connected to my computer
Speaker 1: and then I have two monitors and all sorts of stuff, Ethernet.
Speaker 1: It's a very nice experience just having one cable to connect your laptop.
Speaker 1: Yeah, the
Speaker 2: docking stations are really nice.
Speaker 2: Yeah, my Surface docking station is...
Speaker 2: Yeah, I would need a few more USB ports
Speaker 1: to be
Speaker 2: fully happy.
Speaker 1: This one I got had extra USB-C ports, which was very nice.
Speaker 1: So I think it has like eight USB-Cs.
Speaker 1: And then, yeah, it's a lot.
Speaker 1: And then two monitors and crazy, crazy dock.
Speaker 3: But yeah, I think that's it.
Speaker 3: Thanks, everyone, for listening.
Speaker 3: Again, thank you, Simon, for joining me.
Speaker 3: And we will talk to you next week.
Speaker 3: Bye-bye.
Speaker 3: Bye.