Browse Source

chore: initial commit

e22m4u 5 days ago
commit
b256b8e4cf
19 changed files with 516 additions and 0 deletions
  1. 9 0
      .c8rc
  2. 5 0
      .commitlintrc
  3. 13 0
      .editorconfig
  4. 18 0
      .gitignore
  5. 1 0
      .husky/commit-msg
  6. 6 0
      .husky/pre-commit
  7. 4 0
      .mocharc.json
  8. 7 0
      .prettierrc
  9. 21 0
      LICENSE
  10. 207 0
      README.md
  11. 16 0
      build-cjs.js
  12. 41 0
      eslint.config.js
  13. 69 0
      package.json
  14. 1 0
      src/index.d.ts
  15. 1 0
      src/index.js
  16. 15 0
      src/trie-router-openapi.d.ts
  17. 56 0
      src/trie-router-openapi.js
  18. 12 0
      src/trie-router-openapi.spec.js
  19. 14 0
      tsconfig.json

+ 9 - 0
.c8rc

@@ -0,0 +1,9 @@
+{
+  "all": true,
+  "include": [
+    "src/**/*.js"
+  ],
+  "exclude": [
+    "src/**/*.spec.js"
+  ]
+}

+ 5 - 0
.commitlintrc

@@ -0,0 +1,5 @@
+{
+  "extends": [
+    "@commitlint/config-conventional"
+  ]
+}

+ 13 - 0
.editorconfig

@@ -0,0 +1,13 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+indent_style = space
+indent_size = 2
+max_line_length = 80

+ 18 - 0
.gitignore

@@ -0,0 +1,18 @@
+# OS
+.DS_Store
+
+# Editor
+.idea
+.vscode
+
+# Npm
+node_modules
+npm-debug.log
+package-lock.json
+
+# Yarn
+.yarn/
+.yarn*
+
+# c8
+coverage

+ 1 - 0
.husky/commit-msg

@@ -0,0 +1 @@
+npx --no -- commitlint --edit $1

+ 6 - 0
.husky/pre-commit

@@ -0,0 +1,6 @@
+npm run lint:fix
+npm run format
+npm run test
+npm run build:cjs
+
+git add -A

+ 4 - 0
.mocharc.json

@@ -0,0 +1,4 @@
+{
+  "extension": ["js"],
+  "spec": "src/**/*.spec.js"
+}

+ 7 - 0
.prettierrc

@@ -0,0 +1,7 @@
+{
+  "bracketSpacing": false,
+  "singleQuote": true,
+  "printWidth": 80,
+  "trailingComma": "all",
+  "arrowParens": "avoid"
+}

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023-2025 Mikhail Evstropov <e22m4u@yandex.ru>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 207 - 0
README.md

