Skip to content

Releases: elysiajs/elysia

1.0 - Lament of the Fallen

16 Mar 15:42
a20d6a6
Compare
Choose a tag to compare

lament-of-the-fallent

Elysia 1.0 is the first stable release after development for 1.8 years.

Since started, we have always waiting for a framework that focuses on developer experience, velocity, and how to make writing code for humans, not a machine.

We battle-test Elysia in various situations, simulate medium and large-scale projects, shipping code to clients and this is the first version that we felt confident enough to ship.

Elysia 1.0 introduces significant improvements and contains 1 necessary breaking change.


It's a tradition that Elysia's release note have a version named after a song or media.

This important version is named after "Lament of the Fallen".

Animated short from "Honkai Impact 3rd" from my favorite arc, and my favorite character, "Raiden Mei" featuring her theme song, "Honkai World Diva".

It's a very good game, and you should check it out.

ー SaltyAom

Also known as Raiden Mei from Gun Girl Z, Honkai Impact 3rd, Honkai Star Rail. And her "variation", Raiden Shogun from Genshin Impact, and possibly Acheron from Honkai Star Rail (since she's likely a bad-end herrscher form mentioned in Star Rail 2.1).

::: tip
Remember, ElysiaJS is an open source library maintain by volunteers, and isn't associate with Mihoyo nor Hoyoverse. But we are a huge fan of Honkai series, alright?
:::

Sucrose

Elysia is optimized to have an excellent performance proven in various benchmarks, one of the main factors is thanks to Bun, and our custom JIT static code analysis.

If you are not aware, Elysia has some sort of "compiler" embedded that reads your code and produces an optimized way to handle functions.

The process is fast and happens on the fly without a need for a build step.
However, it's challenging to maintain as it's written mostly in many complex RegEx, and can be slow at times if recursion happens.

That's why we rewrote our static analysis part to separate the code injection phase using a hybrid approach between partial AST-based and pattern-matching name "Sucrose".

Instead of using full AST-based which is more accurate, we choose to implement only a subset of rules that is needed to improve performance as it needs to be fast on runtime.

Sucrose is good at inferring the recursive property of the handler function accurately with low memory usage, resulting in up to 37% faster inference time and significantly reduced memory usage.

Sucrose is shipped to replace RegEx-based to partial AST, and pattern matching starting from Elysia 1.0.

Improved Startup time

Thanks to Sucrose, and separation from the dynamic injection phase, we can defer the analysis time JIT instead of AOT.

In other words, the "compile" phase can be lazily evaluated.

Offloading the evaluation phase from AOT to JIT when a route is matched for the first time and caching the result to compile on demand instead of all routes before server start.

In a runtime performance, a single compilation is usually fast and takes no longer than 0.01-0.03 ms (millisecond not second).

In a medium-sized application and stress test, we measure up to between ~6.5-14x faster start-up time.

Remove ~40 routes/instance limit

Previously you could only stack up to ~40 routes / 1 Elysia instance since Elysia 0.1.

This is the limitation of TypeScript that each queue that has a limited memory and if exceeded, TypeScript will think that "Type instantiation is excessively deep and possibly infinite".

const main = new Elysia()
    .get('/1', () => '1')
    .get('/2', () => '2')
    .get('/3', () => '3')
    // repeat for 40 times
    .get('/42', () => '42')
    // Type instantiation is excessively deep and possibly infinite

As a workaround, we need to separate an instance into a controller to overcome the limit and remerge the type to offload the queue like this.

const controller1 = new Elysia()
    .get('/42', () => '42')
    .get('/43', () => '43')

const main = new Elysia()
    .get('/1', () => '1')
    .get('/2', () => '2')
    // repeat for 40 times
    .use(controller1)

However, starting from Elysia 1.0, we have overcome the limit after a year after optimizing for type-performance, specifically Tail Call Optimization, and variances.

This means theoretically, we can stack an unlimited amount of routes and methods until TypeScript breaks.

(spoiler: we have done that and it's around 558 routes/instance before TypeScript CLI and language server because of JavaScript memory limit per stack/queue)

const main = new Elysia()
    .get('/1', () => '1')
    .get('/2', () => '2')
    .get('/3', () => '42')
    // repeat for n times
    .get('/550', () => '550')

So we increase the limit of ~40 routes to JavaScript memory limit instead, so try not to stack more than ~558 routes/instance, and separate into a plugin if necessary.

TypeScript breaks on 558 routes

The blocker that made us feel like Elysia is not ready for production has been finally resolved.

Type Inference improvement

Thanks to the effort we put into optimization, we measure up to ~82% in most Elysia servers.

Thanks to the removed limitation of stack, and improved type performance, we can expect almost instant type check and auto-completion even after 500 routes stacks.

type-demo.mp4

Up to 13x faster for Eden Treaty, type inference performance by precomputing the type instead offload type remap to Eden.

Overall, Elysia, and Eden Treaty performing together would be up to ~3.9x faster.

Here's a comparison between the Elysia + Eden Treaty on 0.8 and 1.0 for 450 routes.

Type performance comparison between Elysia Eden 0.8 and 1.0, the graph shows that Elysia 0.8 took ~1500ms while Elysia 1.0 took ~400ms

Stress test with 450 routes for Elysia with Eden Treaty, result as follows:

  • Elysia 0.8 took ~1500ms
  • Elysia 1.0 took ~400ms

And thanks to the removal of stack limitation, and remapping process, it's now possible to stack up to over 1,000 routes for a single Eden Treaty instance.

Treaty 2

We ask you for feedback on Eden Treaty what you like and what could have been improved. and you have given us some flaws in Treaty design and several proposals to improvement.

That's why today, we introduce Eden Treaty 2, an overhaul to a more ergonomic design.

As much as we dislike breaking change, Treaty 2 is a successor to Treaty 1.

What's new in Treaty 2:

  • More ergonomic syntax
  • End-to-end type safety for Unit Test
  • Interceptor
  • No "$" prefix and property

Our favorite one is end-to-end type safety for Unit tests.

So instead of starting a mock server and sending a fetch request, we can use Eden Treaty 2 to write unit tests with auto-completion and type safety instead.

// test/index.test.ts
import { describe, expect, it } from 'bun:test'
import { Elysia } from 'elysia'
import { treaty } from '@elysiajs/eden'

const app = new Elysia().get('/hello', () => 'hi')
const api = treaty(app)

describe('Elysia', () => {
    it('return a response', async () => {
        const { data } = await api.hello.get()

        expect(data).toBe('hi')
    })
})

The difference between the two is that Treaty 2 is a successor to Treaty 1.

We don't intend to introduce any breaking change to Treaty 1 nor force you to update to Treaty 2.

You can choose to continue using Treaty 1 for your current project without updating to Treaty 2, and we maintain it in a maintenance mode.

  • You can import treaty to use Treaty 2.
  • And import edenTreaty for Treaty 1.

The documentation for the new Treaty can be found in Treaty overview, and for Treaty 1 in Treaty legacy

Hook type (breaking change)

We hate breaking changes, and this is the first time we do it in large-scale.

We put a lot of effort into API design to reduce changes made to Elysia, but this is necessary to fix a flawed design.

Previously when we added a hook with "on" like onTransform, or onBeforeHandle, it would become a global hook.

This is great for creating something like a plugin but is not ideal for a local instance like a controller.

const plugin = new Elysia()
    .onBeforeHandle(() => {
        console.log('Hi')
    })
    // log Hi
    .get('/hi', () => 'in plugin')

const app = new Elysia()
    .use(plugin)
    // will also log hi
    .get('/no-hi-please', () => 'oh no')

However, we found several problems arise from this behavior.

  • We found that many developers have a lot of nested guards even on the new instance. Guard is almost used as a way to start a new instance to avoid side effects.
  • global by default may cause unpredictable (side-effect) behavior if not careful, especially in a team with inexperienced developers.
  • We asked many developers both familiar and not familiar with Elysia, and found that mo...
Read more

0.8 - Gate of Steiners

16 Mar 15:17
a54756b
Compare
Choose a tag to compare

Gate

Named after the ending song of Steins;Gate Zero, "Gate of Steiner".

Gate of Steiner isn't focused on new exciting APIs and features but on API stability and a solid foundation to make sure that the API will be stable once Elysia 1.0 is released.

However, we do bring improvement and new features including:

Macro API

Macro allows us to define a custom field to hook and guard by exposing full control of the life cycle event stack.

Allowing us to compose custom logic into a simple configuration with full type safety.

Suppose we have an authentication plugin to restrict access based on role, we can define a custom role field.

import { Elysia } from 'elysia'
import { auth } from '@services/auth'

const app = new Elysia()
    .use(auth)
    .get('/', ({ user }) => user.profile, {
        role: 'admin'
    })

Macro has full access to the life cycle stack, allowing us to add, modify, or delete existing events directly for each route.

const plugin = new Elysia({ name: 'plugin' }).macro(({ beforeHandle }) => {
    return {
        role(type: 'admin' | 'user') {
            beforeHandle(
                { insert: 'before' }, 
                async ({ cookie: { session } }) => {
                  const user = await validateSession(session.value)
                  await validateRole('admin', user)
}
            )
        }
    }
})

We hope that with this macro API, plugin maintainers will be able to customize Elysia to their heart's content opening a new way to interact better with Elysia, and Elysia users will be able to enjoy even more ergonomic API Elysia could provide.

The documentation of Macro API is now available in pattern section.

The next generation of customizability is now only a reach away from your keyboard and imagination.

New Life Cycle

Elysia introduced a new life cycle to fix an existing problem and highly requested API including Resolve and MapResponse:
resolve: a safe version of derive. Execute in the same queue as beforeHandle
mapResponse: Execute just after afterResponse for providing transform function from primitive value to Web Standard Response

Resolve

A "safe" version of derive.

Designed to append new value to context after validation process storing in the same stack as beforeHandle.

Resolve syntax is identical to derive, below is an example of retrieving a bearer header from Authorization plugin.

import { Elysia } from 'elysia'

new Elysia()
    .guard(
        {
            headers: t.Object({
                authorization: t.TemplateLiteral('Bearer ${string}')
            })
        },
        (app) =>
            app
                .resolve(({ headers: { authorization } }) => {
                    return {
                        bearer: authorization.split(' ')[1]
                    }
                })
                .get('/', ({ bearer }) => bearer)
    )
    .listen(3000)

MapResponse

Executed just after "afterHandle", designed to provide custom response mapping from primitive value into a Web Standard Response.

Below is an example of using mapResponse to provide Response compression.

import { Elysia, mapResponse } from 'elysia'
import { gzipSync } from 'bun'

new Elysia()
    .mapResponse(({ response }) => {
        return new Response(
            gzipSync(
                typeof response === 'object'
                    ? JSON.stringify(response)
                    : response.toString()
            )
        )
    })
    .listen(3000)

Why not use afterHandle but introduce a new API?

Because afterHandle is designed to read and modify a primitive value. Storing plugins like HTML, and Compression which depends on creating Web Standard Response.

This means that plugins registered after this type of plugin will be unable to read a value or modify the value making the plugin behavior incorrect.

This is why we introduce a new life-cycle run after afterHandle dedicated to providing a custom response mapping instead of mixing the response mapping and primitive value mutation in the same queue.

Error Function

We can set the status code by using either set.status or returning a new Response.

import { Elysia } from 'elysia'

new Elysia()
    .get('/', ({ set }) => {
        set.status = 418

        return "I'm a teapot"
    })
    .listen(3000)

This aligns with our goal, to just the literal value to the client instead of worrying about how the server should behave.

However, this is proven to have a challenging integration with Eden. Since we return a literal value, we can't infer the status code from the response making Eden unable to differentiate the response from the status code.

This results in Eden not being able to use its full potential, especially in error handling as it cannot infer type without declaring explicit response type for each status.

Along with many requests from our users wanting to have a more explicit way to return the status code directly with the value, not wanting to rely on set.status, and new Response for verbosity or returning a response from utility function declared outside handler function.

This is why we introduce an error function to return a status code alongside with value back to the client.

import { Elysia, error } from 'elysia' // [!code ++]

new Elysia()
    .get('/', () => error(418, "I'm a teapot")) // [!code ++]
    .listen(3000)

Which is an equivalent to:

import { Elysia } from 'elysia'

new Elysia()
    .get('/', ({ set }) => {
        set.status = 418

        return "I'm a teapot"
    })
    .listen(3000)

The difference is that using an error function, Elysia will automatically differentiate from the status code into a dedicated response type, helping Eden to infer a response based on status correctly.

This means that by using error, we don't have to include the explicit response schema to make Eden infers type correctly for each status code.

import { Elysia, error, t } from 'elysia'

new Elysia()
    .get('/', ({ set }) => {
        set.status = 418
        return "I'm a teapot"
    }, { // [!code --]
        response: { // [!code --]
            418: t.String() // [!code --]
        } // [!code --]
    }) // [!code --]
    .listen(3000)

We recommended using error function to return a response with the status code for the correct type inference, however, we do not intend to remove the usage of set.status from Elysia to keep existing servers working.

Static Content

Static Content refers to a response that almost always returns the same value regardless of the incoming request.

This type of resource on the server is usually something like a public File, video or hardcode value that is rarely changed unless the server is updated.

By far, most content in Elysia is static content. But we also found that many cases like serving a static file or serving an HTML page using a template engine are usually static content.

This is why Elysia introduced a new API to optimize static content by determining the Response Ahead of Time.

new Elysia()
    .get('/', () => Bun.file('video/kyuukurarin.mp4')) // [!code --]
    .get('/', Bun.file('video/kyuukurarin.mp4')) // [!code ++]
    .listen(3000)

Notice that the handler now isn't a function but is an inline value instead.

This will improve the performance by around 20-25% by compiling the response ahead of time.

Default Property

Elysia 0.8 updates to TypeBox to 0.32 which introduces many new features including dedicated RegEx, Deref but most importantly the most requested feature in Elysia, default field support.

Now defining a default field in Type Builder, Elysia will provide a default value if the value is not provided, supporting schema types from type to body.

import { Elysia } from 'elysia'

new Elysia()
    .get('/', ({ query: { name } }) => name, {
        query: t.Object({
            name: t.String({
                default: 'Elysia'
            }) 
        })
    })
    .listen(3000)

This allows us to provide a default value from schema directly, especially useful when using reference schema.

Default Header

We can set headers using set.headers, which Elysia always creates a default empty object for every request.

Previously we could use onRequest to append desired values into set.headers, but this will always have some overhead because a function is called.

Stacking functions to mutate an object can be a little slower than having the desired value set in the first hand if the value is always the same for every request like CORS or cache header.

This is why we now support setting default headers out of the box instead of creating an empty object for every new request.

new Elysia()
    .headers({
        'X-Powered-By': 'Elysia'
    })

Elysia CORS plugin also has an update to use this new API to improve this performance.

Performance and notable improvement

As usual, we found a way to optimize El...

Read more

0.7 - Stellar Stellar

20 Sep 11:21
7c51647
Compare
Choose a tag to compare

Landscape of wild and mountain in the night full of star

Name after our never giving up spirit, our beloved Virtual YouTuber, Suicopath Hoshimachi Suisei, and her brilliance voice: 「Stellar Stellar」from her first album:「Still Still Stellar」

For once being forgotten, she really is a star that truly shine in the dark.

Stellar Stellar brings many exciting new update to help Elysia solid the foundation, and handle complexity with ease, featuring:

  • Entirely rewrite type, up to 13x faster type inference.
  • "Trace" for declarative telemetry and better performance audit.
  • Reactive Cookie model and cookie valiation to simplify cookie handling.
  • TypeBox 0.31 with a custom decoder support.
  • Rewritten Web Socket for even better support.
  • Definitions remapping, and declarative affix for preventing name collision.
  • Text-based status

Rewritten Type

Core feature of Elysia about developer experience.

Type is one of the most important aspect of Elysia, as it allows us to do many amazing thing like unified type, syncing your business logic, typing, documentation and frontend.

We want you to have an outstanding experience with Elysia, focusing on your business logic part, and let's Elysia handle the rest whether it's type-inference with unified type, and Eden connector for syncing type with backend.

To achieve that, we put our effort on creating a unified type system for to synchronize all of the type, but as the feature grow, we found that our type inference might not be fast enough from our lack of TypeScript experience we have year ago.

With our experience we made along the way of handling complex type system, various optimization and many project like Mobius. We challenge our self to speed up our type system once again, making this a second type rewrite for Elysia.

We delete and rewrite every Elysia type from ground up to make Elysia type to be magnitude faster.

Here's a comparison between 0.6 and 0.7 on a simple Elysia.get code:

0.6

0.7

With our new found experience, and newer TypeScript feature like const generic, we are able to simplify a lot of our code, reducing our codebase over a thousand line in type.

Allowing us to refine our type system to be even faster, and even more stable.

Comparison between Elysia 0.6 and 0.7 on complex project with our 300 routes, and 3,500 lines of type declaration

Using Perfetto and TypeScript CLI to generate trace on a large-scale and complex app, we measure up to 13x inference speed.

And if you might wonder if we might break type inference with 0.6 or not, we do have a unit test in type-level to make sure most of the case, there's no breaking change for type.

We hope this improvement will help you with even faster type inference like faster auto-completion, and load time from your IDE to be near instant to help your development to be even more faster and more fluent than ever before.

Trace

Performance is another one of important aspect for Elysia.

We don't want to be fast for benchmarking purpose, we want you to have a real fast server in real-world scenario, not just benchmarking.

There are many factor that can slow down your app, and it's hard to identifying one, that's why we introduce "Trace".

Trace allow us to take tap into a life-cycle event and identifying performance bottleneck for our app.

Example of usage of Trace

This example code allow us tap into all beforeHandle event, and extract the execution time one-by-one before setting the Server-Timing API to inspect the performance bottleneck.

And this is not limited to only beforeHandle, and event can be trace even the handler itself. The naming convention is name after life-cycle event you are already familiar with.

This API allows us to effortlessly auditing performance bottleneck of your Elysia server and integrate with the report tools of your choice.

By default, Trace use AoT compilation and Dynamic Code injection to conditionally report and even that you actually use automatically, which means there's no performance impact at all.

Reactive Cookie

We merged our cookie plugin into Elysia core.

Same as Trace, Reactive Cookie use AoT compilation and Dynamic Code injection to conditionally inject the cookie usage code, leading to no performance impact if you don't use one.

Reactive Cookie take a more modern approach like signal to handle cookie with an ergonomic API.

Example usage of Reactive COokie

There's no getCookie, setCookie, everything is just a cookie object.

When you want to use cookie, you just extract the name get/set its value like:

app.get('/', ({ cookie: { name } }) => {
    // Get
    name.value

    // Set
    name.value = "New Value"
})

Then cookie will be automatically sync the value with headers, and the cookie jar, making the cookie object a single source of truth for handling cookie.

The Cookie Jar is reactive, which means that if you don't set the new value for the cookie, the Set-Cookie header will not be send to keep the same cookie value and reduce performance bottleneck.

Cookie Schema

With the merge of cookie into the core of Elysia, we introduce a new Cookie Schema for validating cookie value.

This is useful when you have to strictly validate cookie session or want to have a strict type or type inference for handling cookie.

app.get('/', ({ cookie: { name } }) => {
    // Set
    name.value = {
        id: 617,
        name: 'Summoning 101'
    }
}, {
    cookie: t.Cookie({
        value: t.Object({
            id: t.Numeric(),
            name: t.String()
        })
    })
})

Elysia encode and decode cookie value for you automatically, so if you want to store JSON in a cookie like decoded JWT value, or just want to make sure if the value is a numeric string, you can do that effortlessly.

Cookie Signature

And lastly, with an introduction of Cookie Schema, and t.Cookie type. We are able to create a unified type for handling sign/verify cookie signature automatically.

Cookie signature is a cryptographic hash appended to a cookie's value, generated using a secret key and the content of the cookie to enhance security by adding a signature to the cookie.

This make sure that the cookie value is not modified by malicious actor, helps in verifying the authenticity and integrity of the cookie data.

To handle cookie signature in Elysia, it's a simple as providing a secert and sign property:

new Elysia({
    cookie: {
        secret: 'Fischl von Luftschloss Narfidort'
    }
})
    .get('/', ({ cookie: { profile } }) => {
        profile.value = {
            id: 617,
            name: 'Summoning 101'
        }
    }, {
        cookie: t.Cookie({
            profile: t.Object({
                id: t.Numeric(),
                name: t.String()
            })
        }, {
            sign: ['profile']
        })
    })

By provide a cookie secret, and sign property to indicate which cookie should have a signature verification.

Elysia then sign and unsign cookie value automatically, eliminate the need of sign / unsign function manually.

Elysia handle Cookie's secret rotation automatically, so if you have to migrate to a new cookie secret, you can just append the secret, and Elysia will use the first value to sign a new cookie, while trying to unsign cookie with the rest of the secret if match.

new Elysia({
    cookie: {
        secret: ['Vengeance will be mine', 'Fischl von Luftschloss Narfidort']
    }
})

The Reactive Cookie API is declarative and straigth forward, and there's some magical thing about the ergonomic it provide, and we really looking forward for you to try it.

TypeBox 0.31

With the release of 0.7, we are updating to TypeBox 0.31 to brings even more feature to Elysia.

This brings new exciting feature like support for TypeBox's Decode in Elysia natively.

Previously, a custom type like Numeric require a dyanmic code injection to convert numeric string to number, but with the use of TypeBox's decode, we are allow to define a custom function to encode and decode the value of a type automatically.

Allowing us to simplify type to:

Numeric: (property?: NumericOptions<number>) =>
    Type.Transform(Type.Union([Type.String(), Type.Number(property)]))
        .Decode((value) => {
            const number = +value
            if (isNaN(number)) return value

            return number
        })
        .Encode((value) => value) as any as TNumber,

Instead of relying on an extensive check and code injection, it's simplified by a Decode function in TypeBox.

We have rewrite all type that require Dynamic Code Injection to use Transform for easier code maintainance.

Not only limited to that, with t.Transform you can now also define a custom type to with a custom function to Encode and Decode manually, allowing you to write a more expressive code than ever before.

We can't wait to see what you will brings with the introduction of t.Transform.

New Type

With an introduction Transform, we have add a new type like t.ObjectString to automatically decode a value of Object in request.

This is useful when you have to u...

Read more

0.6 - This Game

20 Sep 11:16
7b638c3
Compare
Choose a tag to compare

Chess piece

Named after the opening of the legendary anime, "No Game No Life", 「This Game」composed by Konomi Suzuki.

This Game push the boundary of medium-size project to large-scale app with re-imagined plugin model, dynamic mode, pushing developer experience with declarative custom error, collecting more metric with 'onResponse', customizable loose and strict path mapping, TypeBox 0.30 and WinterCG framework interlop.

(We are still waiting for No Game No Life season 2)

Read more on Elysia release note.

0.5 - Radiant

15 May 17:54
f7c72bd
Compare
Choose a tag to compare

radiant

Named after Arknights' original music, 「Radiant」composed by Monster Sirent Records.

Radiant push the boundary of performance with more stability improvement especially types, and dynamic routes.

Static Code Analysis

With Elysia 0.4 introducing Ahead of Time compliation, allowing Elysia to optimize function calls, and eliminate many overhead we previously had.

Today we are expanding Ahead of Time compliation to be even faster wtih Static Code Analysis, to be the fastest Bun web framework.

Static Code Analysis allow Elysia to read your function, handlers, life-cycle and schema, then try to adjust fetch handler compile the handler ahead of time, and eliminating any unused code and optimize where possible.

For example, if you're using schema with body type of Object, Elysia expect that this route is JSON first, and will parse the body as JSON instead of relying on dynamic checking with Content-Type header:

app.post('/sign-in', ({ body }) => signIn(body), {
    schema: {
        body: t.Object({
            username: t.String(),
            password: t.String()
        })
    }
})

This allows us to improve performance of body parsing by almost 2.5x.

With Static Code Analysis, instead of relying on betting if you will use expensive properties or not.

Elysia can read your code and detect what you will be using, and adjust itself ahead of time to fits your need.

This means that if you're not using expensive property like query, or body, Elysia will skip the parsing entirely to improve the performance.

// Body is not used, skip body parsing
app.post('/id/:id', ({ params: { id } }) => id, {
    schema: {
        body: t.Object({
            username: t.String(),
            password: t.String()
        })
    }
})

With Static Code Analysis, and Ahead of Time compilation, you can rest assure that Elysia is very good at reading your code and adjust itself to maximize the performance automatically.

Static Code Analysis allows us to improve Elysia performance beyond we have imagined, here's a notable mention:

  • overall improvement by ~15%
  • static router fast ~33%
  • empty query parsing ~50%
  • strict type body parsing faster by ~100%
  • empty body parsing faster by ~150%

With this improvement, we are able to surpass Stricjs in term of performance, compared using Elysia 0.5.0-beta.0 and Stricjs 2.0.4

We intent to explain this in more detail with our research paper to explain this topic and how we improve the performance with Static Code Analysis to be published in the future.

New Router, "Memoirist"

Since 0.2, we have been building our own Router, "Raikiri".

Raikiri served it purposed, it's build on the ground up to be fast with our custom Radix Tree implementation.

But as we progress, we found that Raikiri doesn't perform well complex recoliation with of Radix Tree, which cause developers to report many bugs especially with dynamic route which usually solved by re-ordering routees.

We understand, and patched many area in Raikiri's Radix Tree reconcilation algorithm, however our algorithm is complex, and getting more expensive as we patch the router until it doesn't fits our purpose anymore.

That's why we introduce a new router, "Memoirist".

Memoirist is a stable Raix Tree router to fastly handle dynamic path based on Medley Router's algorithm, while on the static side take advantage of Ahead of Time compilation.

With this release, we will be migrating from Raikiri to Memoirist for stability improvement and even faster path mapping than Raikiri.

We hope that any problems you have encountered with Raikiri will be solved with Memoirist and we encourage you to give it a try.

TypeBox 0.28

TypeBox is a core library that powered Elysia's strict type system known as Elysia.t.

In this update, we update TypeBox from 0.26 to 0.28 to make even more fine-grained Type System near strictly typed language.

We update Typebox to improve Elysia typing system to match new TypeBox feature with newer version of TypeScript like Constant Generic

new Elysia()
    .decorate('version', 'Elysia Radiant')
    .model(
        'name',
        Type.TemplateLiteral([
            Type.Literal('Elysia '),
            Type.Union([
                Type.Literal('The Blessing'),
                Type.Literal('Radiant')
            ])
        ])
    )
    // Strictly check for template literal
    .get('/', ({ version }) => version)

This allows us to strictly check for template literal, or a pattern of string/number to validate for your on both runtime and development process all at once.

Ahead of Time & Type System

And with Ahead of Time compilation, Elysia can adjust itself to optimize and match schema defined type to reduce overhead.

That's why we introduced a new Type, URLEncoded.

As we previously mentioned before, Elysia now can take an advantage of schema and optimize itself Ahead of Time, body parsing is one of more expensive area in Elysia, that's why we introduce a dedicated type for parsing body like URLEncoded.

By default, Elysia will parse body based on body's schema type as the following:

  • t.URLEncoded -> application/x-www-form-urlencoded
  • t.Object -> application/json
  • t.File -> multipart/form-data
  • the rest -> text/plain

However, you can explictly tells Elysia to parse body with the specific method using type as the following:

app.post('/', ({ body }) => body, {
    type: 'json'
})

type may be one of the following:

type ContentType = |
    // Shorthand for 'text/plain'
    | 'text'
    // Shorthand for 'application/json'
    | 'json'
    // Shorthand for 'multipart/form-data'
    | 'formdata'
    // Shorthand for 'application/x-www-form-urlencoded'\
    | 'urlencoded'
    | 'text/plain'
    | 'application/json'
    | 'multipart/form-data'
    | 'application/x-www-form-urlencoded'

You can find more detail at explicity body page in concept.

Numeric Type

We found that one of the redundant task our developers found using Elysia is to parse numeric string.

That's we introduce a new Numeric Type.

Previously on Elysia 0.4, to parse numeric string, we need to use transform to manually parse the string ourself.

app.get('/id/:id', ({ params: { id } }) => id, {
    schema: {
        params: t.Object({
            id: t.Number()
        })
    },
    transform({ params }) {
        const id = +params.id

        if(!Number.isNan(id))
            params.id = id
    }
})

We found that this step is redundant, and full of boiler-plate, we want to tap into this problem and solve it in a declarative way.

Thanks to Static Code Analysis, Numeric type allow you to defined a numeric string and parse it to number automatically.

Once validated, a numeric type will be parsed as number automatically both on runtime and type level to fits our need.

app.get('/id/:id', ({ params: { id } }) => id, {
    params: t.Object({
        id: t.Numeric()
    })
})

You can use numeric type on any property that support schema typing, including:

  • params
  • query
  • headers
  • body
  • response

We hope that you will find this new Numeric type useful in your server.

You can find more detail at numeric type page in concept.

With TypeBox 0.28, we are making Elysia type system we more complete, and we excited to see how it play out on your end.

Inline Schema

You might have notice already that our example are not using a schema.type to create a type anymore, because we are making a breaking change to move schema and inline it to hook statement instead.

// ? From
app.get('/id/:id', ({ params: { id } }) => id, {
    schema: {
        params: t.Object({
            id: t.Number()
        })
    },
})

// ? To
app.get('/id/:id', ({ params: { id } }) => id, {
    params: t.Object({
        id: t.Number()
    })
})

We think a lot when making a breaking change especially to one of the most important part of Elysia.

Based on a lot of tinkering and real-world usage, we try to suggest this new change for our community with a vote, and found that around 60% of Elysia developer are happy with migrating to the inline schema.

But we also listen the the rest of our community, and try to get around with the argument against this decision:

Clear separation

With the old syntax, you have to explicitly tells Elysia that the part you are creating are a schema using Elysia.t.

Creating a clear separation between life-cycle and schema are more clear and has a better readability.

But from our intense test, we found that most people don't have any problem struggling reading a new syntax, separating life-cycle hook from schema type, we found that it still has clear separation with t.Type and function, and a different syntax highlight when reviewing the code, although not as good as clear as explicit schema, but people can get used to the new syntax very quickly especially if they are familiar the Elysia.

Auto completion

One of the other area that people are concerned about are reading auto-completion.

Merging schema and life-cycle hook caused the auto-completion to have around 10 properties for auto-complete to suggest, and based on many proven general User Experience research, it can be frastating for user to that many options to choose from, and can cause a steeper learning curve.

However, we found that the schema property name of Elysia is quite predictable to get over this problem once developer are used to Elysia type.

For example, if you want to access a ...

Read more

0.4 - 月夜の音楽会 (Moonlit Night Concert)

31 Mar 01:52
0793a6c
Compare
Choose a tag to compare

moonlit night concert

Named after the opening music of "The Liar Princess and the Blind Prince" trailer, 「月夜の音楽会」(Moonlit Night Concert) composed and sang by Akiko Shikata.

This version doesn't introduce an exciting new feature, later but a foundation for more solid ground, and internal improvement for the future of Elysia.

Ahead of Time Complie

By default, Elysia has to deal with conditional checking in various situations, for example, checking if the life-cycle of the route existed before performing, or unwrapping validation schema if provided.

This introduces a minimal overhead to Elysia and overall because even if the route doesn't have a life-cycle event attached to it, it needs to be runtime checked.

Since every function is checked on compile time, it's not possible to have a conditional async, for example, a simple route that returns a file should be synced, but since it's compile-time checking, some routes might be async thus making the same simple route async too.

An async function introduces an additional tick to the function, making it a bit slower. But since Elysia is a foundation for web servers, we want to optimize every part to make sure that you don't run into performance problems.

We fix this small overhead by introducing Ahead Time Compilation.

As the name suggests, instead of checking dynamic life-cycle and validation on the runtime, Elysia checks life-cycle, validation, and the possibility of an async function and generates a compact function, removing an unnecessary part like an un-used life-cycle and validation.

Making conditional async function possible, since instead of using a centralized function for handling, we compose a new function especially created for each route instead. Elysia then checks all life-cycle functions and handlers to see if there's an async, and if not, the function will be synced to reduce additional overhead.

TypeBox 0.26

TypeBox is a library that powered Elysia's validation and type provider to create a type-level single source of truth, re-exported as Elysia.t.

In this update, we update TypeBox from 0.25.4 to 0.26.

This brings a lot of improvement and new features, for example, a Not type and Convert for coercion value which we might support in some next version of Elysia.

But the one benefit for Elysia would be, Error.First() which allows us to get the first error of type instead of using Iterator, this reduces overhead in creating a new Error to send back to the client.

There are some changes to TypeBox and Elysia.t that normally wouldn't have much effect on your end, but you can see what's a new feature in TypeBox release here.

Validate response per status

Previously, Elysia's response validate multiple status responses using union type.

This might have unexpected results for highly dynamic apps with a strict response for status.
For example if you have a route like:

app.post('/strict-status', process, {
    schema: {
        response: {
            200: t.String(),
            400: t.Number()
        }
    }
})

It's expected that if 200 response is not a string, then it should throw a validation error, but in reality, it wouldn't throw an error because response validation is using union. This might leave an unexpected value to the end user and a type error for Eden Treaty.

With this release, a response is validated per status instead of union, which means that it will strictly validate based on response status instead of unioned type.

Separation of Elysia Fn

Elysia Fn is a great addition to Elysia, with Eden, it breaks the boundary between client and server allowing you to use any server-side function in your client, fully type-safe and even with primitive types like Error, Set, and Map.

But with the primitive type support, Elysia Fn depends on "superjson" which is around 38% of Elysia's dependency size.

In this release, to use Elysia Fn, you're required to explicitly install @elysiajs/fn to use Elysia Fn. This approach is like installing an additional feature same as cargo add --feature.

This makes Elysia lighter for servers that don't use Elysia Fn, Following our philosophy, To ensure that you have what you actually need

However, if you forgot to install Elysia Fn and accidentally use Elysia Fn, there will be a type warning reminding you to install Elysia Fn before usage, and a runtime error telling the same thing.

For migration, besides a breaking change of installing @elysiajs/fn explicitly, there's no migration need.

Conditional Route

This release introduces .if method for registering a conditional route or plugin.

This allows you to declaratively for a specific conditional, for example excluding Swagger documentation from the production environment.

const isProduction = process.env.NODE_ENV === 'production'

const app = new Elysia().if(!isProduction, (app) =>
    app.use(swagger())
)

Eden Treaty will be able to recognize the route as if it's a normal route/plugin.

Custom Validation Error

Big thanks to amirrezamahyari on #31 which allows Elysia to access TypeBox's error property, for a better programmatically error response.

new Elysia()
    .onError(({ code, error, set }) => {
        if (code === 'NOT_FOUND') {
            set.status = 404

            return 'Not Found :('
        }

        if (code === 'VALIDATION') {
            set.status = 400

            return {
                fields: error.all()
            }
        }
    })
    .post('/sign-in', () => 'hi', {
        schema: {
            body: t.Object({
                username: t.String(),
                password: t.String()
            })
        }
    })
    .listen(8080)

Now you can create a validation error for your API not limited to only the first error.


Notable Improvement:

  • Update TypeBox to 0.26.8
  • Inline a declaration for response type
  • Refactor some type for faster response
  • Use Typebox Error().First() instead of iteration
  • Add innerHandle for returning an actual response (for benchmark)

Breaking Change:

  • Separate .fn to @elysiajs/fn

Afterward

This release might not be a big release with a new exciting feature, but this improve a solid foundation, and Proof of Concept for planned I have for Elysia in the future, and making Elysia even faster and more versatile than it was.

I'm really excited for what will be unfold in the future.

Thank you for your continuous support for Elysia~

the moonlit night concert, our secret

let’s start again without letting go of this hand

the moonlit night concert, our bonds

I want to tell you, “you are not a liar”

the memories in my heart is like flower that keeps blooming

no matter what you look like, you are my songstress

be by my side tonight

0.3 ー「大地の閾を探して [Looking for Edge of Ground] 」

17 Mar 13:03
341e9fa
Compare
Choose a tag to compare

Looking for Edge of Ground

Named after Camellia's song「大地の閾を探して [Looking for Edge of Ground]」ft. Hatsune Miku, is the last track of my most favorite's Camellia album,「U.U.F.O」. This song has a high impact on me personally, so I'm not taking the name lightly.

This is the most challenging update, bringing the biggest release of Elysia yet, with rethinking and redesigning of Elysia architecture to be highly scalable while making less breaking change as possible.

I'm pleased to announce the release candidate of Elysia 0.3 with exciting new features coming right up.

Elysia Fn

Introducing Elysia Fn, run any backend function on the frontend with full auto-completion and full type support.

c.12.02.26.mp4

For rapid development, Elysia Fn allows you to "expose" backend code to call from the frontend with full type-safety, autocompletion, original code comment, and "click-to-definition", allowing you to speed up the development.

You can use Elysia Fn with Eden for full-type safety via Eden Fn.

Permission

You can limit allow or deny scopes of the function, check for authorization header and other headers' fields, validate parameters, or limit keys access programmatically.

Keys checking supports type-safety and auto-completion of all possible functions, so you're not missing out on some function or accidentally typing down the wrong name.
Narrowed Key

And narrowing the scope of property programmatically also narrow down the type of parameters, or in other words, full type-safety.
Narrowed Params

Technical detail

In technical detail, Elysia Fn uses JavaScript's Proxy to capture object property, and parameters to create batched requests to the server to handle and returns the value across the network.
Elysia Fn extends superjson, allowing native type in JavaScript like Error, Map, Set, and undefined to parse across JSON data.

Elysia Fn supports multiple use-cases, for example accessing Prisma on the client-side Nextjs app.
Theoretically, it's possible to use Redis, Sequelize, RabbitMQ, and more.
As Elysia is running on Bun, Elysia Fn can run over 1.2 million operation/second concurrently (tested on M1 Max).

Learn more about Elysia Fn at Eden Fn.

Type Rework

Over 6.5-9x faster for type checking, and uncountable type's LoC reduction.

Elysia 0.3, over 80% of Elysia, and Eden types have been rewritten to focus on performance, type-inference, and fast auto-completion.

Testing for over 350 routes with complex types, Elysia uses only 0.22
seconds to generate a type declaration to use with Eden.

As the Elysia route now compile directly to literal object instead of Typebox reference, Elysia type declaration is much smaller than it used to be on 0.2 and is easier to be consumed by Eden. And by much smaller, it means 50-99% smaller.

Not only Elysia integration with TypeScript is significantly faster, but Elysia is better at understanding TypeScript and your code better.

For example, with 0.3, Elysia will be less strict with plugin registration, allowing you to register the plugin without full type-completion of Elysia Instance.
Inlining use function now infers the parent type, and the nested guard can reference types of models from the parent more accurately.

Type Declaration is now also can be built, and exported.

With the rewrite of type, Elysia understands TypeScript far better than it used to, type-completion will be significantly faster than it was, and we encourage you to try it out to see how fast it is.
For more detail, see this thread on Twitter

File Upload

Thanks to Bun 0.5.7, Form Data is implemented and enabled by default in Elysia 0.3 with multipart/formdata.

To define type completion and validation for uploading a file, Elysia.t now extends TypeBox with File and Files for file validation.

The validation includes checking for file type with auto-completion of standard file size, the minimum and maximum size of the file, and the total of files per field.

Elysia 0.3 also features schema.contentType to explicitly validate incoming request type to strictly check headers before validating the data.

OpenAPI Schema 3.0.x

With Elysia 0.3, Elysia now uses OpenAPI schema 3.0.x by default for better stating API definitions, and better support for multiple types based on content-type.

schema.details are now updated to OpenAPI 3.0.x, and Elysia also updates the Swagger plugin to match the OpenAPI 3.0.x to take advantage of new features in OpenAPI 3 and Swagger, especially with file uploading.

Eden Rework

To support more demand for Elysia, supporting Elysia Fn, Rest all together, Eden has been reworked to scale with the new architecture.

Eden now exports 3 types of function.

  • Eden Treaty eden/treaty: Original Eden syntax you know and love
  • Eden Fn eden/fn: Access to Eden Fn
  • Eden Fetch eden/fetch: Fetch-like syntax, for highly complex Elysia type (> 1,000 route / Elysia instance)

With the rework of type definitions and support for Elysia Eden, Eden is now much faster and better at inference type from the server.

Auto-completion and faster and use fewer resources than it used to, in fact, Eden's type declaration has been almost 100% reworked to reduce the size and inference time, making it support over 350 routes of auto-completion in a blink of an eye (~0.26 seconds).

To make Elysia Eden, fully type-safe, with Elysia's better understanding of TypeScript, Eden can now narrow down the type based on response status, allowing you to capture the type correctly in any matter of condition.
Narrowed error.webp

Notable Improvement:

  • Add string format: 'email', 'uuid', 'date', 'date-time'
  • Update @sinclair/typebox to 0.25.24
  • Update Raikiri to 0.2.0-beta.0 (ei)
  • Add file upload test thanks to #21 (@amirrezamahyari)
  • Pre compile lowercase method for Eden
  • Reduce complex instruction for most Elysia types
  • Compile ElysiaRoute type to literal
  • Optimize type compliation, type inference and auto-completion
  • Improve type compilation speed
  • Improve TypeScript inference between plugin registration
  • Optimize TypeScript inference size
  • Context creation optimization
  • Use Raikiri router by default
  • Remove unused function
  • Refactor registerSchemaPath to support OpenAPI 3.0.3
  • Add error inference for Eden
  • Mark @sinclair/typebox as optional peerDenpendencies

Fix:

  • Raikiri 0.2 thrown error on not found
  • Union response with t.File is not working
  • Definitions isn't defined on Swagger
  • details are missing on group plugin
  • group plugin, isn't unable to compile schema
  • group is not exportable because EXPOSED is a private property
  • Multiple cookies doesn't set content-type to application/json
  • EXPOSED is not export when using fn.permission
  • Missing merged return type for .ws
  • Missing nanoid
  • context side-effects
  • t.Files in swagger is referring to single file
  • Eden response type is unknown
  • Unable to type setModel inference definition via Eden
  • Handle error thrown in non permission function
  • Exported variable has or is using name 'SCHEMA' from external module
  • Exported variable has or is using name 'DEFS' from external module
  • Possible errors for building Elysia app with declaration: true in tsconfig.json

Breaking Change:

  • Rename inject to derive
  • Depreacate ElysiaRoute, changed to inline
  • Remove derive
  • Update from OpenAPI 2.x to OpenAPI 3.0.3
  • Move context.store[SYMBOL] to meta[SYMBOL]

Afterward

With the introduction of Elysia Fn, I'm personally excited to see how it will be adopted in frontend development, removing the line between frontend and backend. And Type Rework of Elysia, making type-checking and auto-completion much faster.

I'm excited to see how you will use Elysia to create the wonderful things you are going to build.

We have Discord server dedicated to Elysia. Feel free to say hi or just chill and hang out.

Thank you for supporting Elysia.

Under a celestial map that never have ends

On a cliff that never have name

I just holwed

Hoping the neverending reverberation will reach you

And I believe someday, I will stand on edge of the ground

(Until the day I can be back to you to tell it)

0.3 ー「大地の閾を探して [Looking for Edge of Ground] 」(RC)

07 Mar 13:31
9f6a997
Compare
Choose a tag to compare

edge of ground

Named after Camellia's song「大地の閾を探して [Looking for Edge of Ground]」ft. Hatsune Miku, is the last track of my most favorite's Camellia album,「U.U.F.O」. This song has a high impact on me personally, so I'm not taking the name lightly.

This is the most challenging update, bringing the biggest release of Elysia yet, with rethinking and redesigning of Elysia architecture to be highly scalable while making less breaking change as possible.

And I'm pleased to announce the release candidate of Elysia 0.3 with exciting new features coming right up.

Elysia Fn

Introducing Elysia Fn, run any backend function on the frontend with full auto-completion and full type support.

c.12.02.26.mp4

For rapid development, Elysia Fn allows you to "expose" backend code to call from the frontend with full type-safety, autocompletion, original code comment, and "click-to-definition", allowing you to speed up the development.

You can use Elysia Fn with Eden for full-type safety via Eden Fn.

Permission

You can limit allow or deny scopes of the function, check for authorization header and other headers' fields, validate parameters, or limit keys access programmatically.

Keys checking supports type-safety and auto-completion of all possible functions, so you're not missing out on some function or accidentally typing down the wrong name.
FpvAKdSaEAAkyyk

And narrowing the scope of property programmatically also narrow down the type of parameters, or in other words, full type-safety.
FpvAY9yaMAAzqyL

Technical detail

In technical detail, Elysia Fn uses JavaScript's Proxy to capture object property, and parameters to create batched requests to the server to handle and returns the value across the network.
Elysia Fn extends superjson, allowing native type in JavaScript like Error, Map, Set, and undefined to parse across JSON data.

Elysia Fn supports multiple use-cases, for example accessing Prisma on the client-side Nextjs app.
Theoretically, it's possible to use Redis, Sequelize, RabbitMQ, and more.
As Elysia is running on Bun, Elysia Fn can run over 1.2 million operation/second concurrently (tested on M1 Max).

Type Rework

Over 6.5-9x faster for type checking, and uncountable type's LoC reduction.

Elysia 0.3, over 80% of Elysia, and Eden types have been rewritten to focus on performance, type-inference, and fast auto-completion.

Testing for over 350 routes with complex types, Elysia uses only 0.22
seconds to generate a type declaration to use with Eden.

As the Elysia route now compile directly to literal object instead of Typebox reference, Elysia type declaration is much smaller than it used to be on 0.2 and is easier to be consumed by Eden. And by much smaller, it means 50-99% smaller.

Not only Elysia integration with TypeScript is significantly faster, but Elysia is better at understanding TypeScript and your code better.

For example, with 0.3, Elysia will be less strict with plugin registration, allowing you to register the plugin without full type-completion of Elysia Instance.
Inlining use function now infers the parent type, and the nested guard can reference types of models from the parent more accurately.

Type Declaration is now also can be built, and exported.

With the rewrite of type, Elysia understands TypeScript far better than it used to, type-completion will be significantly faster than it was, and we encourage you to try it out to see how fast it is.
For more detail, see this thread on Twitter

File Upload

Thanks to Bun 0.5.7, Form Data is implemented and enabled by default in Elysia 0.3 with multipart/formdata.

To define type completion and validation for uploading a file, Elysia.t now extends TypeBox with File and Files for file validation.

The validation includes checking for file type with auto-completion of standard file size, the minimum and maximum size of the file, and the total of files per field.

Elysia 0.3 also features schema.contentType to explicitly validate incoming request type to strictly check headers before validating the data.

OpenAPI Schema 3.0.x

With Elysia 0.3, Elysia now uses OpenAPI schema 3.0.x by default for better stating API definitions, and better support for multiple types based on content-type.

schema.details are now updated to OpenAPI 3.0.x, and Elysia also updates the Swagger plugin to match the OpenAPI 3.0.x to take advantage of new features in OpenAPI 3 and Swagger, especially with file uploading.

Eden Rework

To support more demand for Elysia, supporting Elysia Fn, Rest all together, Eden has been reworked to scale with the new architecture.

Eden now exports 3 types of function.
Eden Treaty eden/treaty: Original Eden syntax you know and love,
Eden Fn eden/fn: Access to Eden Fn
Eden Fetch eden/fetch: Fetch-like syntax, for highly complex Elysia type (> 1,000 route / Elysia instance)

With the rework of type definitions and support for Elysia Eden, Eden is now much faster and better at inference type from the server.

Auto-completion and faster and use fewer resources than it used to, in fact, Eden's type declaration has been almost 100% reworked to reduce the size and inference time, making it support over 350 routes of auto-completion in a blink of an eye (~0.26 seconds).

To make Elysia Eden, fully type-safe, with Elysia's better understanding of TypeScript, Eden can now narrow down the type based on response status, allowing you to capture the type correctly in any matter of condition.
narrowed-error

Notable Improvement:

  • Improve TypeScript inference between plugin registration
  • Optimize TypeScript inference size
  • Context creation optimization
  • Use Raikiri router by default
  • Remove unused function
  • Refactor registerSchemaPath to support OpenAPI 3.0.3
  • Add error inference for Eden
  • Mark @sinclair/typebox as optional peerDenpendencies

Fix:

  • Exported variable has or is using name 'SCHEMA' from an external module
  • Exported variable has or is using name 'DEFS' from an external module
  • Possible errors for building the Elysia app with declaration: true in tsconfig.json

Breaking Change:

  • Remove derive
  • Update from OpenAPI 2.x to OpenAPI 3.0.3
  • Moved context.store[SYMBOL] to meta[SYMBOL] (internal)

Afterword

With the introduction of Elysia Fn, I'm personally excited to see how it will be adopted in frontend development, removing the line between frontend and backend. And Type Rework of Elysia, making type-checking and auto-completion much faster.

I'm excited to see how you will use Elysia to create the wonderful things you are going to build.

We have Discord server dedicated to Elysia. Feel free to say hi or just chill and hang out.

Thank you for supporting Elysia.

Under a celestial map that never have ends
On a cliff that never have name
I just holwed
Hoping the neverending reverberation will reach you
And I believe someday, I will stand on edge of the ground
(Until the day I can be back to you to tell it)

0.2 ー「The Blessing」

27 Jan 11:05
51070e6
Compare
Choose a tag to compare

The Blessing

Blessing」brings more improvement, mainly on TypeScript performance, type-inference, and better auto-completion and some new features to reduce boilerplate.

Named after YOASOBI's song「祝福」, an opening for Witch from "Mobile Suit Gundam: The Witch from Mercury".

Defers / Lazy Loading Module

With Elysia 0.2 now add support for the lazy loading module and async plugin.

This made it possible to defer plugin registration and incrementally apply after the Elysia server is started to achieve the fastest possible start-up time in Serverless/Edge environments.

To create defers module, simply mark the plugin as async:

const plugin = async (app: Elysia) => {
    const stuff = await doSomeHeavyWork()

    return app.get('/heavy', stuff)
}

app.use(plugin)

Lazy Loading

Some modules might be heavy and importing before starting the server might not be a good idea.

We can tell Elysia to skip the module then register the module later, and register the module when finish loading by using import statement in use:

app.use(import('./some-heavy-module'))

This will register the module after the import is finished making the module lazy-load.

Defers Plugin and lazy loading module will have all type-inference available right out of the box.

Reference Model

Now Elysia can memorize schema and reference the schema directly in Schema fields, without creating an import file via Elysia.setModel

This list of schema available, brings auto-completion, complete type-inference, and validation as you expected from inline schema.

To use a reference model, first, register the model with setModel, then write a model name to reference a model in schema

const app = new Elysia()
    .setModel({
        sign: t.Object({
            username: t.String(),
            password: t.String()
        })
    })
    .post('/sign', ({ body }) => body, {
        schema: {
            body: 'sign',
            response: 'sign'
        }
    })

This will bring auto-completion of known models.
Screenshot 2566-01-23 at 13 24 28

And type reference stopping you from accidentally returning invalid type.
Screenshot 2566-01-23 at 13 26 00

Using @elysiajs/swagger will also create a separate Model section for listing available models.
Screenshot 2566-01-23 at 13 23 41

Reference also handles validation as you expected.

In short, it's as same as inline schema but now you only need to type the name of the schema to handle validation and typing instead of a long list of imports.

OpenAPI Detail field

Introducing new field schema.detail for customizing detail for the route following the standard of OpenAPI Schema V2 with auto-completion.

Screenshot 2566-01-23 at 13 54 11

This allows you to write better documentation and fully editable Swagger as you want:
Screenshot 2566-01-23 at 13 23 41

Union Type

The previous version of Elysia sometime has a problem with distinct Union types, as Elysia tries to catch the response to create a full type reference for Eden.

Results in invalidation of possible types,

Union Response

Made possible by Union Type, now returning multiple response status for schema now available using schema.response[statusCode]

app
    .post(
        '/json/:id',
        ({ body, params: { id } }) => ({
            ...body,
            id
        }),
        {
            schema: {
                body: 'sign',
                response: {
                    200: t.Object({
                        username: t.String(),
                        password: t.String(),
                        id: t.String()
                    }),
                    400: t.Object({
                        error: t.String()
                    })
                }
            }
        }
    )

Elysia will try to validate all schema in response allowing one of the types to be returned.

Return types are also supported report in Swagger's response.

Faster Type Inference

As Elysia 0.1 explore the possibility of using type inference for improving better Developer Experience, we found that sometimes it takes a long time to update type inference because of heavy type inference and in-efficient custom generic.

With Elysia 0.2 now optimized for faster type-inference, preventing duplication of heavy type unwrap, results in better performance for updating type and inference.

Ecosystem

With Elysia 0.2 enabling async plugin and deferred module many new plugins that isn't possible before became reality.

Like:

  • Elysia Static plugin with the non-blocking capability
  • Eden with union-type inference for multiple responses
  • New Elysia Apollo Plugin for Elysia

Notable Improvement:

  • onRequest and onParse now can access PreContext
  • Support application/x-www-form-urlencoded by default
  • body parser now parse content-type with extra attribute eg. application/json;charset=utf-8
  • Decode URI parameter path parameter
  • Eden now reports an error if Elysia is not installed
  • Skip declaration of existing model and decorators

Breaking Changes:

  • onParse now accepts (context: PreContext, contentType: string) instead of (request: Request, contentType: string)
    • To migrate, add .request to context to access Request

Afterward

Thank you for supporting Elysia and being interested in this project.

This release brings better DX and hopefully all you need to write great software with Bun.

Now we have Discord server where you can ask any questions about Elysia or just hang out and chill around is also welcome.

With the wonderful tools, we are happy to see what wonderful software you will build.

Not to be part of those images someone paints
Not advancing in that show chosen by someone else
You and I, alive to write our story
Will never let you be lone and be gone from your side

0.2 - Blessing (RC)

23 Jan 07:20
9605f4e
Compare
Choose a tag to compare
0.2 - Blessing (RC) Pre-release
Pre-release

0.2.0 RC - 23 Jan 2022

Blessing」brings more improvement, mainly on TypeScript performance, type-inference, and better auto-completion and some new features to reduce boilerplate.

Named after YOASOBI's song「祝福」, an opening for Witch from "Mobile Suit Gundam: The Witch from Mercury".

Defers / Lazy Loading Module

With Elysia 0.2 now add support for the lazy loading module and async plugin.

This made it possible to defer plugin registration and incrementally apply after the Elysia server is started to achieve the fastest possible start-up time in Serverless/Edge environments.

To create defers module, simply mark the plugin as async:

const plugin = async (app: Elysia) => {
    const stuff = await doSomeHeavyWork()

    return app.get('/heavy', stuff)
}

app.use(plugin)

Lazy Loading

Some modules might be heavy and importing before starting the server might not be a good idea.

We can tell Elysia to skip the module then register the module later, and register the module when finish loading by using import statement in use:

app.use(import('./some-heavy-module'))

This will register the module after the import is finished making the module lazy-load.

Defers Plugin and lazy loading module will have all type-inference available right out of the box.

Reference Model

Now Elysia can memorize schema and reference the schema directly in Schema fields, without creating an import file via Elysia.setModel

This list of schema available, brings auto-completion, complete type-inference, and validation as you expected from inline schema.

To use a reference model, first, register the model with setModel, then write a model name to reference a model in schema

const app = new Elysia()
    .setModel({
        sign: t.Object({
            username: t.String(),
            password: t.String()
        })
    })
    .post('/sign', ({ body }) => body, {
        schema: {
            body: 'sign',
            response: 'sign'
        }
    })

This will bring auto-completion of known models.
Screenshot 2566-01-23 at 13 24 28

And type reference stopping you from accidentally returning invalid type.
Screenshot 2566-01-23 at 13 26 00

Using @elysiajs/swagger will also create a separate Model section for listing available models.
Screenshot 2566-01-23 at 13 23 41

Reference also handles validation as you expected.

In short, it's as same as inline schema but now you only need to type the name of the schema to handle validation and typing instead of a long list of imports.

OpenAPI Detail field

Introducing new field schema.detail for customizing detail for the route following the standard of OpenAPI Schema V2 with auto-completion.

Screenshot 2566-01-23 at 13 54 11

This allows you to write better documentation and fully editable Swagger as you want:
Screenshot 2566-01-23 at 13 23 41

Union Type

The previous version of Elysia sometime has a problem with distinct Union types, as Elysia tries to catch the response to create a full type reference for Eden.

Results in invalidation of possible types,

Union Response

Made possible by Union Type, now returning multiple response status for schema now available using schema.response[statusCode]

app
    .post(
        '/json/:id',
        ({ body, params: { id } }) => ({
            ...body,
            id
        }),
        {
            schema: {
                body: 'sign',
                response: {
                    200: t.Object({
                        username: t.String(),
                        password: t.String(),
                        id: t.String()
                    }),
                    400: t.Object({
                        error: t.String()
                    })
                }
            }
        }
    )

Elysia will try to validate all schema in response allowing one of the types to be returned.

Return types are also supported report in Swagger's response.

Faster Type Inference

As Elysia 0.1 explore the possibility of using type inference for improving better Developer Experience, we found that sometimes it takes a long time to update type inference because of heavy type inference and in-efficient custom generic.

With Elysia 0.2 now optimized for faster type-inference, preventing duplication of heavy type unwrap, results in better performance for updating type and inference.

Notable Improvement:

  • onRequest and onParse now can access PreContext
  • Support application/x-www-form-urlencoded by default
  • body parser now parse content-type with extra attribute eg. application/json;charset=utf-8
  • Decode URI parameter path parameter

Breaking Changes:

  • onParse now accepts (context: PreContext, contentType: string) instead of (request: Request, contentType: string)
    • To migrate, add .request to context to access Request

Afterward

Thank you for supporting Elysia and being interested in this project.

This release brings better DX and hopefully all you need to write great software with Bun.

Now we have Discord server where you can ask any questions about Elysia or just hang out and chill around is also welcome.

With the wonderful tools, we are happy to see what wonderful software you will build.

Not to be part of those images someone paints
Not advancing in that show chosen by someone else
You and I, alive to write our story
Will never let you be lone and be gone from your side