bazis 0.100.6 → 0.100.8
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/README.md +12 -7
- package/core/app/index.ts +1 -0
- package/core/app/messages.ts +44 -0
- package/core/app/runApp.ts +20 -0
- package/core/di/errors/InvalidProviderError.ts +3 -2
- package/core/di/errors/ScopedServiceFromRootError.ts +2 -1
- package/core/di/internal/GraphValidator.ts +6 -2
- package/core/di/module/createContainer.ts +18 -1
- package/core/http/Binding/ParameterBinder.ts +3 -3
- package/core/http/Binding/convert.ts +2 -2
- package/core/http/Binding/modelBinder.ts +17 -18
- package/core/http/Binding/modelValidator.ts +12 -4
- package/core/http/Errors/HttpError.ts +42 -20
- package/core/http/Errors/messages.ts +158 -0
- package/core/http/HttpContext/HttpContext.ts +3 -3
- package/core/http/HttpServer.ts +21 -9
- package/core/http/Middleware/errorHandler.ts +39 -13
- package/core/http/index.ts +1 -0
- package/core/http/options.ts +7 -0
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/kernel/Bazis.ts +9 -1
- package/core/kernel/LifecycleCoordinator.ts +10 -1
- package/core/kernel/config/defineConfig.ts +5 -1
- package/core/kernel/startupFailure.ts +4 -2
- package/library/http-client/HttpClient.ts +7 -0
- package/library/http-client/configKeys.ts +36 -0
- package/library/validation/MessageRegistry.ts +39 -10
- package/library/validation/README.md +1 -1
- package/library/validation/RuleEngine.ts +1 -1
- package/library/validation/SPEC.md +7 -1
- package/library/validation/ValidationError.ts +4 -0
- package/library/validation/index.ts +1 -1
- package/library/validation/modelValidatorAdapter.ts +5 -6
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
export type MessageParams = Readonly<Record<string, unknown>>;
|
|
6
6
|
|
|
7
7
|
/** Built-in default messages (English). */
|
|
8
|
-
const
|
|
8
|
+
export const EN_VALIDATION_MESSAGES: Readonly<Record<string, string>> = Object.freeze({
|
|
9
9
|
validationFailed: "Validation failed",
|
|
10
10
|
invalid: 'Field "{property}" is invalid ({code})',
|
|
11
11
|
required: 'Field "{property}" is required',
|
|
@@ -39,30 +39,36 @@ const BUILT_IN: Readonly<Record<string, string>> = {
|
|
|
39
39
|
customError: 'Check of field "{property}" failed with an error: {error}',
|
|
40
40
|
asyncCustomInSyncCall:
|
|
41
41
|
'Field "{property}": an async custom check is not supported by synchronous validate(); use validateAsync()',
|
|
42
|
-
}
|
|
42
|
+
unknownField: 'Field "{property}" is forbidden or not declared by the model.',
|
|
43
|
+
});
|
|
44
|
+
const BUILT_IN = EN_VALIDATION_MESSAGES;
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
|
-
* Russian texts for every built-in code.
|
|
47
|
+
* Russian texts for every built-in code. An application gets them with
|
|
48
|
+
* `runApp(AppModule, { language: "ru" })`; outside an application:
|
|
46
49
|
* `MessageRegistry.setDefaults(RU_VALIDATION_MESSAGES)`.
|
|
50
|
+
*
|
|
51
|
+
* Numbers stand after a dash ("минимальная длина — 5"), so no template needs
|
|
52
|
+
* a plural form. `typeName.*` keys translate the type names in `type`.
|
|
47
53
|
*/
|
|
48
54
|
export const RU_VALIDATION_MESSAGES: Readonly<Record<string, string>> = Object.freeze({
|
|
49
55
|
validationFailed: "Проверка данных не пройдена",
|
|
50
56
|
invalid: 'Поле "{property}" не прошло проверку ({code})',
|
|
51
57
|
required: 'Поле "{property}" обязательно для заполнения',
|
|
52
58
|
notEmpty: 'Поле "{property}" не должно быть пустым',
|
|
53
|
-
minLength: 'Поле "{property}"
|
|
54
|
-
maxLength: 'Поле "{property}"
|
|
55
|
-
length: 'Поле "{property}"
|
|
56
|
-
minItems: 'Поле "{property}"
|
|
57
|
-
maxItems: 'Поле "{property}"
|
|
58
|
-
itemCount: 'Поле "{property}"
|
|
59
|
+
minLength: 'Поле "{property}": минимальная длина — {min}',
|
|
60
|
+
maxLength: 'Поле "{property}": максимальная длина — {max}',
|
|
61
|
+
length: 'Поле "{property}": длина — от {min} до {max}',
|
|
62
|
+
minItems: 'Поле "{property}": минимум элементов — {min}',
|
|
63
|
+
maxItems: 'Поле "{property}": максимум элементов — {max}',
|
|
64
|
+
itemCount: 'Поле "{property}": количество элементов — от {min} до {max}',
|
|
59
65
|
contains: 'Поле "{property}" должно содержать "{contains}"',
|
|
60
66
|
notContains: 'Поле "{property}" не должно содержать "{contains}"',
|
|
61
67
|
pattern: 'Поле "{property}" не соответствует формату {pattern}',
|
|
62
68
|
email: 'Поле "{property}" должно быть корректным email-адресом',
|
|
63
69
|
url: 'Поле "{property}" должно быть корректным URL',
|
|
64
70
|
uuid: 'Поле "{property}" должно быть корректным UUID',
|
|
65
|
-
type: 'Поле "{property}"
|
|
71
|
+
type: 'Поле "{property}": ожидается {expected}, получено: {actual}',
|
|
66
72
|
min: 'Поле "{property}" должно быть не меньше {min}',
|
|
67
73
|
max: 'Поле "{property}" должно быть не больше {max}',
|
|
68
74
|
range: 'Поле "{property}" должно быть в диапазоне от {min} до {max}',
|
|
@@ -79,6 +85,15 @@ export const RU_VALIDATION_MESSAGES: Readonly<Record<string, string>> = Object.f
|
|
|
79
85
|
customError: 'Проверка поля "{property}" завершилась ошибкой: {error}',
|
|
80
86
|
asyncCustomInSyncCall:
|
|
81
87
|
'Поле "{property}": асинхронная custom-проверка не поддерживается синхронным validate(), используйте validateAsync()',
|
|
88
|
+
unknownField: 'Поле "{property}" запрещено или не объявлено в модели',
|
|
89
|
+
"typeName.string": "строка",
|
|
90
|
+
"typeName.number": "число",
|
|
91
|
+
"typeName.int": "целое число",
|
|
92
|
+
"typeName.boolean": "логическое значение",
|
|
93
|
+
"typeName.bool": "логическое значение",
|
|
94
|
+
"typeName.array": "массив",
|
|
95
|
+
"typeName.object": "объект",
|
|
96
|
+
"typeName.JSON object or array": "объект или массив",
|
|
82
97
|
});
|
|
83
98
|
|
|
84
99
|
/**
|
|
@@ -92,6 +107,8 @@ export const RU_VALIDATION_MESSAGES: Readonly<Record<string, string>> = Object.f
|
|
|
92
107
|
export class MessageRegistry {
|
|
93
108
|
/** Current global templates: a copy of the built-in ones + overrides. */
|
|
94
109
|
private static defaults: Record<string, string> = { ...BUILT_IN };
|
|
110
|
+
/** Only the templates set through {@link setDefaults}. */
|
|
111
|
+
private static overrides: Record<string, string> = {};
|
|
95
112
|
|
|
96
113
|
/**
|
|
97
114
|
* Overrides global messages for error codes.
|
|
@@ -107,6 +124,7 @@ export class MessageRegistry {
|
|
|
107
124
|
const template = messages[code];
|
|
108
125
|
if (typeof template === "string") {
|
|
109
126
|
MessageRegistry.defaults[code] = template;
|
|
127
|
+
MessageRegistry.overrides[code] = template;
|
|
110
128
|
}
|
|
111
129
|
}
|
|
112
130
|
}
|
|
@@ -114,6 +132,12 @@ export class MessageRegistry {
|
|
|
114
132
|
/** Resets all global overrides to the built-in messages. */
|
|
115
133
|
static reset(): void {
|
|
116
134
|
MessageRegistry.defaults = { ...BUILT_IN };
|
|
135
|
+
MessageRegistry.overrides = {};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** A template set through {@link setDefaults}, or `undefined`. */
|
|
139
|
+
static override(code: string): string | undefined {
|
|
140
|
+
return Object.prototype.hasOwnProperty.call(MessageRegistry.overrides, code) ? MessageRegistry.overrides[code] : undefined;
|
|
117
141
|
}
|
|
118
142
|
|
|
119
143
|
/**
|
|
@@ -137,6 +161,11 @@ export class MessageRegistry {
|
|
|
137
161
|
if (!Object.prototype.hasOwnProperty.call(params, key) || params[key] === undefined) {
|
|
138
162
|
return placeholder;
|
|
139
163
|
}
|
|
164
|
+
const value = params[key];
|
|
165
|
+
if ((key === "expected" || key === "actual") && typeof value === "string") {
|
|
166
|
+
const typeName = MessageRegistry.defaults[`typeName.${value}`];
|
|
167
|
+
if (typeName !== undefined && Object.prototype.hasOwnProperty.call(MessageRegistry.defaults, `typeName.${value}`)) return typeName;
|
|
168
|
+
}
|
|
140
169
|
return MessageRegistry.stringify(params[key]);
|
|
141
170
|
});
|
|
142
171
|
}
|
|
@@ -47,7 +47,7 @@ More scenarios (complex conditions, async, nested objects) are in
|
|
|
47
47
|
| `ValidationResult.ts` | the outcome: `isValid`, `errors`, `getErrorsFor`, `hasErrorsFor` |
|
|
48
48
|
| `RuleEngine.ts` | the engine: one pass over the plan, sync/async, nested, cycle protection |
|
|
49
49
|
| `RuleCompiler.ts` | compiles class rules into a plan + WeakMap cache (RegExp, enum Set) |
|
|
50
|
-
| `MessageRegistry.ts` | message templates, placeholders, priority local > global > built-in; built-in texts are English, `RU_VALIDATION_MESSAGES` is a Russian set for `setDefaults` |
|
|
50
|
+
| `MessageRegistry.ts` | message templates, placeholders, priority local > global > built-in; built-in texts are English, `RU_VALIDATION_MESSAGES` is a Russian set for `setDefaults` and `runApp({ language: "ru" })` |
|
|
51
51
|
| `metadata.ts` | stores rules through `context.metadata` / `Symbol.metadata` |
|
|
52
52
|
| `types/ValidatorOptions.ts` | all decorator options |
|
|
53
53
|
| `types/ValidationCode.ts` | error codes |
|
|
@@ -381,7 +381,7 @@ export class RuleEngine {
|
|
|
381
381
|
params[key] = extra[key];
|
|
382
382
|
}
|
|
383
383
|
}
|
|
384
|
-
errors.push(new ValidationError(path, value, MessageRegistry.format(code, o.message, params), code));
|
|
384
|
+
errors.push(new ValidationError(path, value, MessageRegistry.format(code, o.message, params), code, o.message === undefined ? params : undefined));
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
private static failType(errors: ValidationError[], path: string, value: unknown, o: ValidatorOptions, expected: string): void {
|
|
@@ -444,7 +444,13 @@ so a valid object pays no allocations for it.
|
|
|
444
444
|
|
|
445
445
|
Template priority: **the decorator's local `message` > global (`setDefaultMessages`) >
|
|
446
446
|
built-in**. A string returned from `custom` wins over all of them.
|
|
447
|
-
Built-in messages are English; `RU_VALIDATION_MESSAGES` is a Russian set for `setDefaultMessages`.
|
|
447
|
+
Built-in messages are English (`EN_VALIDATION_MESSAGES`); `RU_VALIDATION_MESSAGES` is a Russian set for `setDefaultMessages`.
|
|
448
|
+
An HTTP application picks the language with `runApp({ language: "ru", messages })`: each
|
|
449
|
+
`ValidationError` built from a template carries `params`, and the HTTP error handler
|
|
450
|
+
re-renders it in the application language (a rule's own `message` has no `params` and
|
|
451
|
+
stays as is). Priority there: `messages` > `setDefaultMessages` > the language set > English.
|
|
452
|
+
Russian templates put numbers after a dash ("минимальная длина — 5"), so no plural forms are
|
|
453
|
+
needed; `typeName.*` keys translate the type names in the `type` message.
|
|
448
454
|
|
|
449
455
|
```ts
|
|
450
456
|
// Globally (once at application start):
|
|
@@ -22,12 +22,16 @@ export class ValidationError {
|
|
|
22
22
|
* @param value Actual field value at check time.
|
|
23
23
|
* @param message Human-readable message (placeholders already substituted).
|
|
24
24
|
* @param code Machine-readable rule code, for example `"required"` or `"minLength"`.
|
|
25
|
+
* @param params Placeholder values when `message` comes from the template of
|
|
26
|
+
* `code`: an application re-renders it in its language. The engine leaves
|
|
27
|
+
* it out for a rule's own `message`.
|
|
25
28
|
*/
|
|
26
29
|
constructor(
|
|
27
30
|
readonly property: string,
|
|
28
31
|
readonly value: unknown,
|
|
29
32
|
readonly message: string,
|
|
30
33
|
readonly code?: string,
|
|
34
|
+
readonly params?: Readonly<Record<string, unknown>>,
|
|
31
35
|
) {}
|
|
32
36
|
|
|
33
37
|
/** Short `property: message` form, handy for logs. */
|
|
@@ -11,6 +11,6 @@ export { Validator } from "./Validator";
|
|
|
11
11
|
export { modelValidatorAdapter } from "./modelValidatorAdapter";
|
|
12
12
|
export { ValidationError } from "./ValidationError";
|
|
13
13
|
export { ValidationResult } from "./ValidationResult";
|
|
14
|
-
export { MessageRegistry, RU_VALIDATION_MESSAGES, type MessageParams } from "./MessageRegistry";
|
|
14
|
+
export { EN_VALIDATION_MESSAGES, MessageRegistry, RU_VALIDATION_MESSAGES, type MessageParams } from "./MessageRegistry";
|
|
15
15
|
export { ValidationCodes, type ValidationCode } from "./types/ValidationCode";
|
|
16
16
|
export type { CustomOutcome, CustomValidator, ValidationTypeHint, ValidatorOptions } from "./types/ValidatorOptions";
|
|
@@ -7,6 +7,7 @@ interface AdapterValidationIssue {
|
|
|
7
7
|
readonly property: string;
|
|
8
8
|
readonly message: string;
|
|
9
9
|
readonly code?: string;
|
|
10
|
+
readonly params?: Readonly<Record<string, unknown>>;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -38,14 +39,11 @@ export const modelValidatorAdapter = {
|
|
|
38
39
|
return toAdapterResult(instance, await Validator.validateAsync(instance));
|
|
39
40
|
},
|
|
40
41
|
|
|
41
|
-
/** A
|
|
42
|
-
|
|
43
|
-
return MessageRegistry.format(
|
|
42
|
+
/** A binder message in the current `MessageRegistry` language. */
|
|
43
|
+
message(code: string, params: Readonly<Record<string, unknown>>): string {
|
|
44
|
+
return MessageRegistry.format(code, undefined, params);
|
|
44
45
|
},
|
|
45
46
|
|
|
46
|
-
failureTitle(): string {
|
|
47
|
-
return MessageRegistry.format("validationFailed", undefined, {});
|
|
48
|
-
},
|
|
49
47
|
};
|
|
50
48
|
|
|
51
49
|
interface AdapterValidationResult {
|
|
@@ -59,6 +57,7 @@ function toAdapterResult(instance: object, result: ValidationResult): AdapterVal
|
|
|
59
57
|
property: error.property,
|
|
60
58
|
message: error.message,
|
|
61
59
|
code: error.code,
|
|
60
|
+
...(error.params === undefined ? {} : { params: error.params }),
|
|
62
61
|
}));
|
|
63
62
|
errors.push(...nestedInstanceErrors);
|
|
64
63
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bazis",
|
|
3
|
-
"version": "0.100.
|
|
3
|
+
"version": "0.100.8",
|
|
4
4
|
"description": "Modular backend framework for Bun in TypeScript: DI wired by codegen, HTTP, a PostgreSQL ORM, JWT, WebSocket, agents and a CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alexander Alenin",
|