Parcourir la source

[TypeScript/Ditsmod]: added tests implementation for Ditsmod. (#8422)

* [TypeScript/Ditsmod]: added tests implementation for Ditsmod.

* refactor(ditsmod): removed knex.
Костя Третяк il y a 1 an
Parent
commit
d15cc6f333
26 fichiers modifiés avec 648 ajouts et 0 suppressions
  1. 4 0
      frameworks/TypeScript/ditsmod/.dockerignore
  2. 26 0
      frameworks/TypeScript/ditsmod/.eslintrc
  3. 7 0
      frameworks/TypeScript/ditsmod/.gitignore
  4. 8 0
      frameworks/TypeScript/ditsmod/.prettierrc
  5. 40 0
      frameworks/TypeScript/ditsmod/README.md
  6. 70 0
      frameworks/TypeScript/ditsmod/benchmark_config.json
  7. 16 0
      frameworks/TypeScript/ditsmod/ditsmod-mysql.dockerfile
  8. 16 0
      frameworks/TypeScript/ditsmod/ditsmod-postgres.dockerfile
  9. 11 0
      frameworks/TypeScript/ditsmod/ditsmod.dockerfile
  10. 38 0
      frameworks/TypeScript/ditsmod/package.json
  11. 8 0
      frameworks/TypeScript/ditsmod/src/app/app.module.ts
  12. 39 0
      frameworks/TypeScript/ditsmod/src/app/modules/routed/simple/db.controller.ts
  13. 41 0
      frameworks/TypeScript/ditsmod/src/app/modules/routed/simple/fortune.controller.ts
  14. 13 0
      frameworks/TypeScript/ditsmod/src/app/modules/routed/simple/simple.module.ts
  15. 18 0
      frameworks/TypeScript/ditsmod/src/app/modules/routed/simple/without-db.controller.ts
  16. 12 0
      frameworks/TypeScript/ditsmod/src/app/modules/service/db/db.module.ts
  17. 71 0
      frameworks/TypeScript/ditsmod/src/app/modules/service/db/db.service.ts
  18. 41 0
      frameworks/TypeScript/ditsmod/src/app/modules/service/db/init.extension.ts
  19. 32 0
      frameworks/TypeScript/ditsmod/src/app/modules/service/db/mysql.service.ts
  20. 33 0
      frameworks/TypeScript/ditsmod/src/app/modules/service/db/postgres.service.ts
  21. 6 0
      frameworks/TypeScript/ditsmod/src/app/modules/service/db/tokens.ts
  22. 19 0
      frameworks/TypeScript/ditsmod/src/app/modules/service/db/types.ts
  23. 23 0
      frameworks/TypeScript/ditsmod/src/app/utils/helper.ts
  24. 18 0
      frameworks/TypeScript/ditsmod/src/main.ts
  25. 9 0
      frameworks/TypeScript/ditsmod/tsconfig.build.json
  26. 29 0
      frameworks/TypeScript/ditsmod/tsconfig.json

+ 4 - 0
frameworks/TypeScript/ditsmod/.dockerignore

@@ -0,0 +1,4 @@
+node_modules/
+dist/
+package-lock.json
+nodemon.json

+ 26 - 0
frameworks/TypeScript/ditsmod/.eslintrc

@@ -0,0 +1,26 @@
+{
+  "parser": "@typescript-eslint/parser",
+  "plugins": ["@typescript-eslint"],
+  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
+  "parserOptions": {
+    "ecmaVersion": 2020,
+    "sourceType": "module"
+  },
+  "rules": {
+    "semi": ["error", "always"],
+    "quotes": ["error", "single", { "avoidEscape": true }],
+    "@typescript-eslint/no-non-null-assertion": 0,
+    "@typescript-eslint/no-empty-function": 0,
+    "@typescript-eslint/no-empty-interface": 0,
+    "@typescript-eslint/explicit-function-return-type": 0,
+    "@typescript-eslint/explicit-module-boundary-types": 0,
+    "@typescript-eslint/no-explicit-any": 0,
+    "@typescript-eslint/no-inferrable-types": 0,
+    "@typescript-eslint/no-non-null-asserted-optional-chain": 0,
+    "@typescript-eslint/no-unused-vars": 0,
+    "@typescript-eslint/triple-slash-reference": 0,
+    "@typescript-eslint/ban-types": 0,
+    "no-async-promise-executor": 0,
+    "no-prototype-builtins": 0
+  }
+}

+ 7 - 0
frameworks/TypeScript/ditsmod/.gitignore

@@ -0,0 +1,7 @@
+/node_modules
+dist*
+*.log
+.env
+.pnp*
+nodemon.json
+package-lock.json

+ 8 - 0
frameworks/TypeScript/ditsmod/.prettierrc

@@ -0,0 +1,8 @@
+{
+  "printWidth": 120,
+  "tabWidth": 2,
+  "useTabs": false,
+  "semi": true,
+  "singleQuote": true,
+  "bracketSpacing": true
+}

+ 40 - 0
frameworks/TypeScript/ditsmod/README.md

@@ -0,0 +1,40 @@
+# Ditsmod Benchmarking Test
+
+This is the Ditsmod portion of a benchmarking test suite comparing a variety of web development platforms.
+
+Information about Ditsmod can be found at https://github.com/ditsmod/ditsmod or https://ditsmod.github.io/en/
+
+### Test Type Implementation Source Code
+
+* JSON, PLAINTEXT in [this controller](src/app/modules/routed/simple/without-db.controller.ts)
+* DB, QUERY, UPDATE and CACHED QUERY in [this controller](src/app/modules/routed/simple/db.controller.ts)
+* FORTUNES in [this controller](src/app/modules/routed/simple/fortune.controller.ts)
+
+## Test URLs
+### JSON
+
+http://tfb-server:8080/json
+
+### PLAINTEXT
+
+http://tfb-server:8080/plaintext
+
+### DB
+
+http://tfb-server:8080/db
+
+### QUERY
+
+http://tfb-server:8080/queries?queries=10
+
+### UPDATE
+
+http://tfb-server:8080/updates?queries=10
+
+### CACHED QUERY
+
+http://tfb-server:8080/cached-queries?count=10
+
+### Fortune
+
+http://tfb-server:8080/fortunes

+ 70 - 0
frameworks/TypeScript/ditsmod/benchmark_config.json

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

+ 16 - 0
frameworks/TypeScript/ditsmod/ditsmod-mysql.dockerfile

@@ -0,0 +1,16 @@
+FROM node:18.17.1-slim
+
+COPY ./ ./
+
+RUN npm install
+RUN npm run build
+
+ENV NODE_ENV production
+ENV DATABASE mysql
+ENV MYSQL_HOST tfb-database
+ENV MYSQL_USER benchmarkdbuser
+ENV MYSQL_PSWD benchmarkdbpass
+ENV MYSQL_DBNAME hello_world
+
+EXPOSE 8080
+CMD node dist/main.js

+ 16 - 0
frameworks/TypeScript/ditsmod/ditsmod-postgres.dockerfile

@@ -0,0 +1,16 @@
+FROM node:18.17.1-slim
+
+COPY ./ ./
+
+RUN npm install
+RUN npm run build
+
+ENV NODE_ENV production
+ENV DATABASE postgres
+ENV PG_HOST tfb-database
+ENV PG_USER benchmarkdbuser
+ENV PG_PSWD benchmarkdbpass
+ENV PG_DBNAME hello_world
+
+EXPOSE 8080
+CMD node dist/main.js

+ 11 - 0
frameworks/TypeScript/ditsmod/ditsmod.dockerfile

@@ -0,0 +1,11 @@
+FROM node:18.17.1-slim
+
+COPY ./ ./
+
+RUN npm install
+RUN npm run build
+
+ENV NODE_ENV production
+
+EXPOSE 8080
+CMD node dist/main.js

+ 38 - 0
frameworks/TypeScript/ditsmod/package.json

@@ -0,0 +1,38 @@
+{
+  "name": "ditsmod-seed",
+  "type": "module",
+  "version": "1.0.0",
+  "description": "",
+  "scripts": {
+    "start": "npm run build && node dist/main.js",
+    "start-watch": "DATABASE=mysql MYSQL_HOST=172.18.0.2 MYSQL_USER=benchmarkdbuser MYSQL_PSWD=benchmarkdbpass MYSQL_DBNAME=hello_world nodemon dist/main.js",
+    "start-prod": "node dist/main.js",
+    "build": "tsc -b tsconfig.build.json",
+    "clean": "rm -rf dist*"
+  },
+  "imports": {
+    "#routed/*": "./dist/app/modules/routed/*",
+    "#service/*": "./dist/app/modules/service/*",
+    "#utils/*": "./dist/app/utils/*"
+  },
+  "keywords": [],
+  "author": "Костя Третяк",
+  "license": "MIT",
+  "dependencies": {
+    "@ditsmod/core": "~2.49.1",
+    "@ditsmod/router": "~2.7.1",
+    "handlebars": "^4.7.8",
+    "lru-cache": "^10.0.1",
+    "mariadb": "^3.2.1",
+    "postgres": "^3.3.5"
+  },
+  "devDependencies": {
+    "@types/eslint": "^8.44.2",
+    "@types/node": "^20.5.7",
+    "@typescript-eslint/eslint-plugin": "^6.5.0",
+    "@typescript-eslint/parser": "^6.5.0",
+    "eslint": "^8.48.0",
+    "prettier": "^3.0.2",
+    "typescript": "^5.2.2"
+  }
+}

+ 8 - 0
frameworks/TypeScript/ditsmod/src/app/app.module.ts

@@ -0,0 +1,8 @@
+import { Providers, rootModule } from '@ditsmod/core';
+import { SimpleModule } from '#routed/simple/simple.module.js';
+
+@rootModule({
+  appends: [SimpleModule],
+  providersPerApp: [...new Providers().useLogConfig({ level: 'off' })],
+})
+export class AppModule {}

+ 39 - 0
frameworks/TypeScript/ditsmod/src/app/modules/routed/simple/db.controller.ts

@@ -0,0 +1,39 @@
+import { AnyObj, controller, inject, QUERY_PARAMS, Res, route } from '@ditsmod/core';
+
+import { DbService } from '#service/db/db.service.js';
+import { getRandomNumber } from '#utils/helper.js';
+
+@controller()
+export class DbController {
+  constructor(
+    private res: Res,
+    private dbService: DbService,
+  ) {
+    res.nodeRes.setHeader('Server', 'Ditsmod');
+  }
+
+  @route('GET', 'db')
+  async getSingleQuery() {
+    const id = getRandomNumber();
+    const result = await this.dbService.findOneWorld(id);
+    this.res.sendJson(result);
+  }
+
+  @route('GET', 'queries')
+  async getMultiQueries(@inject(QUERY_PARAMS) queryParams: AnyObj) {
+    const result = await this.dbService.getMultiQueries(queryParams.queries);
+    this.res.sendJson(result);
+  }
+
+  @route('GET', 'cached-queries')
+  async getCachedWorlds(@inject(QUERY_PARAMS) queryParams: AnyObj) {
+    const result = await this.dbService.getMultiQueries(queryParams.count, false);
+    this.res.sendJson(result);
+  }
+
+  @route('GET', 'updates')
+  async getUpdates(@inject(QUERY_PARAMS) queryParams: AnyObj) {
+    const worlds = await this.dbService.saveWorlds(queryParams.queries);
+    this.res.sendJson(worlds);
+  }
+}

+ 41 - 0
frameworks/TypeScript/ditsmod/src/app/modules/routed/simple/fortune.controller.ts

@@ -0,0 +1,41 @@
+import Handlebars from 'handlebars';
+import { NODE_RES, NodeResponse, controller, inject, route } from '@ditsmod/core';
+
+import { additionalFortune, compare } from '#utils/helper.js';
+import { DbService } from '#service/db/db.service.js';
+
+const tmpl = Handlebars.compile(
+  [
+    '<!DOCTYPE html>',
+    '<html>',
+    '<head><title>Fortunes</title></head>',
+    '<body>',
+    '<table>',
+    '<tr>',
+    '<th>id</th>',
+    '<th>message</th>',
+    '</tr>',
+    '{{#fortunes}}',
+    '<tr>',
+    '<td>{{id}}</td>',
+    '<td>{{message}}</td>',
+    '</tr>',
+    '{{/fortunes}}',
+    '</table>',
+    '</body>',
+    '</html>',
+  ].join(''),
+);
+
+@controller()
+export class FortuneController {
+  @route('GET', 'fortunes')
+  async fortunes(@inject(NODE_RES) nodeRes: NodeResponse, dbService: DbService) {
+    const fortunes = await dbService.findAllFortunes();
+    fortunes.push(additionalFortune);
+    fortunes.sort(compare);
+    nodeRes.setHeader('Server', 'Ditsmod');
+    nodeRes.setHeader('Content-Type', 'text/html; charset=utf-8');
+    nodeRes.end(tmpl({ fortunes }));
+  }
+}

+ 13 - 0
frameworks/TypeScript/ditsmod/src/app/modules/routed/simple/simple.module.ts

@@ -0,0 +1,13 @@
+import { featureModule } from '@ditsmod/core';
+import { RouterModule } from '@ditsmod/router';
+
+import { DbModule } from '#service/db/db.module.js';
+import { WithoutDbController } from './without-db.controller.js';
+import { DbController } from './db.controller.js';
+import { FortuneController } from './fortune.controller.js';
+
+@featureModule({
+  imports: [RouterModule, DbModule],
+  controllers: [WithoutDbController, DbController, FortuneController]
+})
+export class SimpleModule {}

+ 18 - 0
frameworks/TypeScript/ditsmod/src/app/modules/routed/simple/without-db.controller.ts

@@ -0,0 +1,18 @@
+import { controller, Res, route } from '@ditsmod/core';
+
+@controller()
+export class WithoutDbController {
+  constructor(private res: Res) {
+    res.nodeRes.setHeader('Server', 'Ditsmod');
+  }
+
+  @route('GET', 'plaintext')
+  getHello() {
+    this.res.send('Hello, World!');
+  }
+
+  @route('GET', 'json')
+  getJson() {
+    this.res.sendJson({ message: 'Hello, World!' });
+  }
+}

+ 12 - 0
frameworks/TypeScript/ditsmod/src/app/modules/service/db/db.module.ts

@@ -0,0 +1,12 @@
+import { PRE_ROUTER_EXTENSIONS, featureModule } from '@ditsmod/core';
+
+import { DbService } from './db.service.js';
+import { InitExtension } from './init.extension.js';
+import { DB_INIT_EXTENSIONS } from './tokens.js';
+import { ModelService } from './types.js';
+
+@featureModule({
+  providersPerApp: [DbService, ModelService],
+  extensions: [{ extension: InitExtension, groupToken: DB_INIT_EXTENSIONS, nextToken: PRE_ROUTER_EXTENSIONS }],
+})
+export class DbModule {}

+ 71 - 0
frameworks/TypeScript/ditsmod/src/app/modules/service/db/db.service.ts

@@ -0,0 +1,71 @@
+import { injectable } from '@ditsmod/core';
+import { LRUCache } from 'lru-cache';
+
+import { getNumberOfObjects, getRandomNumber } from '#utils/helper.js';
+import { ModelService, World } from './types.js';
+
+@injectable()
+export class DbService {
+  #cache = new LRUCache<number, World>({ max: 10000 });
+
+  constructor(private modelService: ModelService) {}
+
+  findAllFortunes() {
+    return this.modelService.fortunes();
+  }
+
+  /**
+   * This method is called via `InitExtension` before the route handlers are created.
+   */
+  async setWorldsToCache(): Promise<void> {
+    const result = await this.modelService.getAllWorlds();
+    result.forEach((obj) => this.#cache.set(obj.id, obj));
+  }
+
+  async findOneWorld(id: number, noCache = true): Promise<World> {
+    if (noCache) {
+      return this.modelService.find(`${id}`);
+    } else {
+      let obj = this.#cache.get(id);
+      if (obj) {
+        return obj;
+      }
+      obj = await this.modelService.find(`${id}`);
+      this.#cache.set(id, obj);
+      return obj!;
+    }
+  }
+
+  getMultiQueries(queries: string, noCache = true) {
+    const num = getNumberOfObjects(queries);
+    const promisesArray = [];
+
+    for (let i = 0; i < num; i++) {
+      const id = getRandomNumber();
+      promisesArray.push(this.findOneWorld(id, noCache));
+    }
+
+    return Promise.all(promisesArray);
+  }
+
+  async saveWorlds(queries: string) {
+    const num = getNumberOfObjects(queries);
+    const worldPromises = [];
+
+    for (let i = 0; i < num; i++) {
+      const id = getRandomNumber();
+      worldPromises.push(this.findOneWorld(id));
+    }
+
+    const worlds = await Promise.all(worldPromises);
+
+    const worldsToUpdate = worlds.map((world) => {
+      world.randomnumber = getRandomNumber();
+      return world;
+    });
+
+    await this.modelService.bulkUpdate(worldsToUpdate);
+
+    return worldsToUpdate;
+  }
+}

+ 41 - 0
frameworks/TypeScript/ditsmod/src/app/modules/service/db/init.extension.ts

@@ -0,0 +1,41 @@
+import { Class, Extension, Logger, PerAppService, injectable } from '@ditsmod/core';
+
+import { DbService } from './db.service.js';
+import { ModelService } from './types.js';
+
+@injectable()
+export class InitExtension implements Extension<void> {
+  #inited: boolean;
+
+  constructor(
+    private perAppService: PerAppService,
+    private logger: Logger,
+  ) {}
+
+  async init(): Promise<void> {
+    if (this.#inited) {
+      return;
+    }
+
+    const dbType = process.env.DATABASE as 'mysql' | 'postgres';
+
+    if (dbType == 'mysql') {
+      const { MysqlService } = await import('./mysql.service.js');
+      await this.setDbService(MysqlService);
+    } else if (dbType == 'postgres') {
+      const { PostgresService } = await import('./postgres.service.js');
+      await this.setDbService(PostgresService);
+    } else {
+      this.logger.log('warn', `Unknown database "${dbType}"`);
+    }
+
+    this.#inited = true;
+  }
+
+  protected async setDbService(useClass: Class) {
+    const injector = this.perAppService.injector.resolveAndCreateChild([{ token: ModelService, useClass }]);
+    const dbService = injector.pull(DbService) as DbService;
+    await dbService.setWorldsToCache();
+    this.perAppService.providers.push({ token: DbService, useValue: dbService });
+  }
+}

+ 32 - 0
frameworks/TypeScript/ditsmod/src/app/modules/service/db/mysql.service.ts

@@ -0,0 +1,32 @@
+import { PoolConfig, createPool } from 'mariadb';
+import { Fortune, ModelService, World } from './types.js';
+
+const clientOpts: PoolConfig = {
+  host: process.env.MYSQL_HOST,
+  user: process.env.MYSQL_USER,
+  password: process.env.MYSQL_PSWD,
+  database: process.env.MYSQL_DBNAME,
+};
+
+const pool = createPool({ ...clientOpts, connectionLimit: 1 });
+const execute = (text: string, values: string[]) => pool.execute(text, values || undefined);
+
+export class MysqlService implements ModelService {
+  fortunes() {
+    return execute('select id, message from fortune', []) as Promise<Fortune[]>;
+  }
+
+  find(id: string) {
+    return execute('select id, randomNumber from world where id = ?', [id]).then((arr) => arr[0]) as Promise<World>;
+  }
+
+  getAllWorlds() {
+    return execute('select id, randomNumber from world', []) as Promise<World[]>;
+  }
+
+  bulkUpdate(worlds: World[]) {
+    const sql = 'update world set randomNumber = ? where id = ?';
+    const values = worlds.map((world) => [world.randomnumber, world.id]);
+    return pool.batch(sql, values);
+  }
+}

+ 33 - 0
frameworks/TypeScript/ditsmod/src/app/modules/service/db/postgres.service.ts

@@ -0,0 +1,33 @@
+import postgres from 'postgres';
+import { Fortune, ModelService, World } from './types.js';
+
+const clientOpts: postgres.Options<any> = {
+  host: process.env.PG_HOST,
+  user: process.env.PG_USER,
+  password: process.env.PG_PSWD,
+  database: process.env.PG_DBNAME,
+};
+
+const sql = postgres({ ...clientOpts, max: 1 });
+
+export class PostgresService implements ModelService {
+  fortunes() {
+    return sql`select id, message from fortune` as Promise<Fortune[]>;
+  }
+
+  find(id: string) {
+    return sql`select id, randomNumber from world where id = ${id}`.then((arr) => arr[0]) as Promise<World>;
+  }
+
+  getAllWorlds() {
+    return sql`select id, randomNumber from world` as Promise<World[]>;
+  }
+
+  bulkUpdate(worlds: World[]) {
+    const values = sql(worlds.map((world) => [world.id, world.randomnumber]));
+
+    return sql`update world set randomNumber = (update_data.randomNumber)::int
+      from (values ${values}) as update_data (id, randomNumber)
+      where world.id = (update_data.id)::int`;
+  }
+}

+ 6 - 0
frameworks/TypeScript/ditsmod/src/app/modules/service/db/tokens.ts

@@ -0,0 +1,6 @@
+import { Extension, InjectionToken } from '@ditsmod/core';
+
+/**
+ * A group of extensions intended for preparatory work for the database module.
+ */
+export const DB_INIT_EXTENSIONS = new InjectionToken<Extension<void>[]>('DB_INIT_EXTENSIONS');

+ 19 - 0
frameworks/TypeScript/ditsmod/src/app/modules/service/db/types.ts

@@ -0,0 +1,19 @@
+import type { UpsertResult } from 'mariadb';
+import type postgres from 'postgres';
+
+export interface World {
+  id: number;
+  randomnumber: number;
+}
+
+export interface Fortune {
+  id: number;
+  message: string;
+}
+
+export class ModelService {
+  fortunes: () => Promise<Fortune[]>;
+  find: (id: string) => Promise<World>;
+  getAllWorlds: () => Promise<World[]>;
+  bulkUpdate: (worlds: World[]) => Promise<(UpsertResult | UpsertResult[]) | postgres.RowList<postgres.Row[]>>;
+}

+ 23 - 0
frameworks/TypeScript/ditsmod/src/app/utils/helper.ts

@@ -0,0 +1,23 @@
+import { Fortune } from '#service/db/types.js';
+
+export function getRandomNumber() {
+  return Math.floor(Math.random() * 10000) + 1;
+}
+
+export function getNumberOfObjects(queries: string) {
+  return Math.min(Math.max(parseInt(queries) || 1, 1), 500);
+}
+
+export const additionalFortune = {
+  id: 0,
+  message: 'Additional fortune added at request time.',
+};
+
+export function compare(a: Fortune, b: Fortune) {
+  if (a.message < b.message) {
+    return -1;
+  } else if (a.message > b.message) {
+    return 1;
+  }
+  return 0;
+}

+ 18 - 0
frameworks/TypeScript/ditsmod/src/main.ts

@@ -0,0 +1,18 @@
+import cluster from 'node:cluster';
+import { ServerOptions } from 'node:http';
+import { availableParallelism } from 'node:os';
+import { Application } from '@ditsmod/core';
+
+import { AppModule } from './app/app.module.js';
+
+const numCpus = availableParallelism();
+
+if (numCpus > 1 && cluster.isPrimary) {
+  for (let i = 0; i < numCpus; i++) {
+    cluster.fork();
+  }
+} else {
+  const serverOptions: ServerOptions = { keepAlive: true, keepAliveTimeout: 0 };
+  const app = await new Application().bootstrap(AppModule, { serverOptions });
+  app.server.listen(8080, '0.0.0.0');
+}

+ 9 - 0
frameworks/TypeScript/ditsmod/tsconfig.build.json

@@ -0,0 +1,9 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "dist",
+    "rootDir": "src",
+    "tsBuildInfoFile": "dist/build.tsbuildinfo"
+  },
+  "include": ["src"],
+}

+ 29 - 0
frameworks/TypeScript/ditsmod/tsconfig.json

@@ -0,0 +1,29 @@
+{
+  "compilerOptions": {
+    "strict": true,
+    "target": "es2022",
+    "module": "nodenext",
+    "moduleResolution": "nodenext",
+    "outDir": "dist",
+    "sourceMap": true,
+    "composite": true,
+    "declaration": true,
+    "declarationMap": true,
+    "skipLibCheck": true,
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "removeComments": false,
+    "noImplicitAny": true,
+    "strictPropertyInitialization": false,
+    "allowJs": false,
+    "paths": {
+      "#routed/*": ["./src/app/modules/routed/*"],
+      "#service/*": ["./src/app/modules/service/*"],
+      "#utils/*": ["./src/app/utils/*"],
+    }
+  },
+  "include": [
+    "src",
+    "test"
+  ]
+}