fockeror 0.1.0 → 0.2.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 FOCKUSTY
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FOCKUSTY
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.
@@ -1,4 +1,14 @@
1
+ /**
2
+ * Регулярное выражение для поиска плейсхолдеров вида `${{ key }}`.
3
+ * Допускает только один пробел внутри скобок.
4
+ *
5
+ * @example
6
+ * // Найдёт: ${{ username }}
7
+ */
1
8
  export declare const PLACEHOLDER_PATTERN: RegExp;
9
+ /** Статус HTTP по умолчанию (500 Internal Server Error). */
2
10
  export declare const DEFAULT_HTTP_STATUS: 500;
11
+ /** Регулярное выражение для экранирования специальных символов при создании RegExp из ключа. */
3
12
  export declare const CLEAN_SEARCH_REGEX: RegExp;
13
+ /** Строка замены для экранирования специальных символов. */
4
14
  export declare const CLEAN_REGEX_REPLACER = "\\$&";
package/dist/constants.js CHANGED
@@ -1,7 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CLEAN_REGEX_REPLACER = exports.CLEAN_SEARCH_REGEX = exports.DEFAULT_HTTP_STATUS = exports.PLACEHOLDER_PATTERN = void 0;
4
+ /**
5
+ * Регулярное выражение для поиска плейсхолдеров вида `${{ key }}`.
6
+ * Допускает только один пробел внутри скобок.
7
+ *
8
+ * @example
9
+ * // Найдёт: ${{ username }}
10
+ */
4
11
  exports.PLACEHOLDER_PATTERN = /\$\{\{\s{1}([^}\s]+)\s{1}\}\}/g;
12
+ /** Статус HTTP по умолчанию (500 Internal Server Error). */
5
13
  exports.DEFAULT_HTTP_STATUS = 500;
14
+ /** Регулярное выражение для экранирования специальных символов при создании RegExp из ключа. */
6
15
  exports.CLEAN_SEARCH_REGEX = /[.*+?^${}()|[\]\\]/g;
16
+ /** Строка замены для экранирования специальных символов. */
7
17
  exports.CLEAN_REGEX_REPLACER = "\\$&";
@@ -1,8 +1,24 @@
1
1
  import type { ExceptionFormatterClass, ExceptionOptions } from "./types";
2
+ /**
3
+ * Базовый класс исключения, хранящий данные об ошибке.
4
+ * Позволяет форматировать исключение в специфичный для фреймворка класс через `format()`.
5
+ */
2
6
  export declare class Exception {
3
7
  readonly response: string | Record<string, unknown>;
4
8
  readonly status: number;
5
9
  readonly options?: ExceptionOptions | undefined;
10
+ /**
11
+ * @param response - Текст сообщения или объект с данными об ошибке.
12
+ * @param status - HTTP статус-код.
13
+ * @param options - Дополнительные опции (cause, description и т.д.).
14
+ */
6
15
  constructor(response: string | Record<string, unknown>, status: number, options?: ExceptionOptions | undefined);
16
+ /**
17
+ * Преобразует текущее исключение в экземпляр форматтера, специфичного для фреймворка.
18
+ * @param formatterClass - Класс-конструктор, принимающий параметры (response, status, options).
19
+ * @returns Экземпляр класса форматтера.
20
+ * @template T - Тип возвращаемого форматтера.
21
+ */
7
22
  format<T>(formatterClass: ExceptionFormatterClass<T>): T;
8
23
  }
24
+ export default Exception;
package/dist/exception.js CHANGED
@@ -1,17 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Exception = void 0;
4
+ /**
5
+ * Базовый класс исключения, хранящий данные об ошибке.
6
+ * Позволяет форматировать исключение в специфичный для фреймворка класс через `format()`.
7
+ */
4
8
  class Exception {
5
9
  response;
6
10
  status;
7
11
  options;
12
+ /**
13
+ * @param response - Текст сообщения или объект с данными об ошибке.
14
+ * @param status - HTTP статус-код.
15
+ * @param options - Дополнительные опции (cause, description и т.д.).
16
+ */
8
17
  constructor(response, status, options) {
9
18
  this.response = response;
10
19
  this.status = status;
11
20
  this.options = options;
12
21
  }
22
+ /**
23
+ * Преобразует текущее исключение в экземпляр форматтера, специфичного для фреймворка.
24
+ * @param formatterClass - Класс-конструктор, принимающий параметры (response, status, options).
25
+ * @returns Экземпляр класса форматтера.
26
+ * @template T - Тип возвращаемого форматтера.
27
+ */
13
28
  format(formatterClass) {
14
29
  return new formatterClass(this.response, this.status, this.options);
15
30
  }
16
31
  }
