diff --git a/docs/blog/elysia-10.md b/docs/blog/elysia-10.md index 61fc9e84..805a892a 100644 --- a/docs/blog/elysia-10.md +++ b/docs/blog/elysia-10.md @@ -11,11 +11,11 @@ head: - - meta - name: 'description' - content: Introducing Sucrose, a better static code analysis engine, improved starts up time up to 14x, remove 40 routes/instance limitation, faster type inference up to ~3.8x, Eden Treaty 2, Hook type (breaking change), and inline error for strict type check. + content: Introducing Sucrose, a better static code analysis engine, improved startup time up to 14x, removed ~40 routes-per-instance limitation, faster type inference up to ~3.9x, Eden Treaty 2, hook type (breaking change), and inline errors for strict type checking. - - meta - property: 'og:description' - content: Introducing Sucrose, a better static code analysis engine, improved starts up time up to 14x, remove 40 routes/instance limitation, faster type inference up to ~3.8x, Eden Treaty 2, Hook type (breaking change), and inline error for strict type check. + content: Introducing Sucrose, a better static code analysis engine, improved startup time up to 14x, removed ~40 routes-per-instance limitation, faster type inference up to ~3.9x, Eden Treaty 2, hook type (breaking change), and inline errors for strict type checking. - - meta - property: 'og:image' @@ -38,34 +38,34 @@ head: -Elysia 1.0 is the first stable release after development for 1.8 years. +Elysia 1.0 is the first stable release after 1.8 years of development. -Since we started, we have always been waiting for a framework that focuses on developer experience, velocity, and how to make writing code for humans, not a machine. +Since we started, we have always been looking for a framework that focuses on developer experience, velocity, and making code written for humans, not machines. -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. +We battle-tested Elysia in various situations, simulated medium- and large-scale projects, shipped code to clients, and this is the first version we felt confident enough to ship. -Elysia 1.0 introduces significant improvements and contains 1 necessary breaking change. -- [Sucrose](#sucrose) - Rewritten pattern matching static analysis instead of RegEx +Elysia 1.0 introduces significant improvements and contains one necessary breaking change. +- [Sucrose](#sucrose) - Rewritten static analysis using pattern matching instead of RegEx - [Improved startup time](#improved-startup-time) up to 14x -- [Remove ~40 routes/instance TypeScript limitation](#remove-40-routesinstance-limit) -- [Faster type inference](#type-inference-improvement) up to ~3.8x +- [Removed the ~40 routes/instance TypeScript limitation](#removed-40-routes-per-instance-limit) +- [Faster type inference](#type-inference-improvement) up to ~3.9x - [Treaty 2](#treaty-2) -- [Hook type](#hook-type-breaking-change) (breaking changes) -- [Inline error](#inline-error) for strict error check +- [Hook type](#hook-type-breaking-change) (breaking change) +- [Inline error](#inline-error) for strict error checking --- -It's a tradition that Elysia's release note have a version named after a song or media. +It's a tradition that Elysia's release notes have a version named after a song or media. This important version is named after ["Lament of the Fallen"](https://youtu.be/v1sd5CzR504). -Animated short from **"Honkai Impact 3rd"** from my favorite arc, and my favorite character, **"Raiden Mei"** featuring her theme song, ["Honkai World Diva"](https://youtu.be/s_ZLfaZMpe0). +Animated short from **"Honkai Impact 3rd"** from my favorite arc and my favorite character, **"Raiden Mei"**, featuring her theme song, ["Honkai World Diva"](https://youtu.be/s_ZLfaZMpe0). It's a very good game, and you should check it out. @@ -74,40 +74,45 @@ It's a very good game, and you should check it out. 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? +Remember, ElysiaJS is an open-source library maintained by volunteers, and isn't associated with Mihoyo or Hoyoverse. But we are huge fans of the 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. +Elysia is optimized for excellent performance, as proven in various benchmarks; one of the main factors is thanks to Bun and our custom JIT static code analysis. -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. +If you are not aware, Elysia has an embedded "compiler" that reads your code and produces an optimized way to handle functions. -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"**. +The process is fast and happens on the fly without the need for a build step. -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. +However, it's challenging to maintain because it's written mostly with many complex RegEx patterns, and it can be slow at times when recursion occurs. -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. +That's why we rewrote our static analysis portion to separate the code-injection phase, using a hybrid approach between partial AST-based analysis and pattern matching named **"Sucrose"**. -Sucrose is shipped to replace RegEx-based to partial AST, and pattern matching starting from Elysia 1.0. +Instead of using a full AST-based approach, which is more accurate, we implemented only a subset of rules needed to improve performance, since it needs to be fast at runtime. -## Improved Startup time -Thanks to Sucrose, and separation from the dynamic injection phase, we can defer the analysis time JIT instead of AOT. +Sucrose accurately infers the recursive properties of handler functions with low memory usage, resulting in up to 37% faster inference time and significantly reduced memory usage. + +Sucrose replaces RegEx-based analysis with partial AST and pattern matching starting with Elysia 1.0. + +## Improved startup time + +Thanks to Sucrose and separating the dynamic injection phase, we can defer the analysis time to 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. +We offload the evaluation phase from AOT to JIT when a route is matched for the first time and cache the result to compile on demand instead of compiling 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). +At runtime, 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. +In a medium-sized application and stress test, we measured between ~6.5x and 14x faster startup times. -## Remove ~40 routes/instance limit -Previously you could only stack up to ~40 routes / 1 Elysia instance since Elysia 0.1. +## Removed ~40 routes per instance limit + +Previously, you could only stack up to ~40 routes per Elysia instance since Elysia 0.1. + +This is a limitation of TypeScript: each queue has limited memory, and if exceeded, TypeScript will think that **"Type instantiation is excessively deep and possibly infinite"**. -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"**. ```typescript const main = new Elysia() .get('/1', () => '1') @@ -118,7 +123,8 @@ const main = new Elysia() // 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. +As a workaround, we needed to separate an instance into a controller to overcome the limit and re-merge the type to offload the queue like this. + ```typescript const controller1 = new Elysia() .get('/42', () => '42') @@ -131,11 +137,11 @@ const main = new Elysia() .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. +However, starting with Elysia 1.0, we have overcome the limit after a year of 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. +This means that, theoretically, we can stack an unlimited number 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) +(spoiler: we've done that, and it's around 558 routes/instance before the TypeScript CLI and language server break because of the JavaScript memory limit per stack/queue) ```typescript const main = new Elysia() @@ -146,51 +152,54 @@ const main = new Elysia() .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. +So we increased the limit from ~40 routes to the 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](/blog/elysia-10/558-ts-limit.webp) -The blocker that made us feel like Elysia is not ready for production has been finally resolved. +The blocker that made us feel like Elysia is not ready for production has finally been resolved. -## Type Inference improvement -Thanks to the effort we put into optimization, we measure **up to ~82%** in most Elysia servers. +## Type inference improvement -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. +Thanks to the effort we put into optimization, we measured **up to ~82% improvement** in most Elysia servers. + +Thanks to the removal of the stack limitation and improved type performance, we can expect almost instant type checking and auto-completion even after 500-route stacks. -**Up to 13x faster for Eden Treaty**, type inference performance by precomputing the type instead offload type remap to Eden. +**Up to 13x faster for Eden Treaty**, with type inference performance improved by precomputing the types instead of offloading type remapping to Eden. -Overall, Elysia, and Eden Treaty performing together would be **up to ~3.9x faster**. +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](/blog/elysia-10/ely-comparison.webp) -Stress test with 450 routes for Elysia with Eden Treaty, result as follows: +Stress test with 450 routes for Elysia with Eden Treaty; results 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. +And thanks to the removal of the stack limitation and the 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. + +We asked you for feedback on Eden Treaty—what you like and what could have been improved—and you provided feedback on flaws in the Treaty design and several proposals for 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. +As much as we dislike breaking changes, 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 +- End-to-end type safety for unit tests - Interceptor - No "$" prefix and property -Our favorite one is end-to-end type safety for Unit tests. +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. + ```typescript // test/index.test.ts import { describe, expect, it } from 'bun:test' @@ -213,15 +222,16 @@ 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 choose to continue using Treaty 1 for your current project without updating to Treaty 2, and we will maintain it in 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](/eden/treaty/overview.html), and for Treaty 1 in [Treaty legacy](/eden/treaty/legacy.html) +The documentation for the new Treaty can be found in [Treaty overview](/eden/treaty/overview.html), and for Treaty 1 in [Treaty legacy](/eden/treaty/legacy.html). ## Hook type (breaking change) -We hate breaking changes, and this is the first time we do it in large-scale. + +We hate breaking changes, and this is the first time we've done it on a 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. @@ -243,22 +253,23 @@ const app = new Elysia() .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 most expected hook to be local at first. -- Following the previous point, we found that making hook global by default can easily cause accidental bugs (side-effect) if not reviewed carefully and hard to debug and observe. +However, we found several problems that arise from this behavior: +- We found that many developers have a lot of nested guards even on new instances. 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 unfamiliar with Elysia, and found that most expected hooks to be local at first. +- Following the previous point, we found that making hooks global by default can easily cause accidental bugs (side effects) if not reviewed carefully and are hard to debug and observe. --- To fix this, we introduce a hook type to specify how the hook should be inherited by introducing a **"hook-type"**. Hook types can be classified as follows: -- local (default) - apply to only current instance and descendant only -- scoped - apply to only 1 ascendant, current instance, and descendants -- global (old behavior) - apply to all instances that apply the plugin (all ascendants, current, and descendants) +- local (default) - applies to only the current instance and descendants +- scoped - applies to only one ascendant, the current instance, and descendants +- global (old behavior) - applies to all instances that apply the plugin (all ascendants, the current instance, and descendants) To specify the hook's type, simply add a `{ as: hookType }` to the hook. + ```typescript const plugin = new Elysia() .onBeforeHandle(() => { // [!code --] @@ -286,6 +297,7 @@ const plugin = new Elysia() ``` However, with the introduction of hook type, we can remove the nesting guard boilerplate. + ```typescript const plugin = new Elysia() .guard((app) => // [!code --] @@ -296,6 +308,7 @@ const plugin = new Elysia() ``` Hook type will specify how the hook should be inherited, let's create a plugin to illustrate how hook type works. + ```typescript // ? Value based on table value provided below const type = 'local' @@ -327,7 +340,7 @@ By changing the `type` value, the result should be as follows: | 'scope' | ✅ | ✅ | ✅ | ❌ | | 'global' | ✅ | ✅ | ✅ | ✅ | -Migrating from Elysia 0.8, if you want make a hook global, you have to specify that hook is global. +Migrating from Elysia 0.8, if you want to make a hook global, you have to specify that the hook is global. ```typescript // From Elysia 0.8 @@ -341,25 +354,28 @@ new Elysia() .derive({ as: 'global' }, () => {}) ``` -As much as we hate breaking change and migration, we think this is an important fix that will happen sooner or later to fix problems. +As much as we hate breaking changes and migration, we think this is an important fix that would have happened sooner or later to resolve these problems. -Most of the server might not need to apply migration yourself but **heavily depends on plugin authors**, or should migration required, it usually take no longer than 5-15 minutes. +In most cases, you might not need to apply the migration yourself but **heavily depend on plugin authors**, or should migration be required, it usually takes no longer than 5-15 minutes. For a complete migration note, see [Elysia#513](https://github.com/elysiajs/elysia/issues/513). For the documentation of hook type, see [Lifecycle#hook-type](https://beta.elysiajs.com/essential/scope.html#hook-type) ## Inline error + Starting from Elysia 0.8, we can use the `error` function to return a response with a status code for Eden inference. However, this has some flaws. If you specify a response schema for a route, Elysia will be unable to provide an accurate auto-completion for the status code. -For example, narrowing down an available status code. +For example, narrowing down available status codes. + ![Using import error in Elysia](/blog/elysia-10/error-fn.webp) Inline error can be destructured from handler as follows: + ```typescript import { Elysia } from 'elysia' @@ -376,26 +392,26 @@ new Elysia() }) ``` -Inline error can produce a fine-grained type from a schema, providing type narrowing, auto-completion, and type checking to the accuracy of value, underlining red squiggly at a value instead of an entire function. +Inline error can produce a fine-grained type from a schema, providing type narrowing, auto-completion, and type checking to the accuracy of the value, underlining red squiggles at a value instead of an entire function. ![Using inline error function from Elysia with an auto-completion that shows narrowed down status code](/blog/elysia-10/inline-error-fn.webp) - -We recommended using inline error instead of import error for more accurate type safety. +We recommend using inline error instead of import error for more accurate type safety. ## What does it mean for v1, and what's next + Reaching stable release means we believe that Elysia is stable enough and ready to be used in production. -Maintaining backward compatibility is now one of our goals, putting effort into not introducing breaking changes to Elysia except for security. +Maintaining backward compatibility is now one of our goals, putting effort into not introducing breaking changes to Elysia unless required for security. Our goal is to make backend development feel easy, fun, and intuitive while making sure that the product built with Elysia will have a solid foundation. After this, we will be focusing on refining our ecosystem and plugins. -Introducing an ergonomic way to handle redundant and mundane tasks, starting some internal plugin rewrite, authentication, synchronize behavior between JIT and non-JIT mode, and **universal runtime support.** +Introducing an ergonomic way to handle redundant and mundane tasks, starting some internal plugin rewrites, authentication, synchronizing behavior between JIT and non-JIT mode, and **universal runtime support**. -Bun works excellently in both runtime, package manager and all the toolings they offers, and we believe that Bun is going to be a future of JavaScript. +Bun works excellently as a runtime, package manager, and with all the tooling it offers, and we believe that Bun is going to be the future of JavaScript. -We believe that by opening Elysia to more runtime and offers interesting Bun specific feature (or at-least easy to config, eg. [Bun Loaders API](https://bun.sh/docs/bundler/loaders)) will eventually gets people to try Bun more than Elysia choosing to support only Bun. +We believe that by opening Elysia to more runtimes and offering interesting Bun-specific features (or at least making them easy to configure, e.g., [Bun Loaders API](https://bun.sh/docs/bundler/loaders)), people will eventually try Bun more than if Elysia chose to support only Bun.

Bun was right, the best way to migrate people from Node is to have compatibility layer and offers better DX, and performance on Bun

@@ -403,116 +419,122 @@ We believe that by opening Elysia to more runtime and offers interesting Bun spe March 14, 2024
-Elysia core itself partially WinterCG compatible, but not all the official plugin works with WinterCG, there are some with Bun specific features, and we want to fix that. +Elysia core itself is partially WinterCG compatible, but not all official plugins work with WinterCG—some have Bun-specific features—and we want to fix that. -We don't have a specific date or version for universal runtime supports yet as we will gradually adopting and test until we make sure that it would works without unexpected behavior. +We don't have a specific date or version for universal runtime support yet, as we will gradually adopt and test until we are sure that it works without unexpected behavior. -You can looks forward for the following runtime to support: +You can look forward to support for the following runtimes: - Node - Deno - Cloudflare Worker We also want to support the following: -- Vercel Edge Function -- Netlify Function +- Vercel Edge Functions +- Netlify Functions - AWS Lambda / LLRT -More over, we also support, and test Elysia on the following frameworks that support Server Side Rendering or Edge Function: -- Nextjs +Moreover, we also support and test Elysia on the following frameworks that support Server-Side Rendering or Edge Functions: +- Next.js - Expo - Astro - SvelteKit -In the meantime, there's an [Elysia Polyfills](https://github.com/bogeychan/elysia-polyfills) maintained by Bogeychan, one of an active contributor to Elysia. +In the meantime, there's an [Elysia Polyfills](https://github.com/bogeychan/elysia-polyfills) maintained by Bogeychan, one of the active contributors to Elysia. -Additionally, we have rewrote [Eden documentation](/eden/overview) to explain more in depth details about Eden and we think you should check it out. +Additionally, we have rewritten the [Eden documentation](/eden/overview) to explain more in-depth details about Eden, and we think you should check it out. -We also improve several pages, and remove redundant part of the documentation, You can check the affected pages on [Elysia 1.0 documentation PR](https://github.com/elysiajs/documentation/pull/282/files). +We also improved several pages and removed redundant parts of the documentation. You can check the affected pages on [Elysia 1.0 documentation PR](https://github.com/elysiajs/documentation/pull/282/files). + +And finally, if you have problems with migration and additional questions related to Elysia, feel free to ask one in Elysia's Discord server. -And finally, if you have problems with migration and additional questions related to Elysia, feels free to ask one in Elysia's Discord server.