@@ -0,0 +1,207 @@
+## @e22m4u/js-trie-router-openapi
+
+Модуль OpenAPI документации для
+[@e22m4u/js-trie-router](https://www.npmjs.com/package/@e22m4u/js-trie-router)
+
+## Содержание
+
+- [Установка](#установка)
+- [Использование](#использование)
+- [Тесты](#тесты)
+- [Лицензия](#лицензия)
+
+## Установка
+
+```bash
+npm install @e22m4u/js-trie-router-openapi
+```
+
+Модуль поддерживает ESM и CommonJS стандарты.
+
+*ESM*
+
+```js
+import {TrieRouterOpenApi} from '@e22m4u/js-trie-router-openapi';
+```
+
+*CommonJS*
+
+```js
+const {TrieRouterOpenApi} = require('@e22m4u/js-trie-router-openapi');
+```
+
+## Использование
+
+Подключение модуля к маршрутизатору.
+
+```js
+import {TrieRouter} from '@e22m4u/js-trie-router';
+import {TrieRouterOpenApi} from '@e22m4u/js-trie-router-openapi';
+
+// создание маршрутизатора
+const router = new TrieRouter();
+
+// подключение расширения
+router.useService(TrieRouterOpenApi, {
+  info: {
+    title: 'API Documentation',
+    version: '0.0.1',
+  },
+});
+```
+
+Определение компонентов схем, используемых в следующих примерах.
+
+```js
+import {OADataType, OADocumentBuilder} from '@e22m4u/js-trie-router-openapi';
+
+const builder = router.getService(OADocumentBuilder);
+
+// данные нового пользователя
+builder.defineSchemaComponent({
+  name: 'UserInput', // <= имя новой схемы
+  schema: {
+    type: OADataType.OBJECT,
+    properties: {
+      email: {
+        type: OADataType.STRING,
+        format: 'email',
+      },
+      password: {
+        type: OADataType.STRING,
+      },
+    },
+    required: ['email', 'password'],
+  },
+});
+
+// публичные данные пользователя
+builder.defineSchemaComponent({
+  name: 'UserOutput', // <= имя новой схемы
+  schema: {
+    type: OADataType.OBJECT,
+    properties: {
+      id: {
+        type: OADataType.STRING,
+        format: 'uuid',
+      },
+      email: {
+        type: OADataType.STRING,
+        format: 'email',
+      },
+    },
+    required: ['id', 'password'],
+  },
+});
+```
+
+Определение метаданных маршрута.
+
+```js
+import {HttpMethod} from '@e22m4u/js-trie-router';
+import {oaSchemaRef, OAMediaType} from '@e22m4u/js-trie-router-openapi';
+
+router.defineRoute({
+  method: HttpMethod.POST,
+  path: '/users',
+  meta: {
+    openApi: {
+      summary: 'Create a new user',
+      // тело запроса
+      requestBody: {
+        description: 'Data for the new user',
+        required: true,
+        content: {
+          [OAMediaType.APPLICATION_JSON]: {
+            schema: oaSchemaRef('UserInput'),
+            // ссылка на схему ^^^
+          },
+        },
+      },
+      responses: {
+        // успешный ответ
+        201: {
+          description: 'User created',
+          content: {
+            [OAMediaType.APPLICATION_JSON]: {
+              schema: oaSchemaRef('UserOutput'),
+              // ссылка на схему ^^^
+            },
+          },
+        },
+      },
+    },
+  },
+  handler: (ctx) => {
+    // ...
+  },
+});
+```
+
+Формирование JSON документа.
+
+```js
+import {OADocumentBuilder} from '@e22m4u/js-trie-router-openapi';
+
+const builder = router.getService(OADocumentBuilder);
+
+const jsonDoc = builder.buildJson(2);
+// первый аргумент указывает количество пробелов
+// для каждого уровня вложенности, и может быть
+// опущен в целях экономии размера документа
+
+console.log(jsonDoc);
+// {
+//   "openapi": "3.1.0",
+//   "info": {
+//     "title": "API Documentation",
+//     "version": "0.0.1"
+//   },
+//   "paths": {
+//     "/users": {
+//       "post": {
+//         "summary": "Create a new user",
+//         "requestBody": {
+//           "description": "Data for the new user",
+//           "required": true,
+//           "content": {
+//             "application/json": {
+//               "schema": {
+//                 "$ref": "#/components/schemas/UserInput"
+//               }
+//             }
+//           }
+//         },
+//         "responses": {
+//           "201": {
+//             "description": "User created",
+//             "content": {
+//               "application/json": {
+//                 "schema": {
+//                   "$ref": "#/components/schemas/UserOutput"
+//                 }
+//               }
+//             }
+//           }
+//         }
+//       }
+//     }
+//   },
+//   "components": {
+//     "schemas": {
+//       "UserInput": { ... },
+//       "UserOutput": { ... }
+//     }
+//   }
+// }
+```
+
+## Тесты
+
+```bash
+npm run test
+```
+
+## Лицензия
+
+MIT

+ 16 - 0
build-cjs.js

@@ -0,0 +1,16 @@
+import * as esbuild from 'esbuild';
+import packageJson from './package.json' with {type: 'json'};
+
+await esbuild.build({
+  entryPoints: ['src/index.js'],
+  outfile: 'dist/cjs/index.cjs',
+  format: 'cjs',
+  platform: 'node',
+  target: ['node12'],
+  bundle: true,
+  keepNames: true,
+  external: [
+    ...Object.keys(packageJson.peerDependencies || {}),
+    ...Object.keys(packageJson.dependencies || {}),
+  ],
+});

+ 41 - 0
eslint.config.js

@@ -0,0 +1,41 @@
+import globals from 'globals';
+import eslintJs from '@eslint/js';
+import eslintJsdocPlugin from 'eslint-plugin-jsdoc';
+import eslintMochaPlugin from 'eslint-plugin-mocha';
+import eslintImportPlugin from 'eslint-plugin-import';
+import eslintPrettierConfig from 'eslint-config-prettier';
+import eslintChaiExpectPlugin from 'eslint-plugin-chai-expect';
+
+export default [{
+  languageOptions: {
+    globals: {
+      ...globals.node,
+      ...globals.es2021,
+      ...globals.mocha,
+    },
+  },
+  plugins: {
+    'jsdoc': eslintJsdocPlugin,
+    'mocha': eslintMochaPlugin,
+    'import': eslintImportPlugin,
+    'chai-expect': eslintChaiExpectPlugin,
+  },
+  rules: {
+    ...eslintJs.configs.recommended.rules,
+    ...eslintPrettierConfig.rules,
+    ...eslintImportPlugin.flatConfigs.recommended.rules,
+    ...eslintMochaPlugin.configs.recommended.rules,
+    ...eslintChaiExpectPlugin.configs['recommended-flat'].rules,
+    ...eslintJsdocPlugin.configs['flat/recommended-error'].rules,
+    "curly": "error",
+    'no-duplicate-imports': 'error',
+    'import/export': 0,
+    'jsdoc/reject-any-type': 0,
+    'jsdoc/reject-function-type': 0,
+    'jsdoc/require-param-description': 0,
+    'jsdoc/require-returns-description': 0,
+    'jsdoc/require-property-description': 0,
+    'jsdoc/tag-lines': ['error', 'any', {startLines: 1}],
+  },
+  files: ['src/**/*.js'],
+}];

+ 69 - 0
package.json

@@ -0,0 +1,69 @@
+{
+  "name": "@e22m4u/js-trie-router-openapi",
+  "version": "0.0.0",
+  "description": "Модуль OpenAPI документации для @e22m4u/js-trie-router",
+  "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
+  "license": "MIT",
+  "keywords": [
+    "trie",
+    "router",
+    "schema",
+    "openapi"
+  ],
+  "homepage": "https://gitrepos.ru/e22m4u/js-trie-router-openapi",
+  "repository": {
+    "type": "git",
+    "url": "git+https://gitrepos.ru/e22m4u/js-trie-router-openapi.git"
+  },
+  "type": "module",
+  "types": "./src/index.d.ts",
+  "module": "./src/index.js",
+  "main": "./dist/cjs/index.cjs",
+  "exports": {
+    "types": "./src/index.d.ts",
+    "import": "./src/index.js",
+    "require": "./dist/cjs/index.cjs"
+  },
+  "engines": {
+    "node": ">=12"
+  },
+  "scripts": {
+    "lint": "tsc && eslint ./src",
+    "lint:fix": "tsc && eslint ./src --fix",
+    "format": "prettier --write \"./src/**/*.js\"",
+    "test": "npm run lint && c8 --reporter=text-summary mocha --bail",
+    "test:coverage": "npm run lint && c8 --reporter=text mocha --bail",
+    "build:cjs": "rimraf ./dist/cjs && node build-cjs.js",
+    "prepare": "husky"
+  },
+  "dependencies": {
+    "@e22m4u/js-format": "~0.3.2",
+    "@e22m4u/js-openapi": "~0.0.4",
+    "@e22m4u/js-service": "~0.5.1"
+  },
+  "peerDependencies": {
+    "@e22m4u/js-trie-router": "~0.5.12"
+  },
+  "devDependencies": {
+    "@commitlint/cli": "~20.3.1",
+    "@commitlint/config-conventional": "~20.3.1",
+    "@eslint/js": "~9.39.2",
+    "@types/chai": "~5.2.3",
+    "@types/mocha": "~10.0.10",
+    "c8": "~10.1.3",
+    "chai": "~6.2.2",
+    "esbuild": "~0.27.2",
+    "eslint": "~9.39.2",
+    "eslint-config-prettier": "~10.1.8",
+    "eslint-plugin-chai-expect": "~3.1.0",
+    "eslint-plugin-import": "~2.32.0",
+    "eslint-plugin-jsdoc": "~62.0.0",
+    "eslint-plugin-mocha": "~11.2.0",
+    "globals": "~17.0.0",
+    "husky": "~9.1.7",
+    "mocha": "~11.7.5",
+    "prettier": "~3.7.4",
+    "rimraf": "~6.1.2",
+    "typescript": "~5.9.3"
+  }
+}

+ 1 - 0
src/index.d.ts

@@ -0,0 +1 @@
+export {TrieRouterOpenApi} from './trie-router-openapi.js';

+ 1 - 0
src/index.js

@@ -0,0 +1 @@
+export {TrieRouterOpenApi} from './trie-router-openapi.js';

+ 15 - 0
src/trie-router-openapi.d.ts

@@ -0,0 +1,15 @@
+import {OADocumentInput} from '@e22m4u/js-openapi';
+import {Service, ServiceContainer} from '@e22m4u/js-service';
+
+/**
+ * Trie router OpenAPI.
+ */
+export class TrieRouterOpenApi extends Service {
+  /**
+   * Constructor.
+   *
+   * @param container
+   * @param options
+   */
+  constructor(container: ServiceContainer, options?: OADocumentInput);
+}

+ 56 - 0
src/trie-router-openapi.js

@@ -0,0 +1,56 @@
+import {Service} from '@e22m4u/js-service';
+import {RouteRegistry} from '@e22m4u/js-trie-router';
+import {OADocumentBuilder} from '@e22m4u/js-openapi';
+
+/**
+ * Trie router OpenAPI.
+ */
+export class TrieRouterOpenApi extends Service {
+  /**
+   * Constructor.
+   *
+   * @param {object} [container]
+   * @param {object} [options]
+   */
+  constructor(container, options) {
+    super(container);
+    this.container.use(OADocumentBuilder, options);
+    this._interceptDefineRoute();
+  }
+
+  /**
+   * Intercept define route.
+   *
+   * @private
+   */
+  _interceptDefineRoute() {
+    const registry = this.getService(RouteRegistry);
+    const builder = this.getService(OADocumentBuilder);
+    const originalDefineRoute = registry.defineRoute;
+    // переопределение метода defineRoute для перехвата определений маршрутов
+    registry.defineRoute = routeDef => {
+      // вызов оригинального метода выполняется в первую очередь,
+      // так как оригинальный метод выполняет проверку входных данных
+      const route = originalDefineRoute.call(registry, routeDef);
+      // проверка наличия метаданных OpenAPI, если свойство "meta.openApi"
+      // не определено, то данный маршрут не должен попасть в документацию
+      const openApiOperation = routeDef.meta?.openApi;
+      if (openApiOperation) {
+        // замена формата пути TrieRouter на OpenAPI
+        // пример: "/users/:id" => "/users/{id}"
+        const path = routeDef.path.replace(/:([^/]+)/g, '{$1}');
+        // TrieRouter использует верхний регистр в названии методов,
+        // но сборщик OpenAPI ожидает методы в нижнем регистре
+        // пример: "POST" => "post"
+        const method = routeDef.method.toLowerCase();
+        // регистрация операции в сборщике документа
+        builder.defineOperation({
+          path,
+          method,
+          operation: openApiOperation,
+        });
+      }
+      return route;
+    };
+  }
+}

+ 12 - 0
src/trie-router-openapi.spec.js

@@ -0,0 +1,12 @@
+import {expect} from 'chai';
+import {Service} from '@e22m4u/js-service';
+import {TrieRouterOpenApi} from './trie-router-openapi.js';
+
+describe('TrieRouterOpenApi', function () {
+  describe('constructor', function () {
+    it('should extend the Service class', function () {
+      const res = new TrieRouterOpenApi();
+      expect(res).to.be.instanceOf(Service);
+    });
+  });
+});

+ 14 - 0
tsconfig.json

@@ -0,0 +1,14 @@
+{
+  "compilerOptions": {
+    "strict": true,
+    "target": "es2022",
+    "module": "NodeNext",
+    "moduleResolution": "NodeNext",
+    "noEmit": true,
+    "allowJs": true
+  },
+  "include": [
+    "./src/**/*.ts",
+    "./src/**/*.js"
+  ]
+}