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.
Files changed (34) hide show
  1. package/README.md +12 -7
  2. package/core/app/index.ts +1 -0
  3. package/core/app/messages.ts +44 -0
  4. package/core/app/runApp.ts +20 -0
  5. package/core/di/errors/InvalidProviderError.ts +3 -2
  6. package/core/di/errors/ScopedServiceFromRootError.ts +2 -1
  7. package/core/di/internal/GraphValidator.ts +6 -2
  8. package/core/di/module/createContainer.ts +18 -1
  9. package/core/http/Binding/ParameterBinder.ts +3 -3
  10. package/core/http/Binding/convert.ts +2 -2
  11. package/core/http/Binding/modelBinder.ts +17 -18
  12. package/core/http/Binding/modelValidator.ts +12 -4
  13. package/core/http/Errors/HttpError.ts +42 -20
  14. package/core/http/Errors/messages.ts +158 -0
  15. package/core/http/HttpContext/HttpContext.ts +3 -3
  16. package/core/http/HttpServer.ts +21 -9
  17. package/core/http/Middleware/errorHandler.ts +39 -13
  18. package/core/http/index.ts +1 -0
  19. package/core/http/options.ts +7 -0
  20. package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
  21. package/core/kernel/Bazis.ts +9 -1
  22. package/core/kernel/LifecycleCoordinator.ts +10 -1
  23. package/core/kernel/config/defineConfig.ts +5 -1
  24. package/core/kernel/startupFailure.ts +4 -2
  25. package/library/http-client/HttpClient.ts +7 -0
  26. package/library/http-client/configKeys.ts +36 -0
  27. package/library/validation/MessageRegistry.ts +39 -10
  28. package/library/validation/README.md +1 -1
  29. package/library/validation/RuleEngine.ts +1 -1
  30. package/library/validation/SPEC.md +7 -1
  31. package/library/validation/ValidationError.ts +4 -0
  32. package/library/validation/index.ts +1 -1
  33. package/library/validation/modelValidatorAdapter.ts +5 -6
  34. 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 BUILT_IN: Readonly<Record<string, string>> = {
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. Apply once at startup:
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}" должно содержать не менее {min} символов',
54
- maxLength: 'Поле "{property}" должно содержать не более {max} символов',
55
- length: 'Поле "{property}" должно содержать от {min} до {max} символов',
56
- minItems: 'Поле "{property}" должно содержать не менее {min} элементов',
57
- maxItems: 'Поле "{property}" должно содержать не более {max} элементов',
58
- itemCount: 'Поле "{property}" должно содержать от {min} до {max} элементов',
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}" должно иметь тип {expected}, получено: {actual}',
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 JSON value of the wrong type, in the current `MessageRegistry` language. */
42
- typeMismatchMessage(property: string, expected: string, actual: string): string {
43
- return MessageRegistry.format("type", undefined, { property, expected, actual });
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.6",
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",