elysiajs / eden Goto Github PK
View Code? Open in Web Editor NEWFully type-safe Elysia client
License: MIT License
Fully type-safe Elysia client
License: MIT License
Hi!
In order to use some feature of elysia like dependency injection (https://elysiajs.com/patterns/dependency-injection.html), I split the code into multiple file:
First, a state for the database
// setup.ts
export const setup = new Elysia({ name: 'setup' })
.state('redis', redis)
// routes/auth.ts
import { Elysia } from 'elysia';
import { setup } from '../setup';
export const authRouter = new Elysia({prefix: "/auth"})
.use(setup)
.post('/login', async (context) => { ... })
.post('/logout', async (context) => { ... }));
// routes/foo.ts
import { Elysia } from 'elysia';
import { setup } from '../setup';
export const fooRouter = new Elysia({prefix: "/foo"})
.use(setup)
.get('/', async (context) => { ... })
.post('/manage', async (context) => { ... }));
and finally the main entry point for my app
// server.ts
import { Elysia } from 'elysia';
import { authRouter } from './routes/auth';
import { fooRouter } from './routes/foo';
const app = new Elysia()
.use(authRouter)
.use(fooRouter)
.listen(6001);
export type App = typeof app;
console.log(`๐ฆ Elysia is running at ${app.server?.hostname}:${app.server?.port}`);
Working as expected, manually querying various endpoints.
When using Treaty, I get an empty elysia app on the client side
Any idea where my code fails?
Thanks in advance!
@elysiajs/eden
: 1.0.0-rcelysia
: 1.0.0-rc.19const app = new Elysia()
.post('login', (body) => handler)
.listen(3000)
const client = edenTreaty<typeof app>('http://localhost:3000')
client.login.post(body)
// ^ cannot find login on {}
const app = new Elysia()
.post('/login', (body) => handler) // leading forward-slash
.listen(3000)
const client = edenTreaty<typeof app>('http://localhost:3000')
client.login.post(body)
// ^ typing passed
Remove the hard-requirement to have leading /
s before routes for eden
to recognise them.
Is there any way to send formdata
?
If I send the form data like in the bellow code, I am getting file.name
as blob
for every single file
// server
.post(
'/pdf',
async ({ body }) => {
console.log(body)
for (let index = 0; index < body.files.length; index++) {
const file = body.files[index]
console.log('file.name: ', file.name)
console.log('file.size: ', file.size)
console.log('file.type: ', file.type)
}
return { message: 'success got the file' }
},
{
body: t.Object({ files: t.Files() })
}
)
// client
const client = edenTreaty<Server>('http://localhost:8080')
const files = e.target.files
const { data, error } = await client.pdf.post({ files: files })
// output using edenFetch or edenTreaty
{
files: [
Blob (0.27 MB)
]
}
file.name: blob
file.size: 270947
file.type:
using fetch
or axios
with formdata I am able to get the file.name correctly
// client
const formData = new FormData()
for (let i = 0; i < files.length; i++) {
let file = files.item(i)
formData.append('files', file)
}
await axios.post('http://localhost:8080/pdf', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
await fetch('http://localhost:8080/pdf', {
method: 'POST',
body: formData
})
// output using axois to fetch with formdata
{
files: [
Blob (0.27 MB)
]
}
file.name: society.pdf
file.size: 270947
file.type:
Following EdenTreaty sample from docs:
// server.ts
import { Elysia, t } from 'elysia'
const app = new Elysia()
.get('/', () => 'Hi Elysia')
.get('/id/:id', ({ params: { id } }) => id)
.post('/mirror', ({ body }) => body, {
body: t.Object({
id: t.Number(),
name: t.String()
})
})
.listen(8080)
export type App = typeof app
import { edenTreaty } from '@elysiajs/eden'
import type { App } from './server'
const app = edenTreaty<App>('http://localhost:8080')
I get type error:
Type 'Elysia<"", { request: {}; store: {}; derive: {}; resolve: {}; }, { type: {}; error: {}; }, {}, {}, { "/": { get: { body: unknown; params: never; query: unknown; headers: unknown; response: { 200: string; }; }; }; "/id/:id": { get: { body: unknown; params: Record<"id", string>; query: unknown; headers: unknown; respo...' does not satisfy the constraint 'Elysia<any, any, any, any, any, any, false>'.
Types have separate declarations of a private property 'dependencies'.ts(2344)
I have
Any idea what else I can test?
Hi
I am trying to create a NextJS app using Elysia and did the following steps:
bunx create-next-app
bun add elysia @elysiajs/eden
server.ts
file under the src directory and add a default elysia serverpage.tsx
with the following"use client";
import { edenTreaty } from "@elysiajs/eden";
import { useEffect, useState } from "react";
import type { App } from "../server";
const app = edenTreaty<App>("http://localhost:3000");
export default function Home() {
const [data, setData] = useState(null);
useEffect(() => {
app.api.test.get().then((x: any) => setData(x.data));
}, []);
return <div>test</div>;
}
When hovering over the type of the app
I get back the return type of any.
Is this because the file is a tsx
file?
Thanks for any help!
Having this type definition in the handler:
{
body: t.Null(),
}
results in Eden Treaty inferring the signature of .post
as params: never
...
Similar #23, method
is not being sent as UPPERCASE
.
I think we can change https://github.com/elysiajs/eden/blob/main/src/treaty2/index.ts#L255 to just be:
method: method?.toUpperCase(),
The data
property on EdenTreaty's .post()
response objects are typed as unknown
as of the current versions of Eden & Elysia (0.4.1 & 0.4.10 respectively).
To illustrate this I've tried recreating the example CodePen in the Elysia docs. This works fine with the older Eden & Elysia versions recorded in the package.json
however upgrading them to the latest version causes the following misbehaviour:
package.json
{
"name": "hi-elysia",
"version": "1.0.0",
"module": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "bun run --hot src/index.ts"
},
"dependencies": {
"@elysiajs/eden": "^0.4.1",
"elysia": "^0.4.10"
},
"devDependencies": {
"bun-types": "^0.5.0",
"typescript": "^4.9.4"
}
}
*note: swagger has been removed from example which I don't expect would affect the issue
http.ts
import { Elysia, t } from "elysia";
// Tips: navigate to /swagger to see the documentation
const app = new Elysia()
.get("/", () => "Hello Elysia")
.get("/id/:id", ({ params: { id } }) => id)
.post("/json", ({ body }) => body, {
schema: {
body: t.Object({
name: t.String(),
}),
query: t.Object({
id: t.String(),
}),
},
})
.listen(3000);
console.log(
`Elysia running at http://${app.server?.hostname}:${app.server?.port}`
);
export type App = typeof app;
eden.ts
import { edenTreaty } from "@elysiajs/eden";
import type { App } from "./http";
const api = edenTreaty<App>("http://0.0.0.0:8080");
// Hover over function to see type-definition
// Try hover over the response to see its type
const response1 = await api.index.get();
// Path parameters
api.id["177013"].get();
// Try hover over the response to see its type
// Type inference work even without direct type declaration
const { data } = await api.json.post({
name: "saltyaom",
$query: {
id: "Chiffon",
},
});
// Type Error
api.json.post({
// 'name' is not assignable to string
name: 1,
// property 'id' is missing
$query: {},
});
The only differences as far as I can tell between this setup and the example from the docs are the version updates and that swagger has been removed.
admin-router.ts:
const adminRouter = new Elysia({ name: 'Admin', prefix: '/admin' })
adminRouter.get('/hello', () => 'hello')
export type adminRouter = typeof adminRouter
export default adminRouter
index.ts:
import adminRouter from './admin-router.ts'
const app = new Elysia({ prefix: '/api' }).use(adminRouter).listen(5000)
test.ts:
import type { adminRouter } from '../admin'
import { treaty } from '@elysiajs/eden'
const app = treaty<adminRouter>('localhost:5000/')
const x = await app.api.admin.hello.get() // admin does not exist on type adminRouter
Seems like treaty does not work properly using plugin with prefix approach. Does it only work with .group approach or am I doing something wrong?
Reported on discord
Running following code does not work as expected:
import { Elysia, t } from 'elysia';
import { edenTreaty } from '@elysiajs/eden/treaty';
const app = new Elysia()
.post(
'/upload',
({ body }) => {
console.log(body);
return 'yay';
},
{
body: t.Array(
t.Object({
itemId: t.String()
})
)
}
)
.listen(8080);
const api = edenTreaty<typeof app>('http://localhost:8080');
const { response, data } = await api.upload.post([
{
itemId: 'yay'
}
]);
console.log(response.status, data);
app.stop();
400 Invalid body, 'type': Expected array
Expected: []
Found: {
"0": {
"itemId": "yay"
}
}
This spread syntax converts array into object:
Line 168 in 5ab6667
const array = [
{
itemId: 'yay'
}
];
const { ...obj } = array;
console.log(obj); // { "0": { itemId: "yay" } }
I am trying to return a structured error response from the server, so I can return several validation messages at once for for example.
ie with an onError server handler like this
.onError((context) => {
const { code, error, set } = context
switch (code) {
case 'VALIDATION_ERROR':
set.status = 422
return {
message: error.toString().replace('Error: ', '')
fields: []
}
}
})
But when a request fails, the eden fetch error.value
type is a string:
const { data, error} = await fetch('/endpoint')
It would be nice to be able to customize the returned error type, so I could access error.value.message
and have it properly typed too.
seems query missed in eden fetch
return new Elysia({ prefix: "api/" })
.post("business", ({ query }) => query.page, {
query: t.Object({
page: t.Numeric(),
}),
})
eden part
const fetch = edenFetch<BusinessService>("");
fetch("business",{
query:{ page: "1" } // ????
})
I was going through the documentation and noticed a few things during implementation.
Property 'index' does not exist on type '{ get: (params?: { $fetch?: RequestInit | undefined; $query?: Record<string, string> | undefined; $headers?: Record<string, unknown> | undefined; $transform?: Transform<...> | undefined; } | undefined) => Promise<...>; post: (params?: { ...; } | undefined) => Promise<...>; } & ... 17 more ... & { ...; }'.ts(2339)
edenFn
Module '"@elysiajs/eden"' has no exported member 'edenFn'.ts(2305)
nextjs (client): 13.5.4
eden version 0.7.4
elysia: 0.7.15
bun: 1.0.3
Using the .subscribe
method, I expected the data property to return the inferred type from the schema. Instead, it actually returns a string, which needs to be manually parsed after. I believe this is a bug, as the type inference is correct.
Groups with paths at the root generate incorrect types in eden treaties.
The below snippet creates a route /group
and should be accessible through treaty.group.get()
.
app.group('/group', (app) => {
return app
.get("/", () => "hi")
.get("/sub", () => "yo")
})
However, typescript complains that .get()
"is not callable". treaty.group.get()
does work as expected and returns data from /group
. Originally I thought this was an issue with Elysia's routing and raised the issue here elysiajs/elysia#39. It seems that Elysia is working as intended and treaty.group.get()
behaves as I expect despite the typescript error, leading me to conclude that this is an issue with Eden's typing.
Issue:
Hi guys thanks for the great work!
I am running into an issue with eden treaty where the client call api.get() is making the request to /ge instead of /.
Deps:
"dependencies": {
"@elysiajs/eden": "^0.6.4",
"@elysiajs/swagger": "^0.6.2",
"elysia": "latest"
},
"devDependencies": {
"bun-types": "latest"
},
Code:
Server:
import { Elysia, t } from "elysia";
import { swagger } from "@elysiajs/swagger";
const app = new Elysia()
.use(swagger())
.get("/", () => "Hello Elysia")
.get("/id/:id", ({ params: { id } }) => id)
.post("/json", ({ body }) => body, {
body: t.Object({
name: t.String(),
}),
query: t.Object({
id: t.String(),
}),
})
.listen(3000);
console.log(
`Elysia running at http://${app.server?.hostname}:${app.server?.port}`
);
export type App = typeof app;
client:
import { edenTreaty } from "@elysiajs/eden";
import type { App } from "./http";
const api = edenTreaty<App>("http://0.0.0.0:3000");
const response = await api.get({});
console.log(response);
result:
{
data: "NOT_FOUND",
error: 1 | status;
2 | value;
3 | constructor(e, t) {
4 |
5 | super(), this.status = e, this.value = t;
status: 404,
raw: Response (0 KB) {
ok: false,
url: "http://0.0.0.0:3000/ge",
headers: Headers {
"content-type": "text/plain;charset=utf-8",
"date": "Mon, 11 Sep 2023 22:57:21 GMT",
"content-length": "9"
},
statusText: "Not Found",
redirected: false,
bodyUsed: true,
status: 404
},
headers: Headers {
"content-type": "text/plain;charset=utf-8",
"date": "Mon, 11 Sep 2023 22:57:21 GMT",
"content-length": "9"
}
}
First off, I want to express my appreciation for the Elysia project! It's been instrumental in building efficient and type-safe servers, and the thoughtfulness in design truly shows. I've been using it for a while and am continually impressed by its capabilities and performance.
I was expecting to utilize the edenFetch from @elysiajs/eden with App type from server.ts without encountering type errors, ensuring smooth integration between client and server-side code.
However, I encountered a TypeScript error when trying to use the edenFetch with the App type. The error is as follows:
Property 'params' is missing in type '{}' but required in type '{ params: never; }'.ts(2345)
types.ts(40, 23): 'params' is declared here.
The following code represents a simplified scenario based on the official example provided in the Elysia documentation for edenFetch. I've encountered the TypeScript error while working with this example directly from the Elysia Eden Fetch Documentation.
// server.ts
import { Elysia, t } from 'elysia'
const app = new Elysia().get('/', () => 'Hi Elysia').listen(8080)
export type App = typeof app
import { edenFetch } from '@elysiajs/eden'
import type { App } from './server'
const fetch = edenFetch<App>('http://localhost:8080')
// response type: 'Hi Elysia'
const pong = await fetch('/', {})
I understand the challenge and intricacies involved in type definitions and intersection types, especially when dealing with external type declarations. I suspect this issue might be related to the intersection types or perhaps an optional property that isn't set correctly in the typings.
I'm not entirely sure of the best solution here, but I wanted to bring it to your attention in case it's something that can be improved or if there's a workaround that I'm not aware of. Your insight and guidance would be immensely valuable.
Thank you again for all your hard work on this project. It's been a pleasure to use, and I look forward to continuing with it!
It would be nice if Eden Treaty allowed for the transformation of the response.
Proposed API:
const api = edenTreaty<App>('', {
transform: (response) => {
if (response.error) throw response.error;
return response.data;
},
});
const data = await api.get();
Current workaround, which needs to be coded manually for each request:
const api = edenTreaty<App>('');
const response = await api.get();
if (response.error) throw response.error;
const data = response.data;
--- Why would this be useful? ---
Lot of current querying libraries like Tanstack query, SWR, depend on errors being thrown, so they can be returned by the hook used for querying. Eden Treaty does not throw, but returns errors and data objects, which is fine, but there should be a way to configure the transformation of the response for these use-cases on the client level. Is this something that would be feasible to implement? @SaltyAom
While the documentation made clear that how to use get and post simple json, there are no example on how to post a file using it, and its not clear on how to approach that. Using formdata as body failed for me. So a better documentation depicting how to make it work would be great.
Param string unions are not respected by eden treaty, any string is allowed instead of the specified union.
Reproduction:
import { Elysia, t } from 'elysia';
import { edenTreaty } from '@elysiajs/eden';
const app = new Elysia().get('/:name', ({ params }) => params.name, {
params: t.Object({
name: t.Union([t.Literal('Joe'), t.Literal('Josh')]),
}),
});
type App = typeof app;
const api = edenTreaty<App>('http://localhost:3000');
api[':name'].get();
:name
can be any string, without type checking. Instead of :name
here, the type should only be able to be Joe
or Josh
, nothing else, as they are specified in the union.
with the basic setup described in the docs,
const fetch = edenFetch<App>('http://localhost:3000');
const pong = await fetch('/', {});
i get the type error on fetch Argument of type 'string' is not assignable to parameter of type 'never'.
the type described in vscode shows
const fetch: <never, Uppercase<string>, never>(endpoint: never, options: Omit<RequestInit, "headers" | "body" | "method"> & {
method?: Uppercase<string> | undefined;
Am I doing something wrong?
// Server
=================================================
package.json deps:
"@elysiajs/cookie": "^0.8.0",
"@elysiajs/cors": "^1.0.2",
"@elysiajs/jwt": "^0.8.0",
"@elysiajs/swagger": "^0.8.5",
"@prisma/client": "5.10.2",
"elysia": "latest"
=================================================
const app = new Elysia();
app
.use(swagger()) // It does not change after commenting out this line
.use(cookie()) // It does not change after commenting out this line
.use(corsHandler) // It does not change after commenting out this line
.use(errorHandler) // It does not change after commenting out this line
.use(eventHandler) // It does not change after commenting out this line
.use(jwtHandler) // It does not change after commenting out this line
.use(authRoutes)
.listen(process.env.PORT || 4000);
export type App = typeof app;
// type App = Elysia<"", {
// request: {};
// store: {};
// derive: {};
// resolve: {};
// }, {
// type: {};
// error: {};
// }, {}, {}, {}, false> <== This is what VSC is showing when I hover App object
// Client
=================================================
package.json deps:
"@elysiajs/eden": "latest",
"elysia": "latest", // It does not change after commenting out this line
=================================================
import { edenTreaty } from "@elysiajs/eden";
import type { App } from "../../../../project-api/src/index";
const app = edenTreaty<App>("http://localhost:4000");
// Outcome
Type 'Elysia<"", { request: {}; store: {}; derive: {}; resolve: {}; }, { type: {}; error: {}; }, {}, {}, {}, false>' does not satisfy the constraint 'Elysia<any, any, any, any, any, any, any, any>'.
Type 'Elysia<"", { request: {}; store: {}; derive: {}; resolve: {}; }, { type: {}; error: {}; }, {}, {}, {}, false>' is missing the following properties from type 'Elysia<any, any, any, any, any, any, any, any>': _routes, _types, _ephemeral, _volatile, and 11 more.ts(2344)
DEMO:
https://codesandbox.io/p/sandbox/strange-sid-y335z4?file=/eden.ts:19,55
Some screenshots:
What's Expected:
The route options should be differentiated correctly by methods on the same route
Would love to give this one a shot, and write a pull request for it
Currently we can't stream with Eden Fetch
Proposal add a text/event-stream case
switch (res.headers.get('Content-Type')?.split(';')[0]) {
case 'application/json':
data = await res.json()
break
case 'text/event-stream': {
data = res
break
}
default:
data = await res.text().then((d) => {
if (!Number.isNaN(+d)) return +d
if (d === 'true') return true
if (d === 'false') return false
return d
})
break
}
Add response to the return of EdenFetch
return {
data,
error: null,
status: res.status,
response: res,
headers: res.headers,
retry: execute
}
Making some native Bun tests using Eden but I'm facing a null data object in response object. I'm gonna let my fetching config and what I'm receiving in my test.
// --- src/main/adapter/rest-adapter.ts
import type { Controller, HttpRequest } from "@/presentation/common";
export default (controller: Controller<any>) =>
async ({ body, query, params, headers, set }: any): Promise<any> => {
set.headers["Content-Type"] = "application/json";
try {
const request: HttpRequest<any> = { body, query, params, headers };
const result = await controller.handle(request);
set.status = result.status;
return JSON.stringify({ response: result.body });
} catch (error: any) {
set.status = 500;
return JSON.stringify({ error: error.message });
}
};
// --- src/main/app.ts
import authRoutes from "@/main/routes/auth-routes";
import { Elysia } from "elysia";
const app = new Elysia()
.get("/", () => {
return "Hello World!";
})
.group("/auth", (app) => app.use(authRoutes));
export default app;
export type App = typeof app;
// --- tests/config/api.ts
import app, { type App } from "@/main/app";
import env from "@/main/env";
import { edenFetch } from "@elysiajs/eden";
import { afterAll, beforeAll } from "bun:test";
beforeAll(() => {
app.listen(env.PORT, () => {
console.log("Started testing server...");
});
});
afterAll(async () => {
await app.stop();
});
export default edenFetch<App>(`http://localhost:${env.PORT}`);
// --- test file
import api from "@/config/api";
import { faker } from "@faker-js/faker";
import { beforeAll, describe, expect, it } from "bun:test";
describe("Create an account/Sign up", () => {
let response: any;
beforeAll(async () => {
const user = {
username: faker.internet.userName(),
email: faker.internet.email(),
password: faker.internet.password(),
};
response = await api("/auth/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: {
username: user.username,
email: user.email,
password: user.password,
},
});
});
it("should receive email and password", () => {
console.log(response.data);
expect(response.status).toBe(200);
});
});
When fetching from an external client, like Postman or Insomnia, I get my response just like it should be. But when running tests with bun run test
data field in response object is null. This image is showing what is happening: I'm logging what I'm returning to route and logging the response object I'm getting of this route.
"@elysiajs/eden": "^0.8.1",
"elysia": "^0.8.17",
Any thoughts?
Below minimal code:
import { Elysia } from 'elysia'
import { edenTreaty } from '@elysiajs/eden'
const app = new Elysia().get('/', () => 'Hi Elysia').listen(3000)
type App = typeof app
const eden = edenTreaty<App>('http://localhost:3000')
Produces the error: Type 'Elysia<"", { ... }>' does not satisfy the constraint 'Elysia<any, { ... }>'.
And subsequent errors related to the LifeCycleStore type mismatch.
Versions used:
"@elysiajs/eden": "^0.6.0",
"elysia": "^0.6.10"
Hello!
If I have @elysiajs/static
or @elysia/apollo-graphql
in my server definition client side compilation will fail.
This is happening because of inner dependencies which are not available in the browser environment.
While it can be solved and handled (for example, by splitting your eden-safe plugins and unsafe ones and applying the unsafe ones in a different files), I think it points to an underlying limitation.
Am I missing something?
If not, do you think we can improve the situation with a specific bun build
configuration? Shall we consider an alternative route where we produce a static specification of the API and embed a simple JSON in the client?
Error details:
Apollo:
error: No matching export in "node:zlib" for import "gzip"
import { gzip } from 'zlib';
^
elysia-demo/node_modules/@elysiajs/apollo/node_modules/@apollo/server/dist/esm/plugin/usageReporting/plugin.js:8:10 377
Static:
error: No matching export in "node:path" for import "resolve"
import { resolve, resolve as resolveFn, join } from 'path';
^
elysia-demo/node_modules/@elysiajs/static/dist/index.js:2:10 54
error: No matching export in "node:path" for import "resolve"
import { resolve, resolve as resolveFn, join } from 'path';
^
elysia-demo/node_modules/@elysiajs/static/dist/index.js:2:19 63
error: No matching export in "node:path" for import "join"
import { resolve, resolve as resolveFn, join } from 'path';
^
elysia-demo/node_modules/@elysiajs/static/dist/index.js:2:41 85
Cheers
I often use elysia alongside eden embedded inside sveltekit. Sveltekit provides a custom fetch
function inside load functions that , when used with plain relative paths e.g: fetch('/api')
will call the server-side function directly when performing server-side rendering. More info here
Eden treaty 2 transforms the URL passed into treaty() by appending https://
to it. This disables sveltekit's features when using its fetch. To fix this, remove any transformation functions inside eden treaty 2 or expose configuration options to do so.
const { data, error } = await client.api.inbox({ chatId: chatId }).get()
// the types show as :
// const data: {
// id: number
// message: string
// }
// const error: {
// status: unknown
// value: unknown
}
I'm not sure if this is how its supposed to be with treaty 2 but I had proper error types in v1
reported on discord
Code
import { Elysia, t } from "elysia";
import { edenTreaty } from "@elysiajs/eden";
export const app = new Elysia()
.post(
'/uploadMultipleFiles',
async ({ body }) => {
for (const file of body.files) {
console.log(`${file.name} uploaded`)
}
return 'File uploaded'
},
{
type: 'formdata',
body: t.Object({
files: t.Files({
type: 'text/plain'
})
})
}
)
.listen(0)
const client = edenTreaty<typeof app>(`${app.server?.url}`)
let fileContent = 'hello'
let file1 = new File([fileContent], 'input1.txt')
let file2 = new File([fileContent], 'input2.txt')
const { data: multipleFileUpload } = await client.uploadMultipleFiles.post({
files: [file1, file2] // Type Error: Type 'File[]' is not assignable to type 'Files'.
})
app.stop()
console.log('Test for POST /uploadMultipleFiles path: ' + multipleFileUpload)
Expected Console output:
input1.txt uploaded
input2.txt uploaded
File uploaded
What i see instead:
{
"type": "validation",
"on": "body",
"property": "/files",
"message": "Expected kind 'Files'",
"expected": {
"files": "Files"
},
"found": {
"files": "[object Blob],[object Blob]"
},
"errors": [
{
"type": 31,
"schema": {
"type": "array",
"elysiaMeta": "Files",
"default": "Files",
"extension": "text/plain",
"items": {
"type": "string",
"default": "Files",
"format": "binary"
}
},
"path": "/files",
"value": "[object Blob],[object Blob]",
"message": "Expected kind 'Files'"
}
]
}
Solution (idk about treaty2):
Lines 268 to 270 in 59480e5
doesn't check for Array.isArray(field)
Line 7 in 59480e5
should include File[]
Hi.
I'm trying out Eden Treaty 2, but the path parameter isn't working as expected.
Here's the code I tried
const app = new Elysia({ aot: false }).get(
"/users/:id",
({ params }) => params.id
);
await treaty(app).users({ id: 1 }).get();
treaty(app).users({ id: 1 }).get is not a function.
(In 'treaty(app).users({ id: 1 }).get()', 'treaty(app).users({ id: 1 }).get' is undefined)
If there's something wrong with the way I've written it, I would like to know.
Hi
After testing various things with the RC version, it didn't work correctly. Below is the code I ran
describe("eden test", () => {
it("derive test", async () => {
const app = new Elysia()
.derive(() => ({ user: "name" }))
.get("/user", () => "user")
treaty(app).get("/user", () => "user")
})
it("resolve test", async () => {
const app = new Elysia()
.resolve(() => ({ user: "name" }))
.get("/user", () => "user")
treaty(app).user.get("/user", () => "user")
})
it("prefix test", async () => {
const app = new Elysia({ prefix: undefined }).get("/user", () => "user")
treaty(app).user.user.get() //<-- this is wrong (This is how it's autocompleted.)
})
})
version
elysia: 1.0.0-rc.15
@elysiajs/eden: 1.0.0-rc.4
Hi, I'm using Elysia with Eden Treaty and I use multiple Elysia instances, some of them having prefixes to keep the code tidy.
However, when trying to use Eden Treaty, the intellisense is really slow and most of the time it times out and just doesn't work at all.
Hi!
I have to pass via a formData for a specific API endpoint.
My problem is :
content-type
is providedcontent-type
from headers preventing from browser to handle multipart correctly (https://stackoverflow.com/a/39281156)content-type
should be spelled as Content-Type
, if you want to override the content-type
then it will be parsed as JSON because of a different case here https://github.com/elysiajs/eden/blob/main/src/fetch/index.ts#L19I can open a PR if you want but removing the first might be a breaking change that's why I've open an issue first :D
So taking a look at this
Lines 314 to 327 in 688011d
Eden Treaty can have this block
Lines 314 to 327 in 688011d
EdenTreaty.<1>.<2>.<n>.<method>({
...body,
$query?: {},
$fetch?: RequestInit,
getRaw?: boolean // ๐ This!
})
that will make it so no data property is returned and instead you'll have to handle the Response object yourself to get the data.
I have created a PR which provides a solution but without proper TypeSafety (if you set getRaw: true
the signature will not change to the Response
type). I welcome improvements to the PR. #34
I've noticed that the values of Date type in eden treaty don't match their types (In nested object, not sure about first level depth)
Type inference works correctly and the type is Date but the actual value at runtime is a string and not a date (see following screenshots), I've checked server side and the type sent are indeed Dates
Types:
export interface Experience extends Document {
type: ExperienceType;
company: Types.ObjectId | Company;
summary: string;
title: string;
projects: Project[]
}
type Project = {
name: string;
start?: Date | undefined;
end?: Date | undefined;
summary?: string | undefined;
}
I use edenTreaty as such :
import { edenTreaty } from '@elysiajs/eden'
import type { Portfolio } from 'portfolio-api'
import { env } from '$env/dynamic/public'
const api = edenTreaty<Portfolio>(env.API_URL)
export { api }
let { data, error } = await portfolioApi.domain[params.domain].get({
$fetch: { credentials: 'include' }
});
Server side :
import Elysia, { t } from 'elysia';
import { Experience, Domain } from '../models/database';
export const domain = new Elysia()
.get('/domain/:name', async ({ params: { name } }) => {
const domain = await Domain.findOne({ name: name });
if (!domain) throw new Error('Domain not found');
const populatedDomain = await domain.populate<{ experiences: Experience[] }>('experiences');
return populatedDomain;
})
When using Eden Treaty/Fetch the headers object is empty and I am unable to store session cookies on the frontend. I believe this is an issue with Eden because I am able to use the global fetch
and my cookies are automatically set. Is it possible to have Eden fetch behave more like normal fetch?
Seems like it's the typescript typing issue and it wasn't resolved as stated in #3, or maybe it was resolved when group and route is in the same file, but I haven't checked that
Just checked, it was resolved for that particular case
Recently I discovered some interesting routing behaviour, so I decided to write some test cases:
import { Elysia } from 'elysia';
import { edenTreaty } from '@elysiajs/eden';
/* test case 1: no prefix with slash */ โ
const noPrefixWithSlash = new Elysia().get('/', () => ({ data: '/' }));
type NoPrefixWithSlash = typeof noPrefixWithSlash;
const noPrefixWithSlashTreaty = edenTreaty<NoPrefixWithSlash>('');
// Works as intended!
await noPrefixWithSlashTreaty.get();
/* test case 2: prefix with slash */ โ
const prefixWithSlash = new Elysia({ prefix: '/prefix' }).get('/', () => ({
data: '/',
}));
type PrefixWithSlash = typeof prefixWithSlash;
const prefixWithSlashTreaty = edenTreaty<PrefixWithSlash>('');
// why is this not mapping to 'prefixWithSlashTreaty.prefix.get()'?
// It does not work the same way as the first test case, where also '/' is used.
await prefixWithSlashTreaty.prefix[''].get();
/* test case 3: no prefix without slash */ โ
const noPrefixWithoutSlash = new Elysia().get('', () => ({ data: '' }));
type NoPrefixWithoutSlash = typeof noPrefixWithoutSlash;
const noPrefixWithoutSlashTreaty = edenTreaty<NoPrefixWithoutSlash>('');
// why is this not mapping to 'noPrefixWithoutSlashTreaty.get()'? Why is it not callable? (property) "": {}
await noPrefixWithoutSlashTreaty[''].get(); // Property 'get' does not exist on type '{}'.ts(2339)
/* test case 4: prefix without slash */ โ
const prefixWithoutSlash = new Elysia({ prefix: '/prefix' }).get('', () => ({
data: '',
}));
type PrefixWithoutSlash = typeof prefixWithoutSlash;
const prefixWithoutSlashTreaty = edenTreaty<PrefixWithoutSlash>('');
// Works as intended!
await prefixWithoutSlashTreaty.prefix.get();
Are test cases 2
and 3
failing intentionally, is this working as intended? I thought these were also possible route and prefix combinations we could use in our Elysia apps with Eden Treaty.
When we log an error from Eden, we get this error logged on console:
Problems:
EdenFetchError
is reduced to E
in console output.Line 11 in c7f0c11
It might make it easier to debug if the error message is more human-friendly e.g. Request failed with status code 400
.post({ body: {} })
sends { "body": {} }
, while
.post({})
somehow sends "[object Object]"
Hi,
I just wanted to raise awareness about this since it took a lot of time for me to figure this out, maybe this will help someone save some time in the future:
I use eden and elysia to provide a typesafe way to access my api. Since I'm writing an API which is intended to be used across many projects, I decided to provide a library/sdk to access it. This library provides some init code and exports an edenTreaty api client to access the actual api. After building my library the output .d.ts files did not contain the correct types. TS replaced the return type of the edenTreaty call with an "any" type. It took me a very long time to figure out, why this was happening:
I used the "baseUrl" property in my tsconfig.json (the one in the API package, not the client) to simplify imports. After replacing that with relative imports, everything works as expected. I'm not really expecting the eden/elysia project to do something about this, so I'll instantly close this, but I wanted to create some record so people don't have to deal with the same issue as long as I did.
I have an Elysia app with authentication using bearer tokens.
export default (app: ElysiaPlugins) => {
return app.ws("/chat/ws", {
body: t.Object({ message: t.String() }),
response: t.Object({ name: t.String(), message: t.String() }),
open: (ws) => {
// require authentication from users
const { auth } = ws.data;
if (!auth.isAuthed) {
ws.close();
}
ws.subscribe("global");
},
message: (ws, { message }) => {
const { auth } = ws.data;
if (!auth.isAuthed) return;
console.log(message);
ws.publish("global", { name: auth.user.name, message });
},
close: () => {
console.log("ws closed");
},
});
};
In order to open a connection I want to require users to pass in a valid token in the request headers (since WS is initially an HTTP request before upgrade).
Using the default JS implementation of WebSocket, I can connect just fine:
// this works & authenticates properly
const socket = new WebSocket("ws://localhost:3000/chat/ws", {
headers: {
Authorization:
"Bearer <token>",
},
});
But I cannot send headers when using an Eden Treaty client:
const api = edenTreaty<ElysiaAPI>("http://localhost:3000");
// fails authentication
const edenSocket = api.chat.ws.subscribe(); // no way to pass in headers here
Attempting to modify $fetch
params doesn't seem to have an effect either, which I assume is because it uses WebSocket and not fetch in its implementation:
// also fails authentication on .subscribe() call
const api = edenTreaty<ElysiaAPI>("http://localhost:3000", {
$fetch: {
headers: {
Authorization:
"Bearer <token>",
},
},
});
I want to be able to pass in headers to the .subscribe
method, or initialize an instance of edenTreaty
configured with the desired set of headers for a WebSocket connection.
Ex:
const api = edenTreaty<ElysiaAPI>("http://localhost:3000", {
$webSocket: { // or inherit options from $fetch ?
headers: {
Authorization:
"Bearer <token>",
},
},
});
// or:
const edenSocket = api.chat.ws.subscribe({ headers: { Authorization: "Bearer <token>" } });
"@elysiajs/eden": "1.0.5",
"elysia": "1.0.7"
index.ts:
import { edenTreaty } from "@elysiajs/eden";
import Elysia from "elysia";
const helloHandlers = new Elysia().get("", () => "hello");
const routes = new Elysia().group("/hello", (_) => _.use(helloHandlers));
const app = new Elysia().use(routes).listen(3000);
const eden = edenTreaty<typeof app>("localhost:3000");
console.log((await eden.hello.get()).data); // <-- has red squiggly, but works!
console.log((await eden.hello.index.get()).data); // <-- no red squiggly, "NOT_FOUND"
app.stop();
output from running:
bun run index.ts
hello
NOT_FOUND
Error message:
Property 'get' does not exist on type '{ index: { get: (params?: { $fetch?: RequestInit | undefined; getRaw?: boolean | undefined; $transform?: Transform<unknown> | undefined; $query?: Record<string, string> | undefined; $headers?: Record<...> | undefined; } | undefined, options?: { ...; } | undefined) => Promise<...>; }; }'
I get a typerror when accessing routes by their name
here's my code
// app.ts
import { Elysia } from 'elysia'
import { helmet } from 'elysia-helmet'
import { rateLimit } from 'elysia-rate-limit'
import swagger from '@elysiajs/swagger'
import cors from '@elysiajs/cors'
import user from './Controllers/user'
import blog from './Controllers/blog'
const app = new Elysia()
app
.onError(({ code, error, path }) => {
if (code === 'NOT_FOUND') {
return { err: `Couldn't find what you was looking for` }
}
if (code === 'INTERNAL_SERVER_ERROR') {
return { err: `Something went wrong on our server, We'll try to fix it ASAP!` }
}
console.error(`Error occurred on path ${path} Error: ${error}, ${code}`)
})
.use(helmet())
.use(swagger())
.use(cors({ origin: process.env.DOMAIN, credentials: true }))
.use(
rateLimit({
duration: 300000,
max: 5200,
responseCode: 420,
responseMessage: 'Our server needs a 5 min coffee break'
})
)
.use(user)
.use(blog)
.onResponse(() => {
console.log(`response speed was: ${performance.now()}ms`)
})
.listen(process.env.PORT || 5000)
console.log(`Server running on ${app.server?.url}`)
export type App = typeof app
// +page.server.ts
import type { PageLoad } from './$types'
import {edenTreaty} from '@elysiajs/eden'
import type { App } from '../../../../../backend/src/app.ts'
const app = edenTreaty<App>(`http://backend:3000`)
export const load = (async ({ url }) => {
const res = app.profile.tech
const user = res.data.user
const state = res.data.state
console.log(res.data)
return { user, state }
}) satisfies PageLoad
Please see https://github.com/MatthewAry/elysia-reproductions/tree/treaty2-cant-resolve-some-routes
If you do something like this:
const nested = new Elysia({ prefix: '/level1/level2' })
.get('', () => 'Level 2')
.get("/:id", ({ params: { id }}) => `You are in the identified route! ${id}`);
Eden treaty 2 will not be able to resolve using TS the second route.
Hi, love your work on Elysia.
Would be great if Eden be used directly with Elysia instance / instance.handle method?
I think I would be useful to have a type-safe client that can be used on the server with RSC, server actions or so.
I imagine something like:
import { edenFetch } from '@elysiajs/eden'
import { App } from './server'
const fetch = edenFetch<App>(App)
export default async function ServerComponent() {
const response = await fetch('/test/:id', {
params: {
id: '1895'
}
});
const data = await response.json(); // <โ- Type inferred
return (
<pre>
{JSON.stringify(data, null, 2)}
</pre>
);
}
Importing they type for my Elysia app, or importing the edenTreaty package causes the react type system to explode.
It appears that even in the turborepo monorepo that @SaltyAom created, for some reason the "bun-types" package conflicts with react types that shows up at the very least when you try to use event handlers and I imagine in other places as well.
For example this bug only shows up when I do a `import type {App} from "../path/to/elysia/app" and goes away completely when I get rid of that line
I have a hunch that this is ultimately coming from the "bun-types" package, but I'm wondering if there is a way to avoid importing the "bun-types" package along with the Elysia app.
Property 'value' does not exist on type 'EventTarget & HTMLInputElement'.
Which will not show up normally in a react application.
bun create vite@latest
bun-types
as a dependencye.target.value
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.