Browse Source

[TypeScript] Add ElysiaJS (#8480)

* [TypeScript] Add ElysiaJS

* [Elysia] Fix benchmark_config.json
Imam Fahrur Rofi 1 year ago
parent
commit
13a877f342

+ 61 - 0
frameworks/TypeScript/elysia/README.md

@@ -0,0 +1,61 @@
+# [ElysiaJS](https://elysiajs.com/) - Fast, and friendly Bun web framework
+
+<img src="https://elysiajs.com/assets/feature-sheet.webp" alt="Elysia Feature Sheet" style="max-width: 100%;margin: 0 auto;">
+
+## Introduction
+ElysiaJS is a fast, and friendly [Bun](https://bun.sh) web framework.
+
+> <small>Pronounce as "eh-LIHZ-iy-ah"・ エリシア ・ เอลิเซีย</small>
+
+Building on top of 3 philosophies:
+- Performance
+    - You shall not worry about the underlying performance
+- Simplicity
+    - Simple building blocks to create an abstraction, not repeating yourself
+- Flexibility
+    - You shall be able to customize most of the library to fit your need
+
+Designed with TypeScript in mind, you don't need to understand TypeScript to take advantage of Elysia. The library understands what you want and automatically infers the type from your code.
+
+> [__Elysia Docs__](https://elysiajs.com/introduction.html)
+
+## Important Libraries
+
+The tests were run with:
+
+- [elysia](https://github.com/elysiajs/elysia)
+- [postgres](https://github.com/porsager/postgres)
+
+## Database
+There are **no database endpoints** or drivers attached by default.
+
+To initialize the application with one of these, run any _one_ of the following commands:
+
+```sh
+$ DATABASE=postgres bun start
+```
+
+## Test URLs
+### JSON
+
+http://localhost:8080/json
+
+### PLAINTEXT
+
+http://localhost:8080/plaintext
+
+### DB
+
+http://localhost:8080/db
+
+### QUERY
+
+http://localhost:8080/queries?queries=10
+
+### UPDATE
+
+http://localhost:8080/updates?queries=10
+
+### Fortune
+
+http://localhost:8080/fortunes

+ 47 - 0
frameworks/TypeScript/elysia/benchmark_config.json

@@ -0,0 +1,47 @@
+{
+  "framework": "elysia",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "None",
+        "framework": "elysia",
+        "language": "TypeScript",
+        "flavor": "None",
+        "orm": "None",
+        "platform": "bun",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Elysia",
+        "notes": "",
+        "versus": "nodejs"
+      },
+      "postgres": {
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "fortune_url": "/fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "elysia",
+        "language": "TypeScript",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "bun",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Elysia [PostgreSQL]",
+        "notes": "",
+        "versus": "nodejs"
+      }
+    }
+  ]
+}

BIN
frameworks/TypeScript/elysia/bun.lockb


+ 15 - 0
frameworks/TypeScript/elysia/elysia-postgres.dockerfile

@@ -0,0 +1,15 @@
+FROM oven/bun:1.0
+
+EXPOSE 8080
+
+COPY . .
+
+ENV NODE_ENV production
+
+RUN bun install --production
+
+RUN bun run build
+
+ENV DATABASE postgres
+
+CMD ["bun", "spawn.ts"]

+ 13 - 0
frameworks/TypeScript/elysia/elysia.dockerfile

@@ -0,0 +1,13 @@
+FROM oven/bun:1.0
+
+EXPOSE 8080
+
+COPY . .
+
+ENV NODE_ENV production
+
+RUN bun install --production
+
+RUN bun run build
+
+CMD ["bun", "spawn.ts"]

+ 17 - 0
frameworks/TypeScript/elysia/package.json

@@ -0,0 +1,17 @@
+{
+  "name": "elysia",
+  "version": "0.0.1",
+  "module": "src/index.js",
+  "devDependencies": {
+    "bun-types": "latest"
+  },
+  "scripts": {
+    "dev": "bun run --watch src/index.ts",
+    "start": "bun run src/index.ts",
+    "build": "bun build --target=bun --minify src/index.ts --outdir=build"
+  },
+  "dependencies": {
+    "elysia": "^0.7.15",
+    "postgres": "^3.3.5"
+  }
+}

+ 10 - 0
frameworks/TypeScript/elysia/spawn.ts

@@ -0,0 +1,10 @@
+import os from 'node:os';
+
+// @ts-ignore
+const numCPUs = os.availableParallelism();
+for (let i = 0; i < numCPUs; i++) {
+  Bun.spawn(['bun', 'build/index.js'], {
+    stdio: ['inherit', 'inherit', 'inherit'],
+    env: { ...process.env },
+  });
+}

+ 84 - 0
frameworks/TypeScript/elysia/src/db-handlers.ts

@@ -0,0 +1,84 @@
+import Elysia from 'elysia';
+import * as postgres from './postgres';
+import { Fortune, World } from './types';
+
+const deps = new Elysia({
+  name: 'deps',
+})
+  .decorate('db', postgres)
+  .decorate('generateRandomNumber', () => Math.floor(Math.random() * 10000) + 1)
+  .decorate('html', (fortunes: Fortune[]) => {
+    const n = fortunes.length;
+
+    let html = '';
+    for (let i = 0; i < n; i++) {
+      html += `<tr><td>${fortunes[i].id}</td><td>${Bun.escapeHTML(
+        fortunes[i].message
+      )}</td></tr>`;
+    }
+
+    return `<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>${html}</table></body></html>`;
+  })
+  .derive(({ query }) => ({
+    numberOfObjects: Math.min(parseInt(query.queries ?? '1') || 1, 500),
+  }));
+
+const dbHandlers = new Elysia({
+  name: 'db-handlers',
+})
+  .use(deps)
+  .get(
+    '/db',
+    async ({ db, generateRandomNumber }) =>
+      await db.find(generateRandomNumber())
+  )
+  .get('/queries', async ({ db, generateRandomNumber, numberOfObjects }) => {
+    const worldPromises = new Array<Promise<World>>(numberOfObjects);
+
+    for (let i = 0; i < numberOfObjects; i++) {
+      worldPromises[i] = db.find(generateRandomNumber());
+    }
+
+    const worlds = await Promise.all(worldPromises);
+
+    return worlds;
+  })
+  .get('/updates', async ({ db, generateRandomNumber, numberOfObjects }) => {
+    const worldPromises = new Array<Promise<World>>(numberOfObjects);
+
+    for (let i = 0; i < numberOfObjects; i++) {
+      worldPromises[i] = db.find(generateRandomNumber());
+    }
+
+    const worlds = await Promise.all(worldPromises);
+
+    for (let i = 0; i < numberOfObjects; i++) {
+      worlds[i].randomNumber = generateRandomNumber();
+    }
+
+    await db.bulkUpdate(worlds);
+
+    return worlds;
+  })
+  .get(
+    '/fortunes',
+    async ({ db, html }) => {
+      const fortunes = await db.fortunes();
+
+      fortunes.push({
+        id: 0,
+        message: 'Additional fortune added at request time.',
+      });
+
+      fortunes.sort((a, b) => (a.message < b.message ? -1 : 1));
+
+      return html(fortunes);
+    },
+    {
+      afterHandle({ set }) {
+        set.headers['content-type'] = 'text/html; charset=utf-8';
+      },
+    }
+  );
+
+export default dbHandlers;

+ 25 - 0
frameworks/TypeScript/elysia/src/index.ts

@@ -0,0 +1,25 @@
+import { Elysia } from 'elysia';
+import dbHandlers from './db-handlers';
+
+const app = new Elysia({
+  serve: {
+    // @ts-ignore
+    reusePort: true,
+  },
+})
+  .onAfterHandle(({ set }) => {
+    set.headers['server'] = 'Elysia';
+  })
+  .decorate('HELLO_WORLD_STR', 'Hello, World!')
+  .get('/plaintext', ({ HELLO_WORLD_STR }) => HELLO_WORLD_STR)
+  .get('/json', ({ HELLO_WORLD_STR }) => ({ message: HELLO_WORLD_STR }));
+
+if (process.env.DATABASE) {
+  app.use(dbHandlers);
+}
+
+app.listen(8080);
+
+console.info(
+  `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
+);

+ 27 - 0
frameworks/TypeScript/elysia/src/postgres.ts

@@ -0,0 +1,27 @@
+import postgres from 'postgres';
+import { Fortune, World } from './types';
+
+const sql = postgres({
+  host: 'tfb-database',
+  user: 'benchmarkdbuser',
+  password: 'benchmarkdbpass',
+  database: 'hello_world',
+  max: 1,
+});
+
+export const fortunes = async () =>
+  await sql<Fortune[]>`SELECT id, message FROM fortune`;
+
+export const find = async (id: number) =>
+  await sql<World[]>`SELECT id, randomNumber FROM world WHERE id = ${id}`.then(
+    (arr) => arr[0]
+  );
+
+export const bulkUpdate = async (worlds: World[]) =>
+  await sql`UPDATE world SET randomNumber = (update_data.randomNumber)::int
+  FROM (VALUES ${sql(
+    worlds
+      .map((world) => [world.id, world.randomNumber])
+      .sort((a, b) => (a[0] < b[0] ? -1 : 1))
+  )}) AS update_data (id, randomNumber)
+  WHERE world.id = (update_data.id)::int`;

+ 9 - 0
frameworks/TypeScript/elysia/src/types.ts

@@ -0,0 +1,9 @@
+export type Fortune = {
+  id: number;
+  message: string;
+};
+
+export type World = {
+  id: number;
+  randomNumber: number;
+};

+ 12 - 0
frameworks/TypeScript/elysia/tsconfig.json

@@ -0,0 +1,12 @@
+{
+  "compilerOptions": {
+    "target": "ES2021",
+    "module": "ES2022",
+    "moduleResolution": "node",
+    "types": ["bun-types"],
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "skipLibCheck": true
+  }
+}