@e22m4u/js-trie-router-data-mapper 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.c8rc ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "all": true,
3
+ "include": [
4
+ "src/**/*.js"
5
+ ],
6
+ "exclude": [
7
+ "src/**/*.spec.js"
8
+ ]
9
+ }
package/.commitlintrc ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": [
3
+ "@commitlint/config-conventional"
4
+ ]
5
+ }
package/.editorconfig ADDED
@@ -0,0 +1,13 @@
1
+ # EditorConfig is awesome: https://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ # Unix-style newlines with a newline ending every file
7
+ [*]
8
+ end_of_line = lf
9
+ insert_final_newline = true
10
+ charset = utf-8
11
+ indent_style = space
12
+ indent_size = 2
13
+ max_line_length = 80
@@ -0,0 +1 @@
1
+ npx --no -- commitlint --edit $1
@@ -0,0 +1,6 @@
1
+ npm run lint:fix
2
+ npm run format
3
+ npm run test
4
+ npm run build:cjs
5
+
6
+ git add -A
package/.mocharc.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extension": ["js"],
3
+ "spec": "src/**/*.spec.js"
4
+ }
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "bracketSpacing": false,
3
+ "singleQuote": true,
4
+ "printWidth": 80,
5
+ "trailingComma": "all",
6
+ "arrowParens": "avoid"
7
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2025 Mikhail Evstropov <e22m4u@yandex.ru>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ ## @e22m4u/js-trie-router-data-mapper
2
+
3
+ Парсинг, валидация и проекция данных для
4
+ [@e22m4u/js-trie-router](https://www.npmjs.com/package/@e22m4u/js-trie-router).
5
+
6
+ ## Содержание
7
+
8
+ - [Установка](#установка)
9
+ - [Описание](#описание)
10
+ - [Использование](#использование)
11
+ - [Тесты](#тесты)
12
+ - [Лицензия](#лицензия)
13
+
14
+ ## Установка
15
+
16
+ ```bash
17
+ npm install @e22m4u/js-trie-router-data-mapper
18
+ ```
19
+
20
+ Модуль поддерживает ESM и CommonJS стандарты.
21
+
22
+ *ESM*
23
+
24
+ ```js
25
+ import {TrieRouterDataMapper} from '@e22m4u/js-trie-router-data-mapper';
26
+ ```
27
+
28
+ *CommonJS*
29
+
30
+ ```js
31
+ const {TrieRouterDataMapper} = require('@e22m4u/js-trie-router-data-mapper');
32
+ ```
33
+
34
+ ## Описание
35
+
36
+ Модуль позволяет определить разметку данных маршрута. На основе разметки
37
+ выполняется автоматический парсинг, валидация и проекция данных HTTP-запроса
38
+ и ответа сервера. Сформированные данные помещаются в контекст запроса
39
+ или определяют структуру возвращаемого ответа.
40
+
41
+ Используется синтаксис указанных ниже модулей (не требуют установки).
42
+
43
+ - Схема данных [@e22m4u/js-data-schema](https://www.npmjs.com/package/@e22m4u/js-data-schema)
44
+ - Схема проекции [@e22m4u/js-data-projector](https://www.npmjs.com/package/@e22m4u/js-data-projector)
45
+
46
+ ## Использование
47
+
48
+ Для корректной работы требуется выполнить подключение модуля к маршрутизатору.
49
+ В этот момент будут зарегистрированы два глобальных хука. Первый хук разбирает
50
+ и проверяет входящие данные, а второй фильтрует ответ сервера.
51
+
52
+ ```js
53
+ import {TrieRouter} from '@e22m4u/js-trie-router';
54
+ import {TrieRouterDataMapper} from '@e22m4u/js-trie-router-data-mapper';
55
+
56
+ const router = new TrieRouter(); // экземпляр маршрутизатора
57
+ router.useService(TrieRouterDataMapper); // <= подключение модуля
58
+ ```
59
+
60
+ Разметка данных задается в свойстве `dataMapper` метаданных маршрута. Ключи
61
+ этого объекта определяют имена полей, добавляемых в контекст запроса, а значения
62
+ описывают правила извлечения, валидации и проекции для каждого источника данных.
63
+
64
+ ```js
65
+ import {HttpMethod} from '@e22m4u/js-trie-router';
66
+ import {HttpData, DataType} from '@e22m4u/js-trie-router-data-mapper';
67
+
68
+ router.defineRoute({
69
+ method: HttpMethod.POST,
70
+ path: '/createUser',
71
+ meta: {
72
+ dataMapper: {
73
+ userData: { // свойство "userData" будет добавлено в "ctx.state"
74
+ source: HttpData.REQUEST_BODY, // источник данных
75
+ schema: DataType.OBJECT, // тип или схема данных
76
+ // property: ... (извлечь свойство из источника)
77
+ // projection: ... (схема проекции)
78
+ },
79
+ },
80
+ },
81
+ handler: ({state: {userData}}) => {
82
+ // ...
83
+ },
84
+ });
85
+ ```
86
+
87
+ Параметры разметки (метаданные маршрута).
88
+
89
+ - `source: HttpData` источник данных;
90
+ - `property?: string` извлечение указанного свойства;
91
+ - `schema?: DataType | DataSchema` тип или схема данных;
92
+ - `projection?: DataProjection` схема проекции;
93
+
94
+ Константы источников данных (параметр `source`).
95
+
96
+ ```js
97
+ export const HttpData = {
98
+ REQUEST_PARAMS: 'requestParams',
99
+ REQUEST_QUERY: 'requestQuery',
100
+ REQUEST_HEADERS: 'requestHeaders',
101
+ REQUEST_COOKIES: 'requestCookies',
102
+ REQUEST_BODY: 'requestBody',
103
+ RESPONSE_BODY: 'responseBody',
104
+ };
105
+ ```
106
+
107
+ Константы типов данных (подробнее [@e22m4u/js-data-schema](https://www.npmjs.com/package/@e22m4u/js-data-schema)).
108
+
109
+ ```js
110
+ export const DataType = {
111
+ ANY: 'any',
112
+ STRING: 'string',
113
+ NUMBER: 'number',
114
+ BOOLEAN: 'boolean',
115
+ ARRAY: 'array',
116
+ OBJECT: 'object',
117
+ }
118
+ ```
119
+
120
+ ### Примеры
121
+
122
+ Извлечение и разбор Query-параметра с JSON значением.
123
+
124
+ ```js
125
+ import {HttpMethod} from '@e22m4u/js-trie-router';
126
+ import {HttpData, DataType} from '@e22m4u/js-trie-router-data-mapper';
127
+
128
+ // GET /parseQuery?filter={"foo":"bar"}
129
+ router.defineRoute({
130
+ method: HttpMethod.GET,
131
+ path: '/parseQuery',
132
+ meta: {
133
+ dataMapper: {
134
+ filter: { // свойство "filter" будет добавлено в "ctx.state"
135
+ source: HttpData.REQUEST_QUERY, // источник данных
136
+ property: 'filter', // извлечь свойство из источника
137
+ schema: { // схема для парсинга и валидации
138
+ type: DataType.OBJECT, // разобрать значение как объект
139
+ required: true, // значение является обязательным
140
+ },
141
+ // подробнее о схеме данных (параметр "schema")
142
+ // см. модуль @e22m4u/js-data-schema
143
+ },
144
+ },
145
+ },
146
+ handler: ({state: {filter}}) => {
147
+ // для запроса GET /parseQuery?filter={"foo":"bar"}
148
+ // значение параметра "filter" будет следующим:
149
+ console.log(typeof filter); // "object"
150
+ console.log(filter); // {foo: 'bar'}
151
+ // если значение разобрать не удалось,
152
+ // то будет выброшена ошибка
153
+ return filter;
154
+ },
155
+ });
156
+ ```
157
+
158
+ Фильтрация свойств возвращаемого объекта согласно схеме проекции.
159
+
160
+ ```js
161
+ import {HttpMethod} from '@e22m4u/js-trie-router';
162
+ import {HttpData} from '@e22m4u/js-trie-router-data-mapper';
163
+
164
+ router.defineRoute({
165
+ method: HttpMethod.GET,
166
+ path: '/responseProjection',
167
+ meta: {
168
+ dataMapper: {
169
+ response: {
170
+ // свойство "response" не будет добавлено в "ctx.state",
171
+ // так как в данном случае источником выступает возвращаемое
172
+ // значение обработчика маршрута, а не входящие данные
173
+ source: HttpData.RESPONSE_BODY, // источник данных
174
+ projection: {foo: true, bar: false}, // схема проекции
175
+ // подробнее о схеме проекции (параметр "projection")
176
+ // см. модуль @e22m4u/js-data-projector
177
+ },
178
+ },
179
+ },
180
+ handler: () => {
181
+ return {
182
+ foo: 10, // доступно, явное правило
183
+ bar: 20, // исключено, явное правило
184
+ baz: 30, // исключено, отсутствует в схеме проекции
185
+ };
186
+ },
187
+ });
188
+ // для запроса GET /responseProjection
189
+ // ответ будет {"foo":10}
190
+ ```
191
+
192
+ ## Тесты
193
+
194
+ ```bash
195
+ npm run test
196
+ ```
197
+
198
+ ## Лицензия
199
+
200
+ MIT
package/build-cjs.js ADDED
@@ -0,0 +1,16 @@
1
+ import * as esbuild from 'esbuild';
2
+ import packageJson from './package.json' with {type: 'json'};
3
+
4
+ await esbuild.build({
5
+ entryPoints: ['src/index.js'],
6
+ outfile: 'dist/cjs/index.cjs',
7
+ format: 'cjs',
8
+ platform: 'node',
9
+ target: ['node12'],
10
+ bundle: true,
11
+ keepNames: true,
12
+ external: [
13
+ ...Object.keys(packageJson.peerDependencies || {}),
14
+ ...Object.keys(packageJson.dependencies || {}),
15
+ ],
16
+ });
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.js
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ DataSchemaRegistry: () => import_js_data_schema2.DataSchemaRegistry,
25
+ DataType: () => import_js_data_schema2.DataType,
26
+ HTTP_DATA_LIST: () => HTTP_DATA_LIST,
27
+ HttpData: () => HttpData,
28
+ ProjectionSchemaRegistry: () => import_js_data_projector2.ProjectionSchemaRegistry,
29
+ TrieRouterDataMapper: () => TrieRouterDataMapper,
30
+ validateDataMappingSchema: () => validateDataMappingSchema
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/data-mapping-schema.js
35
+ var HttpData = {
36
+ REQUEST_PARAMS: "requestParams",
37
+ REQUEST_QUERY: "requestQuery",
38
+ REQUEST_HEADERS: "requestHeaders",
39
+ REQUEST_COOKIES: "requestCookies",
40
+ REQUEST_BODY: "requestBody",
41
+ RESPONSE_BODY: "responseBody"
42
+ };
43
+ var HTTP_DATA_LIST = Object.values(HttpData);
44
+
45
+ // src/trie-router-data-mapper.js
46
+ var import_js_service = require("@e22m4u/js-service");
47
+ var import_js_format2 = require("@e22m4u/js-format");
48
+ var import_js_data_projector = require("@e22m4u/js-data-projector");
49
+ var import_js_trie_router = require("@e22m4u/js-trie-router");
50
+ var import_js_data_schema = require("@e22m4u/js-data-schema");
51
+
52
+ // src/validate-data-mapping-schema.js
53
+ var import_js_format = require("@e22m4u/js-format");
54
+ function validateDataMappingSchema(schema) {
55
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
56
+ throw new import_js_format.InvalidArgumentError(
57
+ "Mapping schema must be an Object, but %v was given.",
58
+ schema
59
+ );
60
+ }
61
+ Object.keys(schema).forEach((propName) => {
62
+ const propOptions = schema[propName];
63
+ if (propOptions === void 0) {
64
+ return;
65
+ }
66
+ if (!propOptions || typeof propOptions !== "object" || Array.isArray(propOptions)) {
67
+ throw new import_js_format.InvalidArgumentError(
68
+ "Property options must be an Object, but %v was given.",
69
+ propOptions
70
+ );
71
+ }
72
+ if (!propOptions.source || typeof propOptions.source !== "string" || !HTTP_DATA_LIST.includes(propOptions.source)) {
73
+ throw new import_js_format.InvalidArgumentError(
74
+ "Data source %v is not supported.",
75
+ propOptions.source
76
+ );
77
+ }
78
+ if (propOptions.property !== void 0 && (!propOptions.property || typeof propOptions.property !== "string")) {
79
+ throw new import_js_format.InvalidArgumentError(
80
+ "Property name must be a non-empty String, but %v was given.",
81
+ propOptions.property
82
+ );
83
+ }
84
+ });
85
+ }
86
+ __name(validateDataMappingSchema, "validateDataMappingSchema");
87
+
88
+ // src/trie-router-data-mapper.js
89
+ var HTTP_DATA_TO_CONTEXT_PROPERTY_MAP = {
90
+ [HttpData.REQUEST_PARAMS]: "params",
91
+ [HttpData.REQUEST_QUERY]: "query",
92
+ [HttpData.REQUEST_HEADERS]: "headers",
93
+ [HttpData.REQUEST_COOKIES]: "cookies",
94
+ [HttpData.REQUEST_BODY]: "body"
95
+ };
96
+ var _TrieRouterDataMapper = class _TrieRouterDataMapper extends import_js_service.Service {
97
+ /**
98
+ * Constructor.
99
+ *
100
+ * @param {import('@e22m4u/js-service').ServiceContainer} container
101
+ */
102
+ constructor(container) {
103
+ super(container);
104
+ const router = this.getService(import_js_trie_router.TrieRouter);
105
+ if (!router.hasPreHandler(dataMappingPreHandler)) {
106
+ router.addPreHandler(dataMappingPreHandler);
107
+ }
108
+ if (!router.hasPostHandler(dataMappingPostHandler)) {
109
+ router.addPostHandler(dataMappingPostHandler);
110
+ }
111
+ }
112
+ /**
113
+ * Create state by mapping schema.
114
+ *
115
+ * @param {import('@e22m4u/js-trie-router').RequestContext} ctx
116
+ * @param {import('./data-mapping-schema.js').DataMappingSchema} schema
117
+ * @returns {object}
118
+ */
119
+ createStateByMappingSchema(ctx, schema) {
120
+ if (!(ctx instanceof import_js_trie_router.RequestContext)) {
121
+ throw new import_js_format2.InvalidArgumentError(
122
+ 'Parameter "ctx" must be a RequestContext instance, but %v was given.',
123
+ ctx
124
+ );
125
+ }
126
+ validateDataMappingSchema(schema);
127
+ const res = {};
128
+ const dataParser = this.getService(import_js_data_schema.DataParser);
129
+ const dataProjector = this.getService(import_js_data_projector.DataProjector);
130
+ Object.keys(schema).forEach((propName) => {
131
+ const propOptions = schema[propName];
132
+ if (propOptions === void 0) {
133
+ return;
134
+ }
135
+ const ctxProp = HTTP_DATA_TO_CONTEXT_PROPERTY_MAP[propOptions.source];
136
+ if (ctxProp === void 0) {
137
+ return;
138
+ }
139
+ let value = ctx[ctxProp];
140
+ if (propOptions.property && typeof propOptions.property === "string") {
141
+ if (value && typeof value === "object" && !Array.isArray(value)) {
142
+ value = value[propOptions.property];
143
+ } else {
144
+ throw new import_js_format2.InvalidArgumentError(
145
+ "Property %v does not exist in %v value from the property %v of the request context.",
146
+ propOptions.property,
147
+ value,
148
+ ctxProp
149
+ );
150
+ }
151
+ }
152
+ if (propOptions.schema !== void 0) {
153
+ const sourcePath = propOptions.property ? `request.${ctxProp}.${propOptions.property}` : `request.${ctxProp}`;
154
+ if (import_js_data_schema.DATA_TYPE_LIST.includes(propOptions.schema)) {
155
+ const dataSchema = { type: propOptions.schema };
156
+ value = dataParser.parse(value, dataSchema, { sourcePath });
157
+ } else {
158
+ value = dataParser.parse(value, propOptions.schema, { sourcePath });
159
+ }
160
+ }
161
+ if (propOptions.projection !== void 0) {
162
+ value = dataProjector.project(value, propOptions.projection);
163
+ }
164
+ res[propName] = value;
165
+ });
166
+ return res;
167
+ }
168
+ /**
169
+ * Filter response by mapping schema.
170
+ *
171
+ * @param {*} data
172
+ * @param {import('./data-mapping-schema.js').DataMappingSchema} schema
173
+ * @returns {*}
174
+ */
175
+ filterResponseByMappingSchema(data, schema) {
176
+ validateDataMappingSchema(schema);
177
+ let res = data;
178
+ const dataParser = this.getService(import_js_data_schema.DataParser);
179
+ const dataProjector = this.getService(import_js_data_projector.DataProjector);
180
+ Object.keys(schema).forEach((propName) => {
181
+ const propOptions = schema[propName];
182
+ if (propOptions === void 0) {
183
+ return;
184
+ }
185
+ if (propOptions.source !== HttpData.RESPONSE_BODY) {
186
+ return;
187
+ }
188
+ if (propOptions.property !== void 0) {
189
+ throw new import_js_format2.InvalidArgumentError(
190
+ 'Option "property" is not supported for the %v source, but %v was given.',
191
+ propOptions.property
192
+ );
193
+ }
194
+ if (propOptions.schema !== void 0) {
195
+ const sourcePath = "response.body";
196
+ const parsingOptions = { sourcePath, noParsingErrors: true };
197
+ if (import_js_data_schema.DATA_TYPE_LIST.includes(propOptions.schema)) {
198
+ const dataSchema = { type: propOptions.schema };
199
+ res = dataParser.parse(res, dataSchema, parsingOptions);
200
+ } else {
201
+ res = dataParser.parse(res, propOptions.schema, parsingOptions);
202
+ }
203
+ }
204
+ if (propOptions.projection !== void 0) {
205
+ res = dataProjector.project(res, propOptions.projection);
206
+ }
207
+ });
208
+ return res;
209
+ }
210
+ };
211
+ __name(_TrieRouterDataMapper, "TrieRouterDataMapper");
212
+ var TrieRouterDataMapper = _TrieRouterDataMapper;
213
+ function dataMappingPreHandler(ctx) {
214
+ const schema = (ctx.meta || {}).dataMapper;
215
+ if (schema === void 0) {
216
+ return;
217
+ }
218
+ const mapper = ctx.container.get(TrieRouterDataMapper);
219
+ const state = mapper.createStateByMappingSchema(ctx, schema);
220
+ ctx.state = { ...ctx.state, ...state };
221
+ }
222
+ __name(dataMappingPreHandler, "dataMappingPreHandler");
223
+ function dataMappingPostHandler(ctx, data) {
224
+ const schema = (ctx.meta || {}).dataMapper;
225
+ if (schema === void 0) {
226
+ return;
227
+ }
228
+ const mapper = ctx.container.get(TrieRouterDataMapper);
229
+ return mapper.filterResponseByMappingSchema(data, schema);
230
+ }
231
+ __name(dataMappingPostHandler, "dataMappingPostHandler");
232
+
233
+ // src/index.js
234
+ var import_js_data_projector2 = require("@e22m4u/js-data-projector");
235
+ var import_js_data_schema2 = require("@e22m4u/js-data-schema");
236
+ // Annotate the CommonJS export names for ESM import in node:
237
+ 0 && (module.exports = {
238
+ DataSchemaRegistry,
239
+ DataType,
240
+ HTTP_DATA_LIST,
241
+ HttpData,
242
+ ProjectionSchemaRegistry,
243
+ TrieRouterDataMapper,
244
+ validateDataMappingSchema
245
+ });
@@ -0,0 +1,41 @@
1
+ import globals from 'globals';
2
+ import eslintJs from '@eslint/js';
3
+ import eslintJsdocPlugin from 'eslint-plugin-jsdoc';
4
+ import eslintMochaPlugin from 'eslint-plugin-mocha';
5
+ import eslintImportPlugin from 'eslint-plugin-import';
6
+ import eslintPrettierConfig from 'eslint-config-prettier';
7
+ import eslintChaiExpectPlugin from 'eslint-plugin-chai-expect';
8
+
9
+ export default [{
10
+ languageOptions: {
11
+ globals: {
12
+ ...globals.node,
13
+ ...globals.es2021,
14
+ ...globals.mocha,
15
+ },
16
+ },
17
+ plugins: {
18
+ 'jsdoc': eslintJsdocPlugin,
19
+ 'mocha': eslintMochaPlugin,
20
+ 'import': eslintImportPlugin,
21
+ 'chai-expect': eslintChaiExpectPlugin,
22
+ },
23
+ rules: {
24
+ ...eslintJs.configs.recommended.rules,
25
+ ...eslintPrettierConfig.rules,
26
+ ...eslintImportPlugin.flatConfigs.recommended.rules,
27
+ ...eslintMochaPlugin.configs.recommended.rules,
28
+ ...eslintChaiExpectPlugin.configs['recommended-flat'].rules,
29
+ ...eslintJsdocPlugin.configs['flat/recommended-error'].rules,
30
+ "curly": "error",
31
+ 'no-duplicate-imports': 'error',
32
+ 'import/export': 0,
33
+ 'jsdoc/reject-any-type': 0,
34
+ 'jsdoc/reject-function-type': 0,
35
+ 'jsdoc/require-param-description': 0,
36
+ 'jsdoc/require-returns-description': 0,
37
+ 'jsdoc/require-property-description': 0,
38
+ 'jsdoc/tag-lines': ['error', 'any', {startLines: 1}],
39
+ },
40
+ files: ['src/**/*.js'],
41
+ }];
@@ -0,0 +1,52 @@
1
+ import http from 'http';
2
+ import {DataType} from '@e22m4u/js-data-schema';
3
+ import {HttpMethod, TrieRouter} from '@e22m4u/js-trie-router';
4
+
5
+ import {
6
+ HttpData,
7
+ TrieRouterDataMapper,
8
+ } from '@e22m4u/js-trie-router-data-mapper';
9
+
10
+ const router = new TrieRouter();
11
+ router.useService(TrieRouterDataMapper);
12
+
13
+ // регистрация маршрута для разбора
14
+ // query параметра "filter"
15
+ router.defineRoute({
16
+ method: HttpMethod.GET,
17
+ path: '/parseQuery',
18
+ meta: {
19
+ dataMapper: {
20
+ filter: {
21
+ source: HttpData.REQUEST_QUERY,
22
+ property: 'filter',
23
+ schema: {
24
+ type: DataType.OBJECT,
25
+ required: true,
26
+ },
27
+ },
28
+ },
29
+ },
30
+ handler: ({state: {filter}}) => {
31
+ return filter;
32
+ },
33
+ });
34
+
35
+ // создание экземпляра HTTP сервера
36
+ // и подключение обработчика запросов
37
+ const server = new http.Server();
38
+ server.on('request', router.requestListener);
39
+
40
+ // прослушивание входящих запросов
41
+ // на указанный адрес и порт
42
+ const port = 3000;
43
+ const host = '0.0.0.0';
44
+ server.listen(port, host, function () {
45
+ const cyan = '\x1b[36m%s\x1b[0m';
46
+ console.log(cyan, 'Server listening on port:', port);
47
+ console.log(
48
+ cyan,
49
+ 'Open in browser:',
50
+ `http://${host}:${port}/parseQuery?filter={"foo":"bar"}`,
51
+ );
52
+ });
@@ -0,0 +1,47 @@
1
+ import http from 'http';
2
+ import {HttpMethod, TrieRouter} from '@e22m4u/js-trie-router';
3
+
4
+ import {
5
+ HttpData,
6
+ TrieRouterDataMapper,
7
+ } from '@e22m4u/js-trie-router-data-mapper';
8
+
9
+ const router = new TrieRouter();
10
+ router.useService(TrieRouterDataMapper);
11
+
12
+ // регистрация маршрута для проверки
13
+ // проекции возвращаемого объекта
14
+ router.defineRoute({
15
+ method: HttpMethod.GET,
16
+ path: '/responseProjection',
17
+ meta: {
18
+ dataMapper: {
19
+ response: {
20
+ source: HttpData.RESPONSE_BODY,
21
+ projection: {foo: true, bar: false},
22
+ },
23
+ },
24
+ },
25
+ handler: () => {
26
+ return {foo: 10, bar: 20, baz: 30};
27
+ },
28
+ });
29
+
30
+ // создание экземпляра HTTP сервера
31
+ // и подключение обработчика запросов
32
+ const server = new http.Server();
33
+ server.on('request', router.requestListener);
34
+
35
+ // прослушивание входящих запросов
36
+ // на указанный адрес и порт
37
+ const port = 3000;
38
+ const host = '0.0.0.0';
39
+ server.listen(port, host, function () {
40
+ const cyan = '\x1b[36m%s\x1b[0m';
41
+ console.log(cyan, 'Server listening on port:', port);
42
+ console.log(
43
+ cyan,
44
+ 'Open in browser:',
45
+ `http://${host}:${port}/responseProjection`,
46
+ );
47
+ });
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@e22m4u/js-trie-router-data-mapper",
3
+ "version": "0.1.0",
4
+ "description": "Парсинг, валидация и проекция данных для @e22m4u/js-trie-router",
5
+ "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "data",
9
+ "trie",
10
+ "router",
11
+ "parsing",
12
+ "validation"
13
+ ],
14
+ "homepage": "https://gitrepos.ru/e22m4u/js-trie-router-data-mapper",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://gitrepos.ru/e22m4u/js-trie-router-data-mapper.git"
18
+ },
19
+ "type": "module",
20
+ "types": "./src/index.d.ts",
21
+ "module": "./src/index.js",
22
+ "main": "./dist/cjs/index.cjs",
23
+ "exports": {
24
+ "types": "./src/index.d.ts",
25
+ "import": "./src/index.js",
26
+ "require": "./dist/cjs/index.cjs"
27
+ },
28
+ "engines": {
29
+ "node": ">=12"
30
+ },
31
+ "scripts": {
32
+ "lint": "tsc && eslint ./src",
33
+ "lint:fix": "tsc && eslint ./src --fix",
34
+ "format": "prettier --write \"./src/**/*.js\"",
35
+ "test": "npm run lint && c8 --reporter=text-summary mocha --bail",
36
+ "test:coverage": "npm run lint && c8 --reporter=text mocha --bail",
37
+ "build:cjs": "rimraf ./dist/cjs && node build-cjs.js",
38
+ "prepare": "husky"
39
+ },
40
+ "dependencies": {
41
+ "@e22m4u/js-data-projector": "~0.2.0",
42
+ "@e22m4u/js-data-schema": "~0.0.7",
43
+ "@e22m4u/js-format": "~0.3.2",
44
+ "@e22m4u/js-service": "~0.5.1"
45
+ },
46
+ "peerDependencies": {
47
+ "@e22m4u/js-trie-router": "~0.5.12"
48
+ },
49
+ "devDependencies": {
50
+ "@commitlint/cli": "~20.3.1",
51
+ "@commitlint/config-conventional": "~20.3.1",
52
+ "@eslint/js": "~9.39.2",
53
+ "@types/chai": "~5.2.3",
54
+ "@types/mocha": "~10.0.10",
55
+ "c8": "~10.1.3",
56
+ "chai": "~6.2.2",
57
+ "esbuild": "~0.27.2",
58
+ "eslint": "~9.39.2",
59
+ "eslint-config-prettier": "~10.1.8",
60
+ "eslint-plugin-chai-expect": "~3.1.0",
61
+ "eslint-plugin-import": "~2.32.0",
62
+ "eslint-plugin-jsdoc": "~62.0.0",
63
+ "eslint-plugin-mocha": "~11.2.0",
64
+ "globals": "~17.0.0",
65
+ "husky": "~9.1.7",
66
+ "mocha": "~11.7.5",
67
+ "prettier": "~3.7.4",
68
+ "rimraf": "~6.1.2",
69
+ "typescript": "~5.9.3"
70
+ }
71
+ }
@@ -0,0 +1,41 @@
1
+ import {ProjectionSchema} from '@e22m4u/js-data-projector';
2
+ import {DataSchema, DataType} from '@e22m4u/js-data-schema';
3
+
4
+ /**
5
+ * Http data.
6
+ */
7
+ export declare const HttpData: {
8
+ REQUEST_PARAMS: 'requestParams';
9
+ REQUEST_QUERY: 'requestQuery';
10
+ REQUEST_HEADERS: 'requestHeaders';
11
+ REQUEST_COOKIES: 'requestCookies';
12
+ REQUEST_BODY: 'requestBody';
13
+ RESPONSE_BODY: 'responseBody';
14
+ };
15
+
16
+ /**
17
+ * Http data.
18
+ */
19
+ export type HttpData = (typeof HttpData)[keyof typeof HttpData];
20
+
21
+ /**
22
+ * Http data list.
23
+ */
24
+ export declare const HTTP_DATA_LIST: HttpData[];
25
+
26
+ /**
27
+ * Data mapping schema.
28
+ */
29
+ export type DataMappingSchema = {
30
+ [property: string]: DataMappingPropertyOptions | undefined;
31
+ };
32
+
33
+ /**
34
+ * Data mapping property options.
35
+ */
36
+ export interface DataMappingPropertyOptions {
37
+ source: HttpData;
38
+ property?: string;
39
+ schema?: DataSchema | DataType;
40
+ projection?: ProjectionSchema;
41
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Http data.
3
+ */
4
+ export const HttpData = {
5
+ REQUEST_PARAMS: 'requestParams',
6
+ REQUEST_QUERY: 'requestQuery',
7
+ REQUEST_HEADERS: 'requestHeaders',
8
+ REQUEST_COOKIES: 'requestCookies',
9
+ REQUEST_BODY: 'requestBody',
10
+ RESPONSE_BODY: 'responseBody',
11
+ };
12
+
13
+ /**
14
+ * Http data list.
15
+ */
16
+ export const HTTP_DATA_LIST = Object.values(HttpData);
package/src/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './route-meta.js';
2
+ export * from './data-mapping-schema.js';
3
+ export * from './trie-router-data-mapper.js';
4
+ export * from './validate-data-mapping-schema.js';
5
+ export {ProjectionSchemaRegistry} from '@e22m4u/js-data-projector';
6
+ export {DataType, DataSchemaRegistry} from '@e22m4u/js-data-schema';
package/src/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from './data-mapping-schema.js';
2
+ export * from './trie-router-data-mapper.js';
3
+ export * from './validate-data-mapping-schema.js';
4
+ export {ProjectionSchemaRegistry} from '@e22m4u/js-data-projector';
5
+ export {DataType, DataSchemaRegistry} from '@e22m4u/js-data-schema';
@@ -0,0 +1,8 @@
1
+ import '@e22m4u/js-trie-router';
2
+ import {DataMappingSchema} from './data-mapping-schema.js';
3
+
4
+ declare module '@e22m4u/js-trie-router' {
5
+ export interface RouteMeta {
6
+ dataMapper?: DataMappingSchema,
7
+ }
8
+ }
@@ -0,0 +1,37 @@
1
+ import {RequestContext} from '@e22m4u/js-trie-router';
2
+ import {DataMappingSchema} from './data-mapping-schema.js';
3
+ import {Service, ServiceContainer} from '@e22m4u/js-service';
4
+
5
+ /**
6
+ * Trie router data mapper.
7
+ */
8
+ export declare class TrieRouterDataMapper extends Service {
9
+ /**
10
+ * Constructor.
11
+ *
12
+ * @param container
13
+ */
14
+ constructor(container?: ServiceContainer);
15
+
16
+ /**
17
+ * Create state by mapping schema.
18
+ *
19
+ * @param ctx
20
+ * @param schema
21
+ */
22
+ createStateByMappingSchema(
23
+ ctx: RequestContext,
24
+ schema: DataMappingSchema,
25
+ ): object;
26
+
27
+ /**
28
+ * Filter response by mapping schema.
29
+ *
30
+ * @param data
31
+ * @param schema
32
+ */
33
+ filterResponseByMappingSchema(
34
+ data: unknown,
35
+ schema: DataMappingSchema,
36
+ ): unknown;
37
+ }
@@ -0,0 +1,206 @@
1
+ import {Service} from '@e22m4u/js-service';
2
+ import {HttpData} from './data-mapping-schema.js';
3
+ import {InvalidArgumentError} from '@e22m4u/js-format';
4
+ import {DataProjector} from '@e22m4u/js-data-projector';
5
+ import {RequestContext, TrieRouter} from '@e22m4u/js-trie-router';
6
+ import {DATA_TYPE_LIST, DataParser} from '@e22m4u/js-data-schema';
7
+ import {validateDataMappingSchema} from './validate-data-mapping-schema.js';
8
+
9
+ /**
10
+ * Константа HttpData определяет какое свойство контекста
11
+ * запроса будет использовано для формирования данных.
12
+ * Этот объект связывает значения HttpData со свойствами
13
+ * контекста запроса.
14
+ */
15
+ const HTTP_DATA_TO_CONTEXT_PROPERTY_MAP = {
16
+ [HttpData.REQUEST_PARAMS]: 'params',
17
+ [HttpData.REQUEST_QUERY]: 'query',
18
+ [HttpData.REQUEST_HEADERS]: 'headers',
19
+ [HttpData.REQUEST_COOKIES]: 'cookies',
20
+ [HttpData.REQUEST_BODY]: 'body',
21
+ };
22
+
23
+ /**
24
+ * Trie router data mapper.
25
+ */
26
+ export class TrieRouterDataMapper extends Service {
27
+ /**
28
+ * Constructor.
29
+ *
30
+ * @param {import('@e22m4u/js-service').ServiceContainer} container
31
+ */
32
+ constructor(container) {
33
+ super(container);
34
+ const router = this.getService(TrieRouter);
35
+ if (!router.hasPreHandler(dataMappingPreHandler)) {
36
+ router.addPreHandler(dataMappingPreHandler);
37
+ }
38
+ if (!router.hasPostHandler(dataMappingPostHandler)) {
39
+ router.addPostHandler(dataMappingPostHandler);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Create state by mapping schema.
45
+ *
46
+ * @param {import('@e22m4u/js-trie-router').RequestContext} ctx
47
+ * @param {import('./data-mapping-schema.js').DataMappingSchema} schema
48
+ * @returns {object}
49
+ */
50
+ createStateByMappingSchema(ctx, schema) {
51
+ if (!(ctx instanceof RequestContext)) {
52
+ throw new InvalidArgumentError(
53
+ 'Parameter "ctx" must be a RequestContext instance, but %v was given.',
54
+ ctx,
55
+ );
56
+ }
57
+ validateDataMappingSchema(schema);
58
+ const res = {};
59
+ const dataParser = this.getService(DataParser);
60
+ const dataProjector = this.getService(DataProjector);
61
+ // обход каждого свойства схемы
62
+ // для формирования объекта данных
63
+ Object.keys(schema).forEach(propName => {
64
+ // если параметры свойства не определены,
65
+ // то данное свойство пропускается
66
+ const propOptions = schema[propName];
67
+ if (propOptions === undefined) {
68
+ return;
69
+ }
70
+ // если свойство контекста не определено,
71
+ // то данное свойство пропускается
72
+ const ctxProp = HTTP_DATA_TO_CONTEXT_PROPERTY_MAP[propOptions.source];
73
+ if (ctxProp === undefined) {
74
+ return;
75
+ }
76
+ let value = ctx[ctxProp];
77
+ // если определено вложенное свойство,
78
+ // то выполняется попытка его извлечения
79
+ if (propOptions.property && typeof propOptions.property === 'string') {
80
+ // если свойство контекста содержит объект,
81
+ // то извлекается значение вложенного свойства
82
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
83
+ value = value[propOptions.property];
84
+ }
85
+ // если свойство контекста не содержит
86
+ // объект, то выбрасывается ошибка
87
+ else {
88
+ throw new InvalidArgumentError(
89
+ 'Property %v does not exist in %v value ' +
90
+ 'from the property %v of the request context.',
91
+ propOptions.property,
92
+ value,
93
+ ctxProp,
94
+ );
95
+ }
96
+ }
97
+ // если определена схема данных,
98
+ // то выполняется разбор значения
99
+ if (propOptions.schema !== undefined) {
100
+ const sourcePath = propOptions.property
101
+ ? `request.${ctxProp}.${propOptions.property}`
102
+ : `request.${ctxProp}`;
103
+ if (DATA_TYPE_LIST.includes(propOptions.schema)) {
104
+ const dataSchema = {type: propOptions.schema};
105
+ value = dataParser.parse(value, dataSchema, {sourcePath});
106
+ } else {
107
+ value = dataParser.parse(value, propOptions.schema, {sourcePath});
108
+ }
109
+ }
110
+ // если определена схема проекции,
111
+ // то выполняется создание проекции
112
+ if (propOptions.projection !== undefined) {
113
+ value = dataProjector.project(value, propOptions.projection);
114
+ }
115
+ // значение присваивается
116
+ // результирующему объекту
117
+ res[propName] = value;
118
+ });
119
+ return res;
120
+ }
121
+
122
+ /**
123
+ * Filter response by mapping schema.
124
+ *
125
+ * @param {*} data
126
+ * @param {import('./data-mapping-schema.js').DataMappingSchema} schema
127
+ * @returns {*}
128
+ */
129
+ filterResponseByMappingSchema(data, schema) {
130
+ validateDataMappingSchema(schema);
131
+ let res = data;
132
+ const dataParser = this.getService(DataParser);
133
+ const dataProjector = this.getService(DataProjector);
134
+ // обход каждого свойства схемы
135
+ // для формирования данных ответа
136
+ Object.keys(schema).forEach(propName => {
137
+ // если параметры свойства не определены,
138
+ // то данное свойство пропускается
139
+ const propOptions = schema[propName];
140
+ if (propOptions === undefined) {
141
+ return;
142
+ }
143
+ // если источником не является тело ответа,
144
+ // то данное свойство пропускается
145
+ if (propOptions.source !== HttpData.RESPONSE_BODY) {
146
+ return;
147
+ }
148
+ // если определено вложенное свойство,
149
+ // то выбрасывается ошибка
150
+ if (propOptions.property !== undefined) {
151
+ throw new InvalidArgumentError(
152
+ 'Option "property" is not supported for the %v source, ' +
153
+ 'but %v was given.',
154
+ propOptions.property,
155
+ );
156
+ }
157
+ // если определена схема данных, то выполняется
158
+ // разбор значения без валидации данных
159
+ if (propOptions.schema !== undefined) {
160
+ const sourcePath = 'response.body';
161
+ const parsingOptions = {sourcePath, noParsingErrors: true};
162
+ if (DATA_TYPE_LIST.includes(propOptions.schema)) {
163
+ const dataSchema = {type: propOptions.schema};
164
+ res = dataParser.parse(res, dataSchema, parsingOptions);
165
+ } else {
166
+ res = dataParser.parse(res, propOptions.schema, parsingOptions);
167
+ }
168
+ }
169
+ // если определена схема проекции,
170
+ // то выполняется создание проекции
171
+ if (propOptions.projection !== undefined) {
172
+ res = dataProjector.project(res, propOptions.projection);
173
+ }
174
+ });
175
+ return res;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Data mapping pre-handler.
181
+ *
182
+ * @type {import('@e22m4u/js-trie-router').PreHandlerHook}
183
+ */
184
+ function dataMappingPreHandler(ctx) {
185
+ const schema = (ctx.meta || {}).dataMapper;
186
+ if (schema === undefined) {
187
+ return;
188
+ }
189
+ const mapper = ctx.container.get(TrieRouterDataMapper);
190
+ const state = mapper.createStateByMappingSchema(ctx, schema);
191
+ ctx.state = {...ctx.state, ...state};
192
+ }
193
+
194
+ /**
195
+ * Data mapping post handler.
196
+ *
197
+ * @type {import('@e22m4u/js-trie-router').PostHandlerHook}
198
+ */
199
+ function dataMappingPostHandler(ctx, data) {
200
+ const schema = (ctx.meta || {}).dataMapper;
201
+ if (schema === undefined) {
202
+ return;
203
+ }
204
+ const mapper = ctx.container.get(TrieRouterDataMapper);
205
+ return mapper.filterResponseByMappingSchema(data, schema);
206
+ }
@@ -0,0 +1,8 @@
1
+ import {DataMappingSchema} from './data-mapping-schema.js';
2
+
3
+ /**
4
+ * Validate data mapping schema.
5
+ *
6
+ * @param schema
7
+ */
8
+ export function validateDataMappingSchema(schema: DataMappingSchema): void;
@@ -0,0 +1,54 @@
1
+ import {InvalidArgumentError} from '@e22m4u/js-format';
2
+ import {HTTP_DATA_LIST} from './data-mapping-schema.js';
3
+
4
+ /**
5
+ * Validate data mapping schema.
6
+ *
7
+ * @param {import('./data-mapping-schema.js').DataMappingSchema} schema
8
+ */
9
+ export function validateDataMappingSchema(schema) {
10
+ if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
11
+ throw new InvalidArgumentError(
12
+ 'Mapping schema must be an Object, but %v was given.',
13
+ schema,
14
+ );
15
+ }
16
+ // schema[k]
17
+ Object.keys(schema).forEach(propName => {
18
+ const propOptions = schema[propName];
19
+ if (propOptions === undefined) {
20
+ return;
21
+ }
22
+ if (
23
+ !propOptions ||
24
+ typeof propOptions !== 'object' ||
25
+ Array.isArray(propOptions)
26
+ ) {
27
+ throw new InvalidArgumentError(
28
+ 'Property options must be an Object, but %v was given.',
29
+ propOptions,
30
+ );
31
+ }
32
+ // schema[k].source
33
+ if (
34
+ !propOptions.source ||
35
+ typeof propOptions.source !== 'string' ||
36
+ !HTTP_DATA_LIST.includes(propOptions.source)
37
+ ) {
38
+ throw new InvalidArgumentError(
39
+ 'Data source %v is not supported.',
40
+ propOptions.source,
41
+ );
42
+ }
43
+ // schema[k].property
44
+ if (
45
+ propOptions.property !== undefined &&
46
+ (!propOptions.property || typeof propOptions.property !== 'string')
47
+ ) {
48
+ throw new InvalidArgumentError(
49
+ 'Property name must be a non-empty String, but %v was given.',
50
+ propOptions.property,
51
+ );
52
+ }
53
+ });
54
+ }
@@ -0,0 +1,75 @@
1
+ import {expect} from 'chai';
2
+ import {format} from '@e22m4u/js-format';
3
+ import {HttpData} from './data-mapping-schema.js';
4
+ import {validateDataMappingSchema} from './validate-data-mapping-schema.js';
5
+
6
+ describe('validateDataMappingSchema', function () {
7
+ it('should require the "schema" parameter to be an Object', function () {
8
+ const throwable = v => () => validateDataMappingSchema(v);
9
+ const error = s =>
10
+ format('Mapping schema must be an Object, but %s was given.', s);
11
+ expect(throwable('str')).to.throw(error('"str"'));
12
+ expect(throwable('')).to.throw(error('""'));
13
+ expect(throwable(10)).to.throw(error('10'));
14
+ expect(throwable(0)).to.throw(error('0'));
15
+ expect(throwable(true)).to.throw(error('true'));
16
+ expect(throwable(false)).to.throw(error('false'));
17
+ expect(throwable([])).to.throw(error('Array'));
18
+ expect(throwable(undefined)).to.throw(error('undefined'));
19
+ expect(throwable(null)).to.throw(error('null'));
20
+ expect(throwable(() => undefined)).to.throw(error('Function'));
21
+ throwable({})();
22
+ });
23
+
24
+ it('should require property options to be an Object', function () {
25
+ const throwable = v => () => validateDataMappingSchema({prop: v});
26
+ const error = s =>
27
+ format('Property options must be an Object, but %s was given.', s);
28
+ expect(throwable('str')).to.throw(error('"str"'));
29
+ expect(throwable('')).to.throw(error('""'));
30
+ expect(throwable(10)).to.throw(error('10'));
31
+ expect(throwable(0)).to.throw(error('0'));
32
+ expect(throwable(true)).to.throw(error('true'));
33
+ expect(throwable(false)).to.throw(error('false'));
34
+ expect(throwable([])).to.throw(error('Array'));
35
+ expect(throwable(null)).to.throw(error('null'));
36
+ expect(throwable(() => undefined)).to.throw(error('Function'));
37
+ throwable({source: HttpData.REQUEST_BODY})();
38
+ throwable(undefined)();
39
+ });
40
+
41
+ it('should require the "source" option to be a HttpData value', function () {
42
+ const throwable = v => () => validateDataMappingSchema({prop: {source: v}});
43
+ const error = s => format('Data source %s is not supported.', s);
44
+ expect(throwable('str')).to.throw(error('"str"'));
45
+ expect(throwable('')).to.throw(error('""'));
46
+ expect(throwable(10)).to.throw(error('10'));
47
+ expect(throwable(0)).to.throw(error('0'));
48
+ expect(throwable(true)).to.throw(error('true'));
49
+ expect(throwable(false)).to.throw(error('false'));
50
+ expect(throwable([])).to.throw(error('Array'));
51
+ expect(throwable(undefined)).to.throw(error('undefined'));
52
+ expect(throwable(null)).to.throw(error('null'));
53
+ expect(throwable(() => undefined)).to.throw(error('Function'));
54
+ throwable(HttpData.REQUEST_BODY)();
55
+ });
56
+
57
+ it('should require the "property" option to be a non-empty String', function () {
58
+ const throwable = v => () =>
59
+ validateDataMappingSchema({
60
+ prop: {source: HttpData.REQUEST_BODY, property: v},
61
+ });
62
+ const error = s =>
63
+ format('Property name must be a non-empty String, but %s was given.', s);
64
+ expect(throwable('')).to.throw(error('""'));
65
+ expect(throwable(10)).to.throw(error('10'));
66
+ expect(throwable(0)).to.throw(error('0'));
67
+ expect(throwable(true)).to.throw(error('true'));
68
+ expect(throwable(false)).to.throw(error('false'));
69
+ expect(throwable([])).to.throw(error('Array'));
70
+ expect(throwable(null)).to.throw(error('null'));
71
+ expect(throwable(() => undefined)).to.throw(error('Function'));
72
+ throwable('str')();
73
+ throwable(undefined)();
74
+ });
75
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "target": "es2022",
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "noEmit": true,
8
+ "allowJs": true
9
+ },
10
+ "include": [
11
+ "./src/**/*.ts",
12
+ "./src/**/*.js"
13
+ ]
14
+ }