@modulify/validator 0.1.0 → 0.2.1

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 (61) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +324 -107
  3. package/dist/assert.cjs +66 -0
  4. package/dist/assert.d.ts +16 -0
  5. package/dist/assert.mjs +66 -0
  6. package/dist/assertions.cjs +190 -92
  7. package/dist/assertions.d.ts +58 -2
  8. package/dist/assertions.mjs +191 -93
  9. package/dist/checkers.d.ts +8 -0
  10. package/dist/combinators.cjs +341 -0
  11. package/dist/combinators.d.ts +17 -0
  12. package/dist/combinators.mjs +341 -0
  13. package/dist/constraints.d.ts +4 -0
  14. package/dist/extractors.d.ts +2 -0
  15. package/dist/index.cjs +172 -61
  16. package/dist/index.d.ts +10 -4
  17. package/dist/index.mjs +176 -64
  18. package/dist/json-schema.cjs +514 -0
  19. package/dist/json-schema.d.ts +14 -0
  20. package/dist/json-schema.mjs +514 -0
  21. package/dist/metadata.cjs +8 -0
  22. package/dist/metadata.cjs.js +130 -0
  23. package/dist/metadata.d.ts +8 -0
  24. package/dist/metadata.es.js +131 -0
  25. package/dist/metadata.mjs +8 -0
  26. package/dist/predicates.cjs +40 -5
  27. package/dist/predicates.d.ts +25 -3
  28. package/dist/predicates.mjs +40 -5
  29. package/dist/violations.d.ts +29 -0
  30. package/docs/en/00-index.md +14 -0
  31. package/docs/en/01-shape-api.md +348 -0
  32. package/docs/en/02-metadata-and-introspection.md +276 -0
  33. package/docs/en/03-violations.md +267 -0
  34. package/docs/en/04-json-schema-export.md +264 -0
  35. package/docs/en/05-public-api.md +123 -0
  36. package/docs/en/06-common-recipes.md +273 -0
  37. package/docs/en/07-ai-reference.md +215 -0
  38. package/docs/en/08-violation-code-types.md +241 -0
  39. package/docs/ru/00-index.md +15 -0
  40. package/docs/ru/01-shape-api.md +348 -0
  41. package/docs/ru/02-metadata-and-introspection.md +276 -0
  42. package/docs/ru/03-violations.md +267 -0
  43. package/docs/ru/04-json-schema-export.md +264 -0
  44. package/docs/ru/05-public-api.md +123 -0
  45. package/docs/ru/06-common-recipes.md +273 -0
  46. package/docs/ru/07-ai-reference.md +215 -0
  47. package/docs/ru/08-violation-code-types.md +241 -0
  48. package/docs/ru/README.md +371 -0
  49. package/package.json +51 -33
  50. package/types/index.d.ts +789 -30
  51. package/types/json-schema.d.ts +75 -0
  52. package/dist/assertions/Assert.d.ts +0 -2
  53. package/dist/assertions/HasLength.d.ts +0 -7
  54. package/dist/assertions/check.d.ts +0 -3
  55. package/dist/assertions/index.d.ts +0 -16
  56. package/dist/runners/Each.d.ts +0 -3
  57. package/dist/runners/HasProperties.d.ts +0 -6
  58. package/dist/runners/index.d.ts +0 -2
  59. package/dist/runners.cjs +0 -32
  60. package/dist/runners.d.ts +0 -2
  61. package/dist/runners.mjs +0 -32
