Closed SaltyAom closed 1 week ago
Hi! Is there any place I can contribute to updating the docs for v1?
How is this supposed to work with Plugins? Consider this example:
import { Elysia } from "elysia";
const myPlugin = () => (a: Elysia) =>
a.derive(() => ({ myPluginProp: 1 }));
const app = new Elysia()
.use(myPlugin())
.get("/my-plugin", ({ myPluginProp }) => {
// ^ Property 'myPluginProp' does not exist on type '{ body: unknown; ...
return myPluginProp
});
It seems the app
actually gets myPluginProp
on runtime (expected), just TS types say otherwise.
Returning an object in v1.0.3 is not converting to response to application/json
type. Rather, it sends it out as a text/plain
. How do I fix this?
@SaltyAom any thoughts on this. Seems like this completely prevents usage of plugins using derive
in v1.
@dodas update to elysia 1.0.6
Returning an object in v1.0.3 is not converting to response to
application/json
type. Rather, it sends it out as atext/plain
. How do I fix this?
@kabir-asani A quick workaround I found is to spread the object into another object.
e.g.
return { ...someObject }
@PureDizzi Thank you! This seems to be working but it's ugly.
We need a fix on this!
Returning an object in v1.0.3 is not converting to response to
application/json
type. Rather, it sends it out as atext/plain
. How do I fix this?@kabir-asani A quick workaround I found is to spread the object into another object.
e.g.
return { ...someObject }
For correct typing in the Eden client, return { ...new Example() } as Example
seems to work (this bug is so weird... has anyone made an issue for it yet?)
Returning an object in v1.0.3 is not converting to response to
application/json
type. Rather, it sends it out as atext/plain
. How do I fix this?@kabir-asani A quick workaround I found is to spread the object into another object.
e.g.
return { ...someObject }
For correct typing in the Eden client,
return { ...new Example() } as Example
seems to work (this bug is so weird... has anyone made an issue for it yet?)
I want to get this fix. Can you provide some example that cause this problem?
import { Elysia } from "elysia";
class Data {
message: String;
constructor(message: String) {
this.message = message;
}
}
const app = new Elysia()
.get("/ping", ({ set }) => {
const data = new Data("pong");
set.status = 200;
return data;
})
.listen(3000);
@SaltyAom This snippet is enough to replicate the issue -- please check.
NOTE: What I've noticed is that the issue arises only when I try to access the set
object.
import { Elysia } from "elysia"; class Data { message: String; constructor(message: String) { this.message = message; } } const app = new Elysia() .get("/ping", ({ set }) => { const data = new Data("pong"); set.status = 200; return data; }) .listen(3000);
@SaltyAom This snippet is enough to replicate the issue -- please check.
NOTE: What I've noticed is that the issue arises only when I try to access the
set
object.
Hi, sorry for the slow response.
Unfortunately, this is an expected behavior and the case without using set is a bug.
Web Standard on different runtime has a slight implementation for mapping value to Response
.
Some classes may expected to pass to Response
directly while some classes are not on different implementation, instead of serializing all the unknown case, we leave this gray area for users to handle themself using either mapResponse
or defining a serialization method using toString
In this case, if you are using a custom class, you may use toString
method like this instead.
class Data {
message: String
constructor(message: String) {
this.message = message
}
toString() {
return JSON.stringify(this)
}
}
But is it really a fool-proof solution?
Because then I'd have to set Content-Type
explicitly as well.
I propose this:
toJSON()
method on their data classes, which returns the plain, json-serializable representation of the objecttoJSON()
method if it's available and if so, respond with that along with Content-Type: application/json
@SaltyAom I understand that you don't want to introduce breaking changes after v1. Have you seen https://github.com/elysiajs/elysia/issues/99#issuecomment-1824954127? I am biased, but that seems like an easy thing to slip in with other breaking changes.
@april83c, @kabir-asani, you can do something like this as a workaround:
const Data = class Object {
message: String;
constructor(message: String) {
this.message = message;
}
};
return new Data("yay") // inside handler
The problem is this switch on constructor name:
https://github.com/elysiajs/elysia/blob/da63011c213158853e129d9668c1bb95f908c7d1/src/handler.ts#L159
or implement toJSON
like this:
class Data {
message: String;
constructor(message: String) {
this.message = message;
}
toJSON() {
return structuredClone(this);
}
}
That way you dont have to set Content-Type
:
new Elysia()
.get("/", () => {
return new Data("yay").toJSON();
})
.listen(8080);
Its the same behaviour as return { "message": "yay" }
I am really not a fan of the { as: 'global' }
. It makes the code so cluttered and unfamiliar...
Also, I think mention the { as: type }
in the At A First Glance section would be helpful! It's buried all the way the bottom in the Scope section.
I am really not a fan of the
{ as: 'global' }
. It makes the code so cluttered and unfamiliar...Also, I think mention the
{ as: type }
in the At A First Glance section would be helpful! It's buried all the way the bottom in the Scope section.
Since 1.1 you can use .as("global")
on elysia instance
Breaking change
It's sad but the upcoming Elysia 1.0 RC will contain breaking changes for all users, especially library authors :aris_crying:
Lifecycle event register with "on" will now only apply to the current and descendant instances.
Migration
Migration should take no longer than 10-15 minutes. But for production apps, it heavily depends on library authors.
Starting from Elysia 1.0 RC, all lifecycle events including
derive
, andresolve
will accept additional parameters{ as: 'global' | 'local' }
to specify if hook should be local or global which default tolocal
To migrate, add
{ as: 'global' }
to any even register using.on
to specify it as global.Behavior
If
{ as: 'global' }
is not added, a parent instance that uses the current instance will not inherits a hook as the following:If is a behavior change from 0.8.
As the plugin hook now only defaults to itself and its descendant, it will not be applied to the parent (main).
To make it apply to the parent, explicit annotation is needed.
This will mark a hook as global, which is a default behavior of 0.8.
API Affected
To help with this, you can usually find a global hook by using find all with
.on
. Forderive
,mapDerive
,resolve
,mapResolve
will show a type warning as missing if use outside of the scope.The following API or need to be migrate:
API that does NOT need to be migrated:
Why
We know we don't like migration as well, but we think this is an important breaking change that will happen sooner or later to fix current problems:
Once migrated, your API should have an easier mental model, be more predictable, and be easier to maintain. For example, see the below picture which does the same thing.
For additional information, please see https://x.com/saltyAom/status/1763554701130514944?s=20