Eden Treaty Legacy â
NOTE
This is a documentation for Eden Treaty 1 or (edenTreaty)
For a new project, we recommended starting with Eden Treaty 2 (treaty) instead.
Eden Treaty is an object-like representation of an Elysia server.
Providing accessor like a normal object with type directly from the server, helping us to move faster, and make sure that nothing break
To use Eden Treaty, first export your existing Elysia server type:
// 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
// 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
Then import the server type, and consume the Elysia API on client:
// client.ts
import { edenTreaty } from '@elysiajs/eden'
import type { App } from './server'
const app = edenTreaty<App>('http://localhost:8080')
// response type: 'Hi Elysia'
const { data: pong, error } = app.get()
// response type: 1895
const { data: id, error } = app.id['1895'].get()
// response type: { id: 1895, name: 'Skadi' }
const { data: nendoroid, error } = app.mirror.post({
id: 1895,
name: 'Skadi'
})
// client.ts
import { edenTreaty } from '@elysiajs/eden'
import type { App } from './server'
const app = edenTreaty<App>('http://localhost:8080')
// response type: 'Hi Elysia'
const { data: pong, error } = app.get()
// response type: 1895
const { data: id, error } = app.id['1895'].get()
// response type: { id: 1895, name: 'Skadi' }
const { data: nendoroid, error } = app.mirror.post({
id: 1895,
name: 'Skadi'
})
TIP
Eden Treaty is fully type-safe with auto-completion support.
Anatomy â
Eden Treaty will transform all existing paths to object-like representation, that can be described as:
EdenTreaty.<1>.<2>.<n>.<method>({
...body,
$query?: {},
$fetch?: RequestInit
})
EdenTreaty.<1>.<2>.<n>.<method>({
...body,
$query?: {},
$fetch?: RequestInit
})
Path â
Eden will transform /
into .
which can be called with a registered method
, for example:
- /path -> .path
- /nested/path -> .nested.path
Path parameters â
Path parameters will be mapped automatically by their name in the URL.
- /id/:id -> .id.
<anyThing>
- eg: .id.hi
- eg: .id['123']
TIP
If a path doesn't support path parameters, TypeScript will show an error.
Query â
You can append queries to path with $query
:
app.get({
$query: {
name: 'Eden',
code: 'Gold'
}
})
app.get({
$query: {
name: 'Eden',
code: 'Gold'
}
})
Fetch â
Eden Treaty is a fetch wrapper, you can add any valid Fetch parameters to Eden by passing it to $fetch
:
app.post({
$fetch: {
headers: {
'x-organization': 'MANTIS'
}
}
})
app.post({
$fetch: {
headers: {
'x-organization': 'MANTIS'
}
}
})
Error Handling â
Eden Treaty will return a value of data
and error
as a result, both fully typed.
// response type: { id: 1895, name: 'Skadi' }
const { data: nendoroid, error } = app.mirror.post({
id: 1895,
name: 'Skadi'
})
if(error) {
switch(error.status) {
case 400:
case 401:
warnUser(error.value)
break
case 500:
case 502:
emergencyCallDev(error.value)
break
default:
reportError(error.value)
break
}
throw error
}
const { id, name } = nendoroid
// response type: { id: 1895, name: 'Skadi' }
const { data: nendoroid, error } = app.mirror.post({
id: 1895,
name: 'Skadi'
})
if(error) {
switch(error.status) {
case 400:
case 401:
warnUser(error.value)
break
case 500:
case 502:
emergencyCallDev(error.value)
break
default:
reportError(error.value)
break
}
throw error
}
const { id, name } = nendoroid
Both data, and error will be typed as nullable until you can confirm their statuses with a type guard.
To put it simply, if fetch is successful, data will have a value and error will be null, and vice-versa.
TIP
Error is wrapped with an Error
with its value return from the server can be retrieve from Error.value
Error type based on status â
Both Eden Treaty and Eden Fetch can narrow down an error type based on status code if you explicitly provided an error type in the Elysia server.
// server.ts
import { Elysia, t } from 'elysia'
const app = new Elysia()
.model({
nendoroid: t.Object({
id: t.Number(),
name: t.String()
}),
error: t.Object({
message: t.String()
})
})
.get('/', () => 'Hi Elysia')
.get('/id/:id', ({ params: { id } }) => id)
.post('/mirror', ({ body }) => body, {
body: 'nendoroid',
response: {
200: 'nendoroid',
400: 'error',
401: 'error'
}
})
.listen(8080)
export type App = typeof app
// server.ts
import { Elysia, t } from 'elysia'
const app = new Elysia()
.model({
nendoroid: t.Object({
id: t.Number(),
name: t.String()
}),
error: t.Object({
message: t.String()
})
})
.get('/', () => 'Hi Elysia')
.get('/id/:id', ({ params: { id } }) => id)
.post('/mirror', ({ body }) => body, {
body: 'nendoroid',
response: {
200: 'nendoroid',
400: 'error',
401: 'error'
}
})
.listen(8080)
export type App = typeof app
An on the client side:
const { data: nendoroid, error } = app.mirror.post({
id: 1895,
name: 'Skadi'
})
if(error) {
switch(error.status) {
case 400:
case 401:
// narrow down to type 'error' described in the server
warnUser(error.value)
break
default:
// typed as unknown
reportError(error.value)
break
}
throw error
}
const { data: nendoroid, error } = app.mirror.post({
id: 1895,
name: 'Skadi'
})
if(error) {
switch(error.status) {
case 400:
case 401:
// narrow down to type 'error' described in the server
warnUser(error.value)
break
default:
// typed as unknown
reportError(error.value)
break
}
throw error
}
WebSocket â
Eden supports WebSocket using the same API as a normal route.
// Server
import { Elysia, t } from 'elysia'
const app = new Elysia()
.ws('/chat', {
message(ws, message) {
ws.send(message)
},
body: t.String(),
response: t.String()
})
.listen(8080)
type App = typeof app
// Server
import { Elysia, t } from 'elysia'
const app = new Elysia()
.ws('/chat', {
message(ws, message) {
ws.send(message)
},
body: t.String(),
response: t.String()
})
.listen(8080)
type App = typeof app
To start listening to real-time data, call the .subscribe
method:
// Client
import { edenTreaty } from '@elysiajs/eden'
const app = edenTreaty<App>('http://localhost:8080')
const chat = app.chat.subscribe()
chat.subscribe((message) => {
console.log('got', message)
})
chat.send('hello from client')
// Client
import { edenTreaty } from '@elysiajs/eden'
const app = edenTreaty<App>('http://localhost:8080')
const chat = app.chat.subscribe()
chat.subscribe((message) => {
console.log('got', message)
})
chat.send('hello from client')
We can use schema to enforce type-safety on WebSockets, just like a normal route.
Eden.subscribe returns EdenWebSocket which extends the WebSocket class with type-safety. The syntax is identical with the WebSocket
If more control is need, EdenWebSocket.raw can be accessed to interact with the native WebSocket API.
File Upload â
You may either pass one of the following to the field to attach file:
- File
- FileList
- Blob
Attaching a file will results content-type to be multipart/form-data
Suppose we have the server as the following:
// server.ts
import { Elysia } from 'elysia'
const app = new Elysia()
.post('/image', ({ body: { image, title } }) => title, {
body: t.Object({
title: t.String(),
image: t.Files(),
})
})
.listen(3000)
export type App = typeof app
// server.ts
import { Elysia } from 'elysia'
const app = new Elysia()
.post('/image', ({ body: { image, title } }) => title, {
body: t.Object({
title: t.String(),
image: t.Files(),
})
})
.listen(3000)
export type App = typeof app
We may use the client as follows:
// client.ts
import { edenTreaty } from '@elysia/eden'
import type { Server } from './server'
export const client = edenTreaty<Server>('http://localhost:3000')
const id = <T extends HTMLElement = HTMLElement>(id: string) =>
document.getElementById(id)! as T
const { data } = await client.image.post({
title: "Misono Mika",
image: id<HTMLInputElement>('picture').files!,
})
// client.ts
import { edenTreaty } from '@elysia/eden'
import type { Server } from './server'
export const client = edenTreaty<Server>('http://localhost:3000')
const id = <T extends HTMLElement = HTMLElement>(id: string) =>
document.getElementById(id)! as T
const { data } = await client.image.post({
title: "Misono Mika",
image: id<HTMLInputElement>('picture').files!,
})