17
32
  exports.Exception = Exception;
33
+ exports.default = Exception;
package/dist/factory.d.ts CHANGED
@@ -1,9 +1,52 @@
1
1
  import type { ErrorTemplateInput, ExceptionFormatterClass, Ferors, Logger } from "./types";
2
- export declare class FerorFactory<FormatterClass> {
2
+ /**
3
+ * Фабричный класс, создающий набор предопределённых ошибок с автоматически сгенерированными кодами.
4
+ * Каждой ошибке присваивается уникальный код формата `base64url(prefix):hex(key:index)`.
5
+ * Плейсхолдеры извлекаются автоматически из полей `message` и `description`.
6
+ *
7
+ * @template FormatterClass - Тип класса-форматтера исключений.
8
+ *
9
+ * @example
10
+ * const factory = new FockerorFactory(logger, CustomExceptionFormatter);
11
+ * const errors = factory.execute('AUTH', {
12
+ * INVALID_TOKEN: {
13
+ * message: 'Invalid token',
14
+ * description: 'The token is expired',
15
+ * status: 401,
16
+ * },
17
+ * USER_NOT_FOUND: {
18
+ * message: 'User ${{ userId }} not found',
19
+ * description: 'No user with id ${{ userId }} exists',
20
+ * status: 404,
21
+ * },
22
+ * });
23
+ *
24
+ * // Статическое исключение (без плейсхолдеров)
25
+ * throw errors.INVALID_TOKEN.exception;
26
+ *
27
+ * // Динамическое исключение с подстановкой
28
+ * throw errors.USER_NOT_FOUND.execute({ userId: '123' });
29
+ */
30
+ export declare class FockerorFactory<FormatterClass> {
3
31
  private readonly logger;
4
32
  private readonly formatterClass;
33
+ /**
34
+ * @param logger - Экземпляр логгера (должен соответствовать интерфейсу Logger).
35
+ * @param formatterClass - Класс для форматирования исключения (должен иметь конструктор, совместимый с Exception).
36
+ */
5
37
  constructor(logger: Logger, formatterClass: ExceptionFormatterClass<FormatterClass>);
38
+ /**
39
+ * Генерирует набор ошибок для указанного префикса и шаблонов.
40
+ * Для каждого шаблона создаётся экземпляр `Fockeror` с автоматически вычисленными плейсхолдерами.
41
+ *
42
+ * @param prefix - Строка для генерации уникального кода (будет преобразована в base64url).
43
+ * @param templates - Объект, где ключи — идентификаторы ошибок, значения — `ErrorTemplateInput`.
44
+ * @returns Объект с экземплярами `Fockeror`, ключи соответствуют ключам входного объекта.
45
+ *
46
+ * @template Templates - Тип входных шаблонов.
47
+ */
6
48
  execute<const Templates extends Record<string, ErrorTemplateInput>>(prefix: string, templates: Templates): Ferors<Templates, FormatterClass>;
7
49
  private generateCode;
8
50
  private defineError;
9
51
  }
52
+ export default FockerorFactory;
package/dist/factory.js CHANGED
@@ -1,15 +1,57 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FerorFactory = void 0;
3
+ exports.FockerorFactory = void 0;
4
4
  const constants_1 = require("./constants");
5
- const feror_1 = require("./feror");
6
- class FerorFactory {
5
+ const fockeror_1 = require("./fockeror");
6
+ /**
7
+ * Фабричный класс, создающий набор предопределённых ошибок с автоматически сгенерированными кодами.
8
+ * Каждой ошибке присваивается уникальный код формата `base64url(prefix):hex(key:index)`.
9
+ * Плейсхолдеры извлекаются автоматически из полей `message` и `description`.
10
+ *
11
+ * @template FormatterClass - Тип класса-форматтера исключений.
12
+ *
13
+ * @example
14
+ * const factory = new FockerorFactory(logger, CustomExceptionFormatter);
15
+ * const errors = factory.execute('AUTH', {
16
+ * INVALID_TOKEN: {
17
+ * message: 'Invalid token',
18
+ * description: 'The token is expired',
19
+ * status: 401,
20
+ * },
21
+ * USER_NOT_FOUND: {
22
+ * message: 'User ${{ userId }} not found',
23
+ * description: 'No user with id ${{ userId }} exists',
24
+ * status: 404,
25
+ * },
26
+ * });
27
+ *
28
+ * // Статическое исключение (без плейсхолдеров)
29
+ * throw errors.INVALID_TOKEN.exception;
30
+ *
31
+ * // Динамическое исключение с подстановкой
32
+ * throw errors.USER_NOT_FOUND.execute({ userId: '123' });
33
+ */
34
+ class FockerorFactory {
7
35
  logger;
8
36
  formatterClass;
37
+ /**
38
+ * @param logger - Экземпляр логгера (должен соответствовать интерфейсу Logger).
39
+ * @param formatterClass - Класс для форматирования исключения (должен иметь конструктор, совместимый с Exception).
40
+ */
9
41
  constructor(logger, formatterClass) {
10
42
  this.logger = logger;
11
43
  this.formatterClass = formatterClass;
12
44
  }
45
+ /**
46
+ * Генерирует набор ошибок для указанного префикса и шаблонов.
47
+ * Для каждого шаблона создаётся экземпляр `Fockeror` с автоматически вычисленными плейсхолдерами.
48
+ *
49
+ * @param prefix - Строка для генерации уникального кода (будет преобразована в base64url).
50
+ * @param templates - Объект, где ключи — идентификаторы ошибок, значения — `ErrorTemplateInput`.
51
+ * @returns Объект с экземплярами `Fockeror`, ключи соответствуют ключам входного объекта.
52
+ *
53
+ * @template Templates - Тип входных шаблонов.
54
+ */
13
55
  execute(prefix, templates) {
14
56
  const templateKeys = Object.keys(templates);
15
57
  const entries = templateKeys.map((key, index) => {
@@ -21,7 +63,8 @@ class FerorFactory {
21
63
  ...errorTemplate,
22
64
  message: `${code} : ${errorTemplate.message}`,
23
65
  };
24
- return [key, new feror_1.Feror(prefixed, this.logger, this.formatterClass)];
66
+ const fockeror = new fockeror_1.Fockeror(prefixed, this.logger, this.formatterClass);
67
+ return [key, fockeror];
25
68
  });
26
69
  return Object.fromEntries(entries);
27
70
  }
@@ -38,4 +81,5 @@ class FerorFactory {
38
81
  return { ...error, placeholders: uniqueKeys };
39
82
  }
40
83
  }
41
- exports.FerorFactory = FerorFactory;
84
+ exports.FockerorFactory = FockerorFactory;
85
+ exports.default = FockerorFactory;
@@ -0,0 +1,52 @@
1
+ import type { ErrorTemplate, ExceptionFormatterClass, Logger, PlaceholderObject } from "./types";
2
+ /**
3
+ * Класс, представляющий конкретную ошибку с возможностью динамической подстановки плейсхолдеров по примеру `${{ key }}`.
4
+ * @template Placeholders - Кортеж ключей плейсхолдеров (например, `['userId', 'action']`).
5
+ * @template FormatterClass - Тип класса-форматтера для преобразования исключения.
6
+ *
7
+ * @example
8
+ * const error = new Fockeror({
9
+ * message: 'User ${{ userId }} not found',
10
+ * description: 'No user with id ${{ userId }} exists',
11
+ * placeholders: ['userId']
12
+ * }, logger, formatterClass);
13
+ *
14
+ * // Подстановка значений
15
+ * throw error.throw({ userId: '123' });
16
+ *
17
+ * // Статическое исключение (без подстановки)
18
+ * throw error.exception;
19
+ */
20
+ export declare class Fockeror<const Placeholders extends string[], FormatterClass> {
21
+ readonly template: ErrorTemplate<Placeholders>;
22
+ private readonly logger;
23
+ private readonly formatterClass;
24
+ private readonly placeholderRegexes;
25
+ /**
26
+ * Создаёт экземпляр Fockeror.
27
+ * @param template - Шаблон ошибки (должен содержать корректный массив `placeholders`).
28
+ * @param logger - Экземпляр логгера (должен соответствовать интерфейсу Logger).
29
+ * @param formatterClass - Класс для форматирования исключения в специфичный для фреймворка тип.
30
+ * @throws {Error} Если объявленные в `placeholders` ключи не соответствуют реально найденным в тексте.
31
+ */
32
+ constructor(template: ErrorTemplate<Placeholders>, logger: Logger, formatterClass: ExceptionFormatterClass<FormatterClass>);
33
+ execute(placeholders: PlaceholderObject<Placeholders>, cause?: Error): FormatterClass;
34
+ execute(cause?: Error): FormatterClass;
35
+ throw(placeholders: PlaceholderObject<Placeholders>, cause?: Error): never;
36
+ throw(cause?: Error): never;
37
+ /**
38
+ * Геттер, возвращающий статическое исключение (без подстановки плейсхолдеров).
39
+ * Позволяет использовать краткую запись `throw error.exception`.
40
+ */
41
+ get exception(): FormatterClass;
42
+ private createStaticException;
43
+ private createDynamicException;
44
+ private formatTemplate;
45
+ private createException;
46
+ private collectPlaceholdersFromText;
47
+ private validatePlaceholders;
48
+ private buildRegexMap;
49
+ private extractPlaceholders;
50
+ private createRegEx;
51
+ }
52
+ export default Fockeror;
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Fockeror = void 0;
4
+ const constants_1 = require("./constants");
5
+ const exception_1 = require("./exception");
6
+ /**
7
+ * Класс, представляющий конкретную ошибку с возможностью динамической подстановки плейсхолдеров по примеру `${{ key }}`.
8
+ * @template Placeholders - Кортеж ключей плейсхолдеров (например, `['userId', 'action']`).
9
+ * @template FormatterClass - Тип класса-форматтера для преобразования исключения.
10
+ *
11
+ * @example
12
+ * const error = new Fockeror({
13
+ * message: 'User ${{ userId }} not found',
14
+ * description: 'No user with id ${{ userId }} exists',
15
+ * placeholders: ['userId']
16
+ * }, logger, formatterClass);
17
+ *
18
+ * // Подстановка значений
19
+ * throw error.throw({ userId: '123' });
20
+ *
21
+ * // Статическое исключение (без подстановки)
22
+ * throw error.exception;
23
+ */
24
+ class Fockeror {
25
+ template;
26
+ logger;
27
+ formatterClass;
28
+ placeholderRegexes;
29
+ /**
30
+ * Создаёт экземпляр Fockeror.
31
+ * @param template - Шаблон ошибки (должен содержать корректный массив `placeholders`).
32
+ * @param logger - Экземпляр логгера (должен соответствовать интерфейсу Logger).
33
+ * @param formatterClass - Класс для форматирования исключения в специфичный для фреймворка тип.
34
+ * @throws {Error} Если объявленные в `placeholders` ключи не соответствуют реально найденным в тексте.
35
+ */
36
+ constructor(template, logger, formatterClass) {
37
+ this.template = template;
38
+ this.logger = logger;
39
+ this.formatterClass = formatterClass;
40
+ this.placeholderRegexes = this.extractPlaceholders(template);
41
+ }
42
+ /**
43
+ * Возвращает отформатированное исключение с подстановкой данных.
44
+ * Если передан пустой объект `placeholders` или вызов без аргументов, возвращается статическое исключение.
45
+ *
46
+ * @param placeholdersOrCause - Объект с значениями для замены плейсхолдеров или причина ошибки.
47
+ * @param cause - Дополнительная причина ошибки (если первый аргумент — placeholders).
48
+ * @returns Экземпляр форматтера (обычно исключение фреймворка).
49
+ *
50
+ * @example
51
+ * const error = new Fockeror({ message: 'Hello ${{ name }}', description: '', placeholders: ['name'] }, logger, formatter);
52
+ * const exception = error.execute({ name: 'World' });
53
+ * throw exception;
54
+ */
55
+ execute(placeholdersOrCause, cause) {
56
+ let placeholders;
57
+ let actualCause;
58
+ if (placeholdersOrCause instanceof Error) {
59
+ actualCause = placeholdersOrCause;
60
+ }
61
+ else {
62
+ placeholders = placeholdersOrCause;
63
+ actualCause = cause;
64
+ }
65
+ if (!placeholders || Object.keys(placeholders).length === 0) {
66
+ return this.createStaticException(actualCause);
67
+ }
68
+ return this.createDynamicException(placeholders, actualCause);
69
+ }
70
+ /**
71
+ * Немедленно выбрасывает отформатированное исключение с подстановкой данных.
72
+ * @param placeholdersOrCause - Объект с значениями для замены плейсхолдеров или причина ошибки.
73
+ * @param cause - Дополнительная причина ошибки.
74
+ * @throws {FormatterClass} Всегда выбрасывает исключение, отформатированное через `formatterClass`.
75
+ */
76
+ throw(placeholdersOrCause, cause) {
77
+ if (placeholdersOrCause instanceof Error) {
78
+ throw this.execute(placeholdersOrCause);
79
+ }
80
+ throw this.execute(placeholdersOrCause, cause);
81
+ }
82
+ /**
83
+ * Геттер, возвращающий статическое исключение (без подстановки плейсхолдеров).
84
+ * Позволяет использовать краткую запись `throw error.exception`.
85
+ */
86
+ get exception() {
87
+ return this.createStaticException();
88
+ }
89
+ createStaticException(cause) {
90
+ return this.createException({ ...this.template, placeholders: [] }, cause);
91
+ }
92
+ createDynamicException(placeholders, cause) {
93
+ const error = this.formatTemplate(placeholders);
94
+ return this.createException(error, cause);
95
+ }
96
+ formatTemplate(placeholders) {
97
+ let message = this.template.message;
98
+ let description = this.template.description;
99
+ for (const [key, regex] of this.placeholderRegexes.entries()) {
100
+ const value = placeholders[key];
101
+ if (value === undefined) {
102
+ this.logger.error(new Error(`Placeholder \${{ ${key} }} not provided in data`));
103
+ continue;
104
+ }
105
+ message = message.replace(regex, value);
106
+ description = description.replace(regex, value);
107
+ }
108
+ return {
109
+ ...this.template,
110
+ message,
111
+ description,
112
+ placeholders: [],
113
+ };
114
+ }
115
+ createException(error, cause) {
116
+ const status = error.status ?? this.template.status ?? constants_1.DEFAULT_HTTP_STATUS;
117
+ const exception = new exception_1.Exception(error.message, status, {
118
+ description: error.description,
119
+ cause: cause ?? error.cause,
120
+ });
121
+ return exception.format(this.formatterClass);
122
+ }
123
+ collectPlaceholdersFromText(template) {
124
+ const text = `${template.message} ${template.description}`;
125
+ const set = new Set();
126
+ for (const match of text.matchAll(constants_1.PLACEHOLDER_PATTERN)) {
127
+ set.add(match[1]);
128
+ }
129
+ return set;
130
+ }
131
+ validatePlaceholders(collectedPlaceholders, declaredPlaceholders) {
132
+ if (!declaredPlaceholders) {
133
+ return;
134
+ }
135
+ const errors = [];
136
+ for (const placeholder of declaredPlaceholders) {
137
+ if (collectedPlaceholders.has(placeholder)) {
138
+ continue;
139
+ }
140
+ errors.push(new Error(`Placeholder "${placeholder}" not found in text`));
141
+ }
142
+ for (const placeholder of collectedPlaceholders) {
143
+ if (declaredPlaceholders.includes(placeholder)) {
144
+ continue;
145
+ }
146
+ errors.push(new Error(`Placeholder from text "${placeholder}" not found in placeholders list`));
147
+ }
148
+ if (errors.length !== 0) {
149
+ errors.forEach((e) => this.logger.error(e));
150
+ throw new Error("Placeholder validation failed.");
151
+ }
152
+ }
153
+ buildRegexMap(keys) {
154
+ const map = new Map();
155
+ for (const key of keys) {
156
+ map.set(key, this.createRegEx(key));
157
+ }
158
+ return map;
159
+ }
160
+ extractPlaceholders(template) {
161
+ const collected = this.collectPlaceholdersFromText(template);
162
+ this.validatePlaceholders(collected, template.placeholders);
163
+ return this.buildRegexMap(collected);
164
+ }
165
+ createRegEx(key) {
166
+ const clean = key.replace(constants_1.CLEAN_SEARCH_REGEX, constants_1.CLEAN_REGEX_REPLACER);
167
+ return new RegExp(`\\$\\{\\{\\s{1}${clean}\\s{1}\\}\\}`);
168
+ }
169
+ }
170
+ exports.Fockeror = Fockeror;
171
+ exports.default = Fockeror;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { FerorFactory } from "./factory";
2
- export * from "./feror";
1
+ import { FockerorFactory } from "./factory";
2
+ export * from "./fockeror";
3
3
  export * from "./factory";
4
4
  export * from "./exception";
5
- export type { Logger, ExceptionOptions, } from "./types";
6
- export default FerorFactory;
5
+ export type { Logger, ExceptionOptions } from "./types";
6
+ export default FockerorFactory;
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  const factory_1 = require("./factory");
18
- __exportStar(require("./feror"), exports);
18
+ __exportStar(require("./fockeror"), exports);
19
19
  __exportStar(require("./factory"), exports);
20
20
  __exportStar(require("./exception"), exports);
21
- exports.default = factory_1.FerorFactory;
21
+ exports.default = factory_1.FockerorFactory;
package/dist/types.d.ts CHANGED
@@ -1,24 +1,63 @@
1
1
  import { Exception } from "./exception";
2
- import type { Feror } from "./feror";
2
+ import type { Fockeror } from "./fockeror";
3
+ /** Интерфейс логгера, используемого в модуле. */
3
4
  export interface Logger {
5
+ /** Логирование ошибки. */
4
6
  error: (message: Error) => unknown;
7
+ /** Логирование информационного сообщения. */
5
8
  execute: (message: string) => unknown;
6
9
  }
10
+ /**
11
+ * Объект, содержащий значения для подстановки вместо плейсхолдеров.
12
+ * @template Placeholders - Массив строковых ключей (например, `['userId', 'role']`).
13
+ * @example
14
+ * type Data = PlaceholderObject<['userId', 'role']>; // { userId: string; role: string }
15
+ */
7
16
  export type PlaceholderObject<Placeholders extends string[]> = {
8
17
  [P in Placeholders[number]]: string;
9
18
  };
19
+ /**
20
+ * Рекурсивный тип, извлекающий все ключи плейсхолдеров из строки в кортеж.
21
+ * **Важно:** для корректного вывода типов используйте ровно один пробел внутри `${{ key }}`.
22
+ * В рантайме допускается любое количество пробелов благодаря регулярному выражению.
23
+ *
24
+ * @template InputString - Исходная строка.
25
+ * @template Acc - Аккумулятор кортежа (по умолчанию []).
26
+ * @example
27
+ * type Keys = ExtractPlaceholdersTuple<"Hello ${{ name }}, your id is ${{ id }}">; // ['name', 'id']
28
+ */
10
29
  export type ExtractPlaceholdersTuple<InputString extends string, Acc extends string[] = []> = InputString extends `${infer _}\${{ ${infer P} }}${infer Rest}` ? ExtractPlaceholdersTuple<Rest, [...Acc, P]> : Acc;
30
+ /**
31
+ * Вычисляет кортеж плейсхолдеров, объединяя поля `message` и `description` шаблона.
32
+ * @template T - Объект с обязательными полями `message` и `description`.
33
+ * @example
34
+ * type Template = { message: "User ${{ userId }}", description: "Action ${{ action }}" };
35
+ * type Placeholders = InferPlaceholders<Template>; // ['userId', 'action']
36
+ */
11
37
  export type InferPlaceholders<T extends {
12
38
  message: string;
13
39
  description: string;
14
40
  }> = ExtractPlaceholdersTuple<`${T["message"]} ${T["description"]}`>;
41
+ /**
42
+ * Входной формат описания ошибки без автоматически вычисляемого поля `placeholders`.
43
+ * Используется при создании фабрики ошибок.
44
+ */
15
45
  export type ErrorTemplateInput = {
46
+ /** Текст сообщения об ошибке (может содержать плейсхолдеры `${{ key }}`). */
16
47
  message: string;
48
+ /** Подробное описание ошибки (может содержать плейсхолдеры `${{ key }}`). */
17
49
  description: string;
50
+ /** Дополнительная причина ошибки (объект Error). */
18
51
  cause?: Error;
52
+ /** HTTP статус-код (по умолчанию 500). */
19
53
  status?: number;
54
+ /** Дополнительные опции, которые будут переданы в форматтер исключения. */
20
55
  options?: Record<string, unknown>;
21
56
  };
57
+ /**
58
+ * Внутренний шаблон ошибки, где поле `placeholders` выводится автоматически из `message` и `description`.
59
+ * @template Placeholders - Кортеж ключей плейсхолдеров.
60
+ */
22
61
  export interface ErrorTemplate<Placeholders extends string[]> {
23
62
  message: string;
24
63
  description: string;
@@ -27,14 +66,27 @@ export interface ErrorTemplate<Placeholders extends string[]> {
27
66
  options?: Record<string, unknown>;
28
67
  placeholders: Placeholders;
29
68
  }
69
+ /** Карта ключ → регулярное выражение для замены плейсхолдера в строке. */
30
70
  export type PlaceholderRegexMap<Placeholders extends string[]> = Map<keyof PlaceholderObject<Placeholders>, RegExp>;
71
+ /** Шаблон после замены всех плейсхолдеров (плейсхолдеры отсутствуют). */
31
72
  export type FormattedErrorTemplate = ErrorTemplate<[]>;
32
- export type Ferors<ErrorTemplate extends Record<string, ErrorTemplateInput>, FormatterClass> = {
33
- [K in keyof ErrorTemplate]: Feror<InferPlaceholders<ErrorTemplate[K]>, FormatterClass>;
73
+ /**
74
+ * Преобразует запись входных шаблонов в запись экземпляров `Feror`.
75
+ * @template T - Объект с шаблонами ошибок.
76
+ * @template FormatterClass - Класс форматтера исключений.
77
+ */
78
+ export type Ferors<T extends Record<string, ErrorTemplateInput>, FormatterClass> = {
79
+ [K in keyof T]: Fockeror<InferPlaceholders<T[K]>, FormatterClass>;
34
80
  };
81
+ /** Опции исключения (cause, description). */
35
82
  export interface ExceptionOptions {
36
83
  cause?: unknown;
37
84
  description?: string;
38
85
  }
39
- export type ExceptionConstuctorParameters = ConstructorParameters<typeof Exception>;
40
- export type ExceptionFormatterClass<T> = new (...parameters: ExceptionConstuctorParameters) => T;
86
+ /** Параметры конструктора класса Exception. */
87
+ export type ExceptionConstructorParameters = ConstructorParameters<typeof Exception>;
88
+ /**
89
+ * Класс-конструктор форматтера исключений.
90
+ * @template T - Тип возвращаемого экземпляра.
91
+ */
92
+ export type ExceptionFormatterClass<T> = new (...parameters: ExceptionConstructorParameters) => T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fockeror",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -12,15 +12,19 @@
12
12
  ".": {
13
13
  "types": "./dist/index.d.ts",
14
14
  "import": "./dist/index.js"
15
+ },
16
+ "./index": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
15
19
  }
16
20
  },
17
21
  "repository": {
18
22
  "type": "git",
19
- "url": "git+https://github.com/fockusty/fockeror.git"
23
+ "url": "https://github.com/FOCKUSTY/fockeror.git"
20
24
  },
21
- "homepage": "https://github.com/fockusty/fockeror#readme",
25
+ "homepage": "https://github.com/FOCKUSTY/fockeror#readme",
22
26
  "bugs": {
23
- "url": "https://github.com/fockusty/fockeror/issues"
27
+ "url": "https://github.com/FOCKUSTY/fockeror/issues"
24
28
  },
25
29
  "files": [
26
30
  "dist"
package/dist/feror.d.ts DELETED
@@ -1,21 +0,0 @@
1
- import type { ErrorTemplate, ExceptionFormatterClass, Logger, PlaceholderObject } from "./types";
2
- export declare class Feror<const Placeholders extends string[], FormatterClass> {
3
- readonly template: ErrorTemplate<Placeholders>;
4
- private readonly logger;
5
- private readonly formatterClass;
6
- private readonly placeholderRegexes;
7
- constructor(template: ErrorTemplate<Placeholders>, logger: Logger, formatterClass: ExceptionFormatterClass<FormatterClass>);
8
- execute(placeholders: PlaceholderObject<Placeholders>, cause?: Error): FormatterClass;
9
- execute(cause?: Error): FormatterClass;
10
- throw(placeholders: PlaceholderObject<Placeholders>, cause?: Error): never;
11
- throw(cause?: Error): never;
12
- private createStaticException;
13
- private createDynamicException;
14
- private formatTemplate;
15
- private createException;
16
- private collectPlaceholdersFromText;
17
- private validatePlaceholders;
18
- private buildRegexMap;
19
- private extractPlaceholders;
20
- private createRegEx;
21
- }
package/dist/feror.js DELETED
@@ -1,119 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Feror = void 0;
4
- const constants_1 = require("./constants");
5
- const exception_1 = require("./exception");
6
- const PLACEHOLDER_PATTERN = /\$\{\{\s{1}([^}\s]+)\s{1}\}\}/g;
7
- class Feror {
8
- template;
9
- logger;
10
- formatterClass;
11
- placeholderRegexes;
12
- constructor(template, logger, formatterClass) {
13
- this.template = template;
14
- this.logger = logger;
15
- this.formatterClass = formatterClass;
16
- this.placeholderRegexes = this.extractPlaceholders(template);
17
- }
18
- execute(placeholdersOrCause, cause) {
19
- let placeholders = undefined;
20
- let actualCause;
21
- if (placeholdersOrCause instanceof Error) {
22
- actualCause = placeholdersOrCause;
23
- }
24
- else {
25
- placeholders = placeholdersOrCause;
26
- actualCause = cause;
27
- }
28
- if (!placeholders || Object.keys(placeholders).length === 0) {
29
- return this.createStaticException(actualCause);
30
- }
31
- return this.createDynamicException(placeholders, actualCause);
32
- }
33
- throw(placeholdersOrCause, cause) {
34
- if (placeholdersOrCause instanceof Error) {
35
- throw this.execute(placeholdersOrCause);
36
- }
37
- throw this.execute(placeholdersOrCause, cause);
38
- }
39
- createStaticException(cause) {
40
- return this.createException({ ...this.template, placeholders: [] }, cause);
41
- }
42
- createDynamicException(placeholders, cause) {
43
- const error = this.formatTemplate(placeholders);
44
- return this.createException(error, cause);
45
- }
46
- formatTemplate(placeholders) {
47
- let message = this.template.message;
48
- let description = this.template.description;
49
- for (const [key, regex] of this.placeholderRegexes.entries()) {
50
- const value = placeholders[key];
51
- if (value === undefined) {
52
- this.logger.error(new Error(`Placeholder {${key}} not provided in data`));
53
- continue;
54
- }
55
- message = message.replace(regex, value);
56
- description = description.replace(regex, value);
57
- }
58
- return {
59
- ...this.template,
60
- message,
61
- description,
62
- placeholders: [],
63
- };
64
- }
65
- createException(error, cause) {
66
- const status = error.status ?? this.template.status ?? constants_1.DEFAULT_HTTP_STATUS;
67
- return new exception_1.Exception(error.message, status, {
68
- description: error.description,
69
- cause: cause ?? error.cause,
70
- }).format(this.formatterClass);
71
- }
72
- collectPlaceholdersFromText(template) {
73
- const text = `${template.message} ${template.description}`;
74
- const set = new Set();
75
- for (const match of text.matchAll(PLACEHOLDER_PATTERN)) {
76
- set.add(match[1]);
77
- }
78
- return set;
79
- }
80
- validatePlaceholders(collectedPlaceholders, declaredPlaceholders) {
81
- if (!declaredPlaceholders) {
82
- return;
83
- }
84
- const errors = [];
85
- for (const placeholder of declaredPlaceholders) {
86
- if (collectedPlaceholders.has(placeholder)) {
87
- continue;
88
- }
89
- errors.push(new Error(`Placeholder "${placeholder}" not found in text`));
90
- }
91
- for (const placeholder of collectedPlaceholders) {
92
- if (declaredPlaceholders.includes(placeholder)) {
93
- continue;
94
- }
95
- errors.push(new Error(`Placeholder from text "${placeholder}" not found in placeholders list`));
96
- }
97
- if (errors.length !== 0) {
98
- errors.forEach((e) => this.logger.error(e));
99
- throw new Error("Placeholder validation failed.");
100
- }
101
- }
102
- buildRegexMap(keys) {
103
- const placeholdersMap = new Map();
104
- for (const key of keys) {
105
- placeholdersMap.set(key, this.createRegEx(key));
106
- }
107
- return placeholdersMap;
108
- }
109
- extractPlaceholders(template) {
110
- const collected = this.collectPlaceholdersFromText(template);
111
- this.validatePlaceholders(collected, template.placeholders);
112
- return this.buildRegexMap(collected);
113
- }
114
- createRegEx(key) {
115
- const clean = key.replace(constants_1.CLEAN_SEARCH_REGEX, constants_1.CLEAN_REGEX_REPLACER);
116
- return new RegExp(`\\$\\{\\{\\s{1}${clean}\\s{1}\\}\\}`);
117
- }
118
- }
119
- exports.Feror = Feror;