@@ -0,0 +1,215 @@
1
+ # Справка для AI
2
+
3
+ [Оглавление документации](./00-index.md)
4
+ [Английская версия](../en/07-ai-reference.md)
5
+
6
+ Эта страница — компактное описание контракта для AI agents, code generators, IDE tools и людей, которым нужен максимально короткий и однозначный набор правил.
7
+
8
+ Её стоит воспринимать как каноническую краткую справку поверх более подробных руководств.
9
+
10
+ ## Базовая модель
11
+
12
+ `@modulify/validator` организован вокруг трёх основных слоёв:
13
+
14
+ - predicates: проверки во время выполнения и type guards;
15
+ - assertions: машиночитаемые ошибки leaf-уровня;
16
+ - combinators: структурная композиция assertions и validators.
17
+
18
+ Библиотека не строится вокруг встроенных человекочитаемых сообщений.
19
+
20
+ Violations — это прежде всего структурированные данные.
21
+
22
+ ## Канонические точки входа
23
+
24
+ Используйте корневой пакет для:
25
+
26
+ - `validate`
27
+ - `validate.sync`
28
+ - `matches.sync`
29
+ - `meta`
30
+ - `describe`
31
+ - `custom`
32
+ - `collection`
33
+ - built-in assertions
34
+ - combinators
35
+
36
+ Используйте `@modulify/validator/predicates` для самостоятельных проверок в стиле guard.
37
+
38
+ Используйте `@modulify/validator/json-schema` для:
39
+
40
+ - `toJsonSchema(...)`
41
+ - `JsonSchemaExportError`
42
+
43
+ ## Контракт результата валидации
44
+
45
+ ```typescript
46
+ type ValidationTuple<T> =
47
+ | [ok: true, validated: T, violations: []]
48
+ | [ok: false, validated: unknown, violations: Violation[]]
49
+ ```
50
+
51
+ Важные следствия:
52
+
53
+ - `validate(...)` сужает `validated` в успешной ветке;
54
+ - `validate(...)` не сужает исходную входную переменную;
55
+ - `matches.sync(...)` — это API для сужения исходной переменной;
56
+ - `violations` при успехе всегда пуст.
57
+
58
+ ## Семантика wrappers
59
+
60
+ - `optional(x)` принимает `undefined`
61
+ - `nullable(x)` принимает `null`
62
+ - `nullish(x)` принимает `null | undefined`
63
+
64
+ Эти wrappers моделируют допустимые значения во время выполнения, а не формулировки для UI.
65
+
66
+ ## Семантика shape
67
+
68
+ `shape(...)` валидирует обычные record-like objects.
69
+
70
+ По умолчанию:
71
+
72
+ - unknown keys разрешены;
73
+ - unknown-key mode равен `'passthrough'`;
74
+ - object-level rules отсутствуют.
75
+
76
+ Переключатели режима:
77
+
78
+ - `.strict()` сохраняет те же поля и rules, но отклоняет unknown keys;
79
+ - `.passthrough()` сохраняет те же поля и rules, но разрешает unknown keys.
80
+
81
+ Структурные derivations:
82
+
83
+ - `.pick(...)`
84
+ - `.omit(...)`
85
+ - `.partial(...)`
86
+ - `.extend(...)`
87
+ - `.merge(...)`
88
+
89
+ Важное правило:
90
+
91
+ - structural derivations намеренно сбрасывают object-level rules;
92
+ - mode switches намеренно сохраняют object-level rules.
93
+
94
+ ## Контракт violations
95
+
96
+ Violations — это машиночитаемые объекты с:
97
+
98
+ - значением, на котором произошла ошибка;
99
+ - path;
100
+ - semantic subject в `violates`.
101
+
102
+ Не стройте дальнейшую обработку на разборе текста.
103
+
104
+ Предпочитайте:
105
+
106
+ - `violations`
107
+ - `collection(...)`
108
+ - поиск по path
109
+
110
+ Вместо:
111
+
112
+ - сопоставления строк;
113
+ - разового разбора сообщений;
114
+ - извлечения имён полей из текстов.
115
+
116
+ ## Контракт metadata
117
+
118
+ `meta(...)` добавляет непрозрачные машиночитаемые метаданные к любому constraint.
119
+
120
+ `describe(...)` возвращает стабильное рекурсивное дерево descriptors.
121
+
122
+ Custom validators могут участвовать в этом контракте через `describe()`.
123
+
124
+ Без `describe()` custom validator намеренно остаётся непрозрачным и обычно выглядит так:
125
+
126
+ ```typescript
127
+ { kind: 'validator' }
128
+ ```
129
+
130
+ ## Контракт JSON Schema
131
+
132
+ `toJsonSchema(...)` — это производный слой для interoperability.
133
+
134
+ Это не источник runtime truth.
135
+
136
+ Best-effort режим:
137
+
138
+ - режим по умолчанию;
139
+ - unsupported nodes превращаются в permissive `{}` schemas;
140
+ - unsupported shape rules могут быть отброшены с `$comment`.
141
+
142
+ Strict режим:
143
+
144
+ - выбрасывает `JsonSchemaExportError`;
145
+ - содержит `descriptor`, `reason` и `path`.
146
+
147
+ Важное несоответствие:
148
+
149
+ - JSON Schema `required` — это лишь приближение runtime semantics вокруг `undefined`;
150
+ - не следует ожидать полного семантического совпадения между runtime validation и экспортированной JSON Schema.
151
+
152
+ ## Канонические шаблоны
153
+
154
+ Используйте это, когда нужно валидировать payload:
155
+
156
+ ```typescript
157
+ const [ok, validated, violations] = validate.sync(input, schema)
158
+ ```
159
+
160
+ Используйте это, когда нужно сузить исходную переменную:
161
+
162
+ ```typescript
163
+ if (matches.sync(value, schema)) {
164
+ // здесь value уже сужено
165
+ }
166
+ ```
167
+
168
+ Используйте это, когда нужны переиспользуемые object schemas:
169
+
170
+ ```typescript
171
+ const schema = shape({...}).strict()
172
+ const partial = schema.partial()
173
+ const subset = schema.pick([...])
174
+ ```
175
+
176
+ Используйте это, когда другому слою нужна машиночитаемая introspection:
177
+
178
+ ```typescript
179
+ const descriptor = describe(schema)
180
+ ```
181
+
182
+ Используйте это, когда внешней системе нужно представление для экспорта:
183
+
184
+ ```typescript
185
+ const jsonSchema = toJsonSchema(schema)
186
+ ```
187
+
188
+ ## Стоит / Не стоит
189
+
190
+ Стоит:
191
+
192
+ - держите leaf constraints маленькими и хорошо сочетаемыми друг с другом;
193
+ - относитесь к `violations` как к данным, а не как к сообщениям;
194
+ - используйте `shape(...)` для переиспользуемых object contracts;
195
+ - добавляйте `describe()` к custom validators, которые должны участвовать в инструментах;
196
+ - используйте strict JSON Schema export только тогда, когда экспорт с потерями неприемлем.
197
+
198
+ Не стоит:
199
+
200
+ - ожидать, что `validate(...)` сузит исходную входную переменную;
201
+ - считать `toJsonSchema(...)` полным зеркалом runtime semantics;
202
+ - считать, что structural derivations shape сохраняют object-level rules;
203
+ - строить инструменты на приватных internals вместо `describe(...)`;
204
+ - строить дальнейшую логику вокруг человекочитаемых строк.
205
+
206
+ ## Лучшие источники истины
207
+
208
+ Для деталей реализации и edge cases предпочтителен такой порядок:
209
+
210
+ 1. `README.md`
211
+ 2. `docs/ru/*.md` или `docs/en/*.md`
212
+ 3. `tests/*.test.ts`
213
+ 4. `tests/*.test-d.ts`
214
+
215
+ Тесты — самый точный источник поведения там, где проза может оставлять пространство для неверной интерпретации.
@@ -0,0 +1,241 @@
1
+ # Типы кодов нарушений
2
+
3
+ [Индекс документации](./00-index.md)
4
+ [English version](../en/08-violation-code-types.md)
5
+
6
+ `@modulify/validator` теперь даёт два связанных слоя для машиночитаемых кодов:
7
+
8
+ - точные literal-коды в assertion descriptors и structured violations;
9
+ - расширяемый глобальный реестр, из которого можно получить project-wide union известных кодов.
10
+
11
+ В этом руководстве разобрано, когда полезен каждый из слоёв, как они сочетаются и как безопасно расширять их в приложении.
12
+
13
+ ## Быстрый старт
14
+
15
+ Основные публичные точки входа:
16
+
17
+ - `ViolationCodeEntry` - компактная контрактная запись для известного кода;
18
+ - `ViolationCodeRegistry` - интерфейс-реестр известных кодов;
19
+ - `ViolationCode` - union, извлекаемый из `keyof ViolationCodeRegistry`;
20
+ - `ViolationArgs<C>`, `ViolationKindOf<C>` и `ViolationNameOf<C>` - code-driven utility types.
21
+
22
+ Пакет уже содержит built-in ключи для собственных violations, например:
23
+
24
+ - `'type.string'`
25
+ - `'length.min'`
26
+ - `'shape.unknown-key'`
27
+ - `'runtime.rejection'`
28
+
29
+ Поэтому такой код работает сразу:
30
+
31
+ ```typescript
32
+ import type { ViolationCode } from '@modulify/validator'
33
+
34
+ const code: ViolationCode = 'type.string'
35
+ ```
36
+
37
+ ## Точные коды из `describe(...)`
38
+
39
+ Built-in assertions теперь сохраняют точные literal-коды в интроспекции.
40
+
41
+ ```typescript
42
+ import {
43
+ describe,
44
+ hasLength,
45
+ isString,
46
+ } from '@modulify/validator'
47
+
48
+ const stringDescriptor = describe(isString)
49
+ const lengthDescriptor = describe(hasLength({ min: 3 }))
50
+ ```
51
+
52
+ С точки зрения TypeScript это значит:
53
+
54
+ - `stringDescriptor.code` имеет тип `'type.string'`;
55
+ - `stringDescriptor.args` имеет тип `[]`;
56
+ - `lengthDescriptor.code` имеет тип `'length.unsupported-type'`;
57
+ - `lengthDescriptor.constraints[number].code` имеет конкретный union length-кодов вместо обычного `string`.
58
+
59
+ Это удобно для адаптеров и tooling-кода, который читает descriptors и хочет ветвиться по коду без ручных cast.
60
+
61
+ ## Зачем нужен глобальный реестр
62
+
63
+ Точные literals на отдельных значениях полезны для локальной интроспекции.
64
+
65
+ Глобальный реестр решает другую задачу: позволяет получить один переиспользуемый union для всего приложения.
66
+
67
+ ```typescript
68
+ import type { ViolationCode } from '@modulify/validator'
69
+
70
+ type AppViolationCode = ViolationCode
71
+ ```
72
+
73
+ Такой union удобно использовать в:
74
+
75
+ - словарях сообщений;
76
+ - контрактах аналитики;
77
+ - API envelopes с ошибками;
78
+ - UI-мапперах состояния ошибок;
79
+ - общих helper utilities.
80
+
81
+ ## Расширение `ViolationCodeRegistry`
82
+
83
+ Реестр рассчитан на module augmentation и теперь хранит небольшие контрактные записи по коду.
84
+
85
+ ```typescript
86
+ import type { ViolationCodeEntry } from '@modulify/validator'
87
+ import '@modulify/validator'
88
+
89
+ declare module '@modulify/validator' {
90
+ interface ViolationCodeRegistry {
91
+ 'user.email.taken': ViolationCodeEntry<'validator', 'user', readonly []>;
92
+ 'profile.password.mismatch': ViolationCodeEntry<'validator', 'shape', readonly []>;
93
+ }
94
+ }
95
+ ```
96
+
97
+ После этого:
98
+
99
+ ```typescript
100
+ import type { ViolationCode } from '@modulify/validator'
101
+
102
+ const codeA: ViolationCode = 'user.email.taken'
103
+ const codeB: ViolationCode = 'profile.password.mismatch'
104
+ ```
105
+
106
+ Так можно один раз объявить project-specific коды и потом использовать извлечённый union во всех остальных слоях.
107
+
108
+ Если в проекте ещё остались старые augmentation-записи с `never`, они по-прежнему будут попадать в `ViolationCode`, но для `kind` / `name` / `args` останется generic fallback, пока вы не переведёте их на `ViolationCodeEntry`.
109
+
110
+ ## Производные типы от кода
111
+
112
+ После регистрации кода с контрактной записью он становится ключом к связанным типам.
113
+
114
+ ```typescript
115
+ import type {
116
+ ViolationArgs,
117
+ ViolationKindOf,
118
+ ViolationNameOf,
119
+ ViolationSubject,
120
+ } from '@modulify/validator'
121
+
122
+ type PasswordArgs = ViolationArgs<'profile.password.mismatch'>
123
+ type PasswordKind = ViolationKindOf<'profile.password.mismatch'>
124
+ type PasswordName = ViolationNameOf<'profile.password.mismatch'>
125
+ type PasswordSubject = ViolationSubject<'profile.password.mismatch'>
126
+ ```
127
+
128
+ То есть:
129
+
130
+ - `PasswordArgs` становится `readonly []`;
131
+ - `PasswordKind` становится `'validator'`;
132
+ - `PasswordName` становится `'shape'`;
133
+ - `PasswordSubject` автоматически получает согласованные `kind`, `name`, `code` и `args`.
134
+
135
+ ## Использование расширенных кодов в custom assertions
136
+
137
+ Custom assertions могут хранить свои собственные явные literal-коды.
138
+
139
+ ```typescript
140
+ import { assert } from '@modulify/validator/assertions'
141
+
142
+ const isAvailableEmail = assert(
143
+ (value: unknown): value is string => typeof value === 'string' && value.includes('@'),
144
+ {
145
+ name: 'isAvailableEmail',
146
+ bail: true,
147
+ code: 'user.email.taken',
148
+ }
149
+ )
150
+ ```
151
+
152
+ Тогда `describe(isAvailableEmail).code` будет иметь тип `'user.email.taken'`.
153
+
154
+ Эта часть не зависит от глобального union. Literal сохраняется прямо из определения assertion.
155
+
156
+ ## Использование расширенных кодов в shape refinements
157
+
158
+ Та же идея работает и для object-level refinement issues.
159
+
160
+ ```typescript
161
+ import type { ObjectShapeRefinementIssue } from '@modulify/validator'
162
+ import {
163
+ isEmail,
164
+ isString,
165
+ shape,
166
+ } from '@modulify/validator'
167
+
168
+ const signUpForm = shape({
169
+ email: [isString, isEmail],
170
+ password: isString,
171
+ confirmation: shape({
172
+ password: isString,
173
+ }),
174
+ }).refine(value => {
175
+ if (value.password === value.confirmation.password) {
176
+ return []
177
+ }
178
+
179
+ return [{
180
+ path: ['confirmation', 'password'],
181
+ code: 'profile.password.mismatch',
182
+ args: [],
183
+ }] satisfies ObjectShapeRefinementIssue<'profile.password.mismatch'>
184
+ })
185
+ ```
186
+
187
+ Так код refinement остаётся согласованным с тем же реестром, из которого вы строите общий union.
188
+
189
+ ## Практический паттерн для app-level мапперов
190
+
191
+ Часто поверх codes хочется сделать небольшой слой, который отвечает уже за рендеринг или транспорт.
192
+
193
+ ```typescript
194
+ import type {
195
+ Violation,
196
+ ViolationCode,
197
+ } from '@modulify/validator'
198
+
199
+ const labels: Partial<Record<ViolationCode, string>> = {
200
+ 'type.string': 'Expected a string',
201
+ 'length.min': 'Value is too short',
202
+ 'user.email.taken': 'Email is already taken',
203
+ }
204
+
205
+ function toLabel(violation: Violation) {
206
+ return labels[violation.violates.code as ViolationCode] ?? violation.violates.code
207
+ }
208
+ ```
209
+
210
+ Необязательно превращать все возможные коды в один огромный исчерпывающий словарь. На практике `Partial<Record<ViolationCode, ...>>` часто самый удобный вариант.
211
+
212
+ ## Built-In union и сохранение явных literals
213
+
214
+ Эти два механизма дополняют друг друга:
215
+
216
+ - built-in и augmented коды попадают в переиспользуемый union `ViolationCode`;
217
+ - явные custom literals сохраняются прямо в местах создания значения, например в `assert(...)` или typed refinement issues.
218
+
219
+ Это важное различие.
220
+
221
+ Если вы определили custom literal, но ещё не аугментировали `ViolationCodeRegistry`:
222
+
223
+ - локальные descriptor и violation значения всё равно могут нести точный literal;
224
+ - глобальный union `ViolationCode` пока не будет его содержать.
225
+
226
+ Если вы аугментировали `ViolationCodeRegistry` через `never`, а не через `ViolationCodeEntry`:
227
+
228
+ - код попадёт в глобальный union `ViolationCode`;
229
+ - для него всё ещё будет использоваться generic fallback по `kind`, `name` и `args`.
230
+
231
+ Обычно удобно делать так:
232
+
233
+ 1. объявить custom code там, где он создаётся;
234
+ 2. добавить его в `ViolationCodeRegistry`;
235
+ 3. использовать `ViolationCode` в адаптерах и app-level helper types.
236
+
237
+ ## Связанные разделы
238
+
239
+ - [Метаданные и интроспекция](./02-metadata-and-introspection.md)
240
+ - [Нарушения](./03-violations.md)
241
+ - [Публичный API](./05-public-api.md)