@modulify/validator 0.2.1 → 0.3.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/CHANGELOG.md +27 -1
- package/README.md +12 -2
- package/dist/assert-BDOtHdLx.cjs +289 -0
- package/dist/assert-CzMg5aO7.mjs +206 -0
- package/dist/assert.cjs +7 -65
- package/dist/assert.d.cts +37 -0
- package/dist/assert.d.mts +37 -0
- package/dist/assert.d.ts +24 -3
- package/dist/assert.mjs +2 -66
- package/dist/assertions-DPYCHREw.mjs +297 -0
- package/dist/assertions-nfOKqcjs.cjs +476 -0
- package/dist/assertions.cjs +34 -206
- package/dist/assertions.d.cts +56 -0
- package/dist/assertions.d.mts +56 -0
- package/dist/assertions.d.ts +43 -45
- package/dist/assertions.mjs +3 -207
- package/dist/checkers.d.cts +8 -0
- package/dist/checkers.d.mts +8 -0
- package/dist/checkers.d.ts +1 -1
- package/dist/combinators.cjs +303 -304
- package/dist/combinators.d.cts +16 -0
- package/dist/combinators.d.mts +16 -0
- package/dist/combinators.d.ts +15 -16
- package/dist/combinators.mjs +304 -315
- package/dist/constraints.d.cts +4 -0
- package/dist/constraints.d.mts +4 -0
- package/dist/constraints.d.ts +2 -2
- package/dist/extractors.d.cts +2 -0
- package/dist/extractors.d.mts +2 -0
- package/dist/index.cjs +210 -213
- package/dist/index.d.cts +12 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.mjs +165 -218
- package/dist/json-schema.cjs +364 -489
- package/dist/json-schema.d.cts +14 -0
- package/dist/json-schema.d.mts +14 -0
- package/dist/json-schema.d.ts +3 -3
- package/dist/json-schema.mjs +365 -492
- package/dist/metadata.cjs +6 -7
- package/dist/metadata.d.cts +8 -0
- package/dist/metadata.d.mts +8 -0
- package/dist/metadata.d.ts +2 -2
- package/dist/metadata.mjs +2 -8
- package/dist/predicates.cjs +98 -41
- package/dist/predicates.d.cts +77 -0
- package/dist/predicates.d.mts +77 -0
- package/dist/predicates.d.ts +25 -4
- package/dist/predicates.mjs +92 -66
- package/dist/types/index.d.cts +984 -0
- package/dist/types/index.d.mts +984 -0
- package/dist/types/index.d.ts +984 -0
- package/dist/types/json-schema.d.cts +75 -0
- package/dist/types/json-schema.d.mts +75 -0
- package/dist/types/json-schema.d.ts +75 -0
- package/dist/violations.d.cts +29 -0
- package/dist/violations.d.mts +29 -0
- package/dist/violations.d.ts +1 -1
- package/docs/RELEASING.md +66 -0
- package/docs/en/00-index.md +2 -0
- package/docs/en/01-shape-api.md +35 -10
- package/docs/en/02-metadata-and-introspection.md +2 -1
- package/docs/en/03-violations.md +1 -1
- package/docs/en/04-json-schema-export.md +5 -0
- package/docs/en/05-public-api.md +35 -3
- package/docs/en/06-common-recipes.md +2 -1
- package/docs/en/07-ai-reference.md +5 -2
- package/docs/en/08-violation-code-types.md +28 -2
- package/docs/en/09-migration.md +75 -0
- package/docs/ru/00-index.md +2 -0
- package/docs/ru/01-shape-api.md +35 -10
- package/docs/ru/02-metadata-and-introspection.md +2 -1
- package/docs/ru/03-violations.md +1 -1
- package/docs/ru/04-json-schema-export.md +5 -0
- package/docs/ru/05-public-api.md +35 -3
- package/docs/ru/06-common-recipes.md +2 -1
- package/docs/ru/07-ai-reference.md +5 -2
- package/docs/ru/08-violation-code-types.md +28 -2
- package/docs/ru/09-migration.md +76 -0
- package/docs/ru/README.md +10 -2
- package/package.json +63 -37
- package/types/index.d.ts +275 -115
- package/dist/metadata.cjs.js +0 -130
- package/dist/metadata.es.js +0 -131
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Migrating From 0.2.1
|
|
2
|
+
|
|
3
|
+
[Documentation index](./00-index.md)
|
|
4
|
+
[Russian translation](../ru/09-migration.md)
|
|
5
|
+
|
|
6
|
+
This guide describes the unreleased changes after `0.2.1`.
|
|
7
|
+
|
|
8
|
+
## Object-Level Refinements
|
|
9
|
+
|
|
10
|
+
`shape(...).refine(...)` is async-first even when its callback returns a plain value.
|
|
11
|
+
Use `shape(...).refine.sync(...)` for rules used by `validate.sync(...)`,
|
|
12
|
+
`matches.sync(...)`, or `shape.check(...)`.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const profile = shape({ password: isString, confirmation: isString })
|
|
16
|
+
.refine.sync(value => value.password === value.confirmation ? null : {
|
|
17
|
+
code: 'profile.password.mismatch',
|
|
18
|
+
})
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Use `.refine(...)` with `await validate(...)` for asynchronous rules.
|
|
22
|
+
|
|
23
|
+
## Staged Assertions
|
|
24
|
+
|
|
25
|
+
Guard assertions establish a domain; refinements check properties inside it:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
validate.sync('name', [isString, hasLength({ min: 3 })])
|
|
29
|
+
validate.sync(4, [isInteger, multipleOf(2)])
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Replace standalone refinements such as `validate.sync(value, hasLength(...))`
|
|
33
|
+
with a compatible guard followed by the refinement. Incompatible combinations,
|
|
34
|
+
such as `[isNumber, hasLength(...)]`, are rejected by TypeScript.
|
|
35
|
+
|
|
36
|
+
Structural validators reset the assertion stage. To refine the outer array after
|
|
37
|
+
checking its elements, use `[each(isString), isDefined, hasLength({ min: 2 })]`.
|
|
38
|
+
|
|
39
|
+
## Public Type Names
|
|
40
|
+
|
|
41
|
+
The old names have no compatibility aliases. Update imports and annotations:
|
|
42
|
+
|
|
43
|
+
| Old name | Current name |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `ValidationTuple` | `ValidationResult` |
|
|
46
|
+
| `InferMaybeManyViolations` | `InferViolations` |
|
|
47
|
+
| `ObjectDescriptor` | `ShapeDescriptor` |
|
|
48
|
+
| `InferObjectDescriptor` | `InferShape` |
|
|
49
|
+
| `PartialObjectDescriptor` | `PartialShapeDescriptor` |
|
|
50
|
+
| `MergeObjectDescriptors` | `MergeShapeDescriptors` |
|
|
51
|
+
| `ObjectShapeFieldSelector` | `ShapeFieldSelector` |
|
|
52
|
+
| `ObjectShapeRefinement` / `ObjectShapeAsyncRefinement` | `ShapeRefinement` |
|
|
53
|
+
| `ObjectShapeRefinementSync` / `ObjectShapeSyncRefinement` | `SyncShapeRefinement` |
|
|
54
|
+
| `ObjectShapeRefinementIssue` | `ShapeRefinementViolationInput` |
|
|
55
|
+
| `ObjectShapeRefineMethod` | `ShapeRefineMethod` |
|
|
56
|
+
| `ObjectShapeRefineMethodSync` | `ShapeRefineMethodSync` |
|
|
57
|
+
| `DescribeMaybeMany` | `DescribeConstraints` |
|
|
58
|
+
| `DescribeObjectDescriptor` | `DescribeShapeDescriptor` |
|
|
59
|
+
| `AssertionDescriptorConstraint` | `AssertionConstraintDescriptor` |
|
|
60
|
+
| `ConstraintDescriptorBase` | `BaseConstraintDescriptor` |
|
|
61
|
+
| `ValidatorDescriptor` | `OpaqueValidatorDescriptor` |
|
|
62
|
+
| `GenericObjectShapeRuleDescriptor` | `SyncObjectShapeRuleDescriptor` |
|
|
63
|
+
|
|
64
|
+
## Optional Predicate Fields
|
|
65
|
+
|
|
66
|
+
`isShape({ name: [isString, false] })` permits an absent `name`, but rejects
|
|
67
|
+
`{ name: 2 }` and `{ name: undefined }`. To allow explicit `undefined`, use
|
|
68
|
+
`[Or(isString, isUndefined), false]`. Required fields must be present even when
|
|
69
|
+
their predicate accepts `undefined`.
|
|
70
|
+
|
|
71
|
+
## Package Consumers
|
|
72
|
+
|
|
73
|
+
The public root and subpath exports support ESM, CommonJS, and strict TypeScript
|
|
74
|
+
with NodeNext or Bundler resolution. Import from package entrypoints rather than
|
|
75
|
+
internal files in `dist/`.
|
package/docs/ru/00-index.md
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
- [Справка для AI](./07-ai-reference.md) - Краткое описание контракта для AI agents, инструментов и быстрого поиска стабильной семантики библиотеки.
|
|
11
11
|
- [Типы кодов нарушений](./08-violation-code-types.md) - Подробное руководство по `ViolationCodeRegistry`, `ViolationCode`, сохранению literal-кодов в descriptors и внешнему расширению реестра.
|
|
12
12
|
|
|
13
|
+
- [Миграция с 0.2.1](./09-migration.md) - Breaking changes, переименованные типы и обновлённые контракты валидации.
|
|
14
|
+
|
|
13
15
|
## Переводы
|
|
14
16
|
|
|
15
17
|
- [English](../en/00-index.md)
|
package/docs/ru/01-shape-api.md
CHANGED
|
@@ -49,6 +49,8 @@ Shape по-прежнему остаётся validator-ом. Дополните
|
|
|
49
49
|
- массив constraint-ов, которые выполняются последовательно;
|
|
50
50
|
- другой structural validator вроде `shape(...)`, `each(...)`, `tuple(...)`, `record(...)`, `union(...)` или `discriminatedUnion(...)`.
|
|
51
51
|
|
|
52
|
+
Для массивов assertions эта последовательность теперь stage-aware: сначала guard assertions задают домен поля, а refinement assertions обязаны быть совместимы с этим доменом.
|
|
53
|
+
|
|
52
54
|
Поэтому object validation остаётся согласованной с остальной библиотекой:
|
|
53
55
|
|
|
54
56
|
- field-level checks переиспользуют те же assertions и combinators;
|
|
@@ -79,6 +81,11 @@ Runtime behavior:
|
|
|
79
81
|
- вложенные violations возвращаются на пути поля;
|
|
80
82
|
- unknown keys по умолчанию разрешены.
|
|
81
83
|
|
|
84
|
+
Type-level behavior:
|
|
85
|
+
|
|
86
|
+
- `[isString, hasLength({ min: 8 })]` типизируется корректно;
|
|
87
|
+
- `[isNumber, hasLength({ min: 8 })]` TypeScript отсекает ещё до runtime.
|
|
88
|
+
|
|
82
89
|
## Неизвестные ключи
|
|
83
90
|
|
|
84
91
|
У shape есть два режима работы с неизвестными ключами:
|
|
@@ -190,13 +197,36 @@ Shapes позволяют выражать cross-field invariants без вне
|
|
|
190
197
|
|
|
191
198
|
### `refine(...)`
|
|
192
199
|
|
|
193
|
-
`refine(...)` добавляет
|
|
200
|
+
`refine(...)` добавляет async-first object-level rule, которое запускается только после того, как базовая shape уже успешно провалидировалась как объект.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const registration = shape({
|
|
204
|
+
email: isString,
|
|
205
|
+
}).refine(async value => {
|
|
206
|
+
const taken = await users.has(value.email)
|
|
207
|
+
|
|
208
|
+
return taken
|
|
209
|
+
? [{ path: ['email'], code: 'user.email.taken' }]
|
|
210
|
+
: []
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`refine(...)` специально остаётся тонким:
|
|
215
|
+
|
|
216
|
+
- он принимает sync- и async-callbacks;
|
|
217
|
+
- при успехе возвращает `[]`, `null` или `undefined`;
|
|
218
|
+
- при ошибке возвращает один issue или массив issue;
|
|
219
|
+
- `path` задаётся относительно текущей shape и по умолчанию равен `[]`;
|
|
220
|
+
- `value` необязателен и по умолчанию берётся из значения объекта по этому относительному пути;
|
|
221
|
+
- `code` остаётся machine-readable.
|
|
222
|
+
|
|
223
|
+
Если нужен явно sync-safe object rule для `validate.sync(...)`, используйте `refine.sync(...)`:
|
|
194
224
|
|
|
195
225
|
```typescript
|
|
196
226
|
const registration = shape({
|
|
197
227
|
password: isString,
|
|
198
228
|
confirmPassword: isString,
|
|
199
|
-
}).refine(value => {
|
|
229
|
+
}).refine.sync(value => {
|
|
200
230
|
return value.password === value.confirmPassword
|
|
201
231
|
? []
|
|
202
232
|
: [{
|
|
@@ -207,14 +237,7 @@ const registration = shape({
|
|
|
207
237
|
})
|
|
208
238
|
```
|
|
209
239
|
|
|
210
|
-
`
|
|
211
|
-
|
|
212
|
-
- он только sync;
|
|
213
|
-
- при успехе возвращает `[]`, `null` или `undefined`;
|
|
214
|
-
- при ошибке возвращает один issue или массив issue;
|
|
215
|
-
- `path` задаётся относительно текущей shape и по умолчанию равен `[]`;
|
|
216
|
-
- `value` необязателен и по умолчанию берётся из значения объекта по этому относительному пути;
|
|
217
|
-
- `code` остаётся machine-readable.
|
|
240
|
+
`validate.sync(...)`, `matches.sync(...)` и `shape.check(...)` выбрасывают явную ошибку, если встречают async-callback в `refine(...)`.
|
|
218
241
|
|
|
219
242
|
Сгенерированные violations используют:
|
|
220
243
|
|
|
@@ -243,6 +266,8 @@ const registration = shape({
|
|
|
243
266
|
|
|
244
267
|
Позже это попадает в `describe(...)` в массив `rules`.
|
|
245
268
|
|
|
269
|
+
Rules, зарегистрированные через async-first `refine(...)`, получают в этом массиве признак `async: true`.
|
|
270
|
+
|
|
246
271
|
### `fieldsMatch(...)`
|
|
247
272
|
|
|
248
273
|
`fieldsMatch(...)` — небольшой helper для частого случая с полем подтверждения.
|
|
@@ -182,7 +182,8 @@ const descriptor = describe(shape({
|
|
|
182
182
|
Built-in примеры:
|
|
183
183
|
|
|
184
184
|
- `fieldsMatch(...)` создаёт компактный `fieldsMatch` rule descriptor;
|
|
185
|
-
- `refine(...)` может принимать собственный компактный rule descriptor object
|
|
185
|
+
- `refine(...)` может принимать собственный компактный rule descriptor object;
|
|
186
|
+
- async-first rules из `refine(...)` помечаются через `async: true`.
|
|
186
187
|
|
|
187
188
|
Так публичное descriptor tree остаётся стабильным и достаточно сериализуемым для tooling, при этом библиотека не пытается сериализовать произвольные callbacks.
|
|
188
189
|
|
package/docs/ru/03-violations.md
CHANGED
|
@@ -120,7 +120,7 @@ const violation: Violation = {
|
|
|
120
120
|
`validate(...)` и `validate.sync(...)` возвращают:
|
|
121
121
|
|
|
122
122
|
```typescript
|
|
123
|
-
type
|
|
123
|
+
type ValidationResult<T> =
|
|
124
124
|
| [ok: true, validated: T, violations: []]
|
|
125
125
|
| [ok: false, validated: unknown, violations: Violation[]]
|
|
126
126
|
```
|
|
@@ -69,6 +69,9 @@ Exporter покрывает built-in descriptor set, который уже уч
|
|
|
69
69
|
|
|
70
70
|
- `isString` -> `type: 'string'`
|
|
71
71
|
- `isNumber` -> `type: 'number'`
|
|
72
|
+
- `isFiniteNumber` -> `type: 'number'`
|
|
73
|
+
- `isInteger` -> `type: 'integer'`
|
|
74
|
+
- `isSafeInteger` -> `type: 'integer'`, `minimum: Number.MIN_SAFE_INTEGER`, `maximum: Number.MAX_SAFE_INTEGER`
|
|
72
75
|
- `isBoolean` -> `type: 'boolean'`
|
|
73
76
|
- `isNull` -> `type: 'null'`
|
|
74
77
|
- `isEmail` -> `type: 'string'` и `format: 'email'`
|
|
@@ -202,6 +205,8 @@ Strict режим полезен, когда silent fallback был бы вво
|
|
|
202
205
|
|
|
203
206
|
Exporter фиксирует эти границы явно и не пытается гадать.
|
|
204
207
|
|
|
208
|
+
`isValidDate`, `isError`, `isRegExp` и `isPromiseLike` описывают runtime-значения без точного представления в JSON Schema. Strict mode бросает `JsonSchemaExportError`, а best-effort mode выдаёт узел без ограничений. Числа JSON конечны, поэтому для `isFiniteNumber` дополнительный schema keyword не нужен.
|
|
209
|
+
|
|
205
210
|
## Связь с `describe(...)`
|
|
206
211
|
|
|
207
212
|
`toJsonSchema(...)` находится downstream от `describe(...)`.
|
package/docs/ru/05-public-api.md
CHANGED
|
@@ -19,6 +19,8 @@ Root package экспортирует:
|
|
|
19
19
|
- `validate`
|
|
20
20
|
- `validate.sync`
|
|
21
21
|
- `matches.sync`
|
|
22
|
+
- `Guard`
|
|
23
|
+
- `Refinement`
|
|
22
24
|
- `meta`
|
|
23
25
|
- `describe`
|
|
24
26
|
- `custom`
|
|
@@ -33,8 +35,9 @@ Root package экспортирует:
|
|
|
33
35
|
|
|
34
36
|
Root package включает:
|
|
35
37
|
|
|
36
|
-
- низкоуровневое создание assertions через `assert(...)`
|
|
37
|
-
- built-in assertions вроде `isString`, `isNumber`, `isBoolean`, `isNull`, `isEmail`, `
|
|
38
|
+
- низкоуровневое создание assertions через `assert(...)` и `refine(...)`
|
|
39
|
+
- built-in guard assertions вроде `isString`, `isNumber`, `isBoolean`, `isNull`, `isEmail`, `oneOf(...)`
|
|
40
|
+
- built-in refinement assertions вроде `hasLength(...)`, `hasSize(...)`, `hasPattern(...)`, `startsWith(...)`, `hasValue(...)`, `multipleOf(...)`
|
|
38
41
|
- structural combinators вроде `shape(...)`, `each(...)`, `tuple(...)`, `record(...)`
|
|
39
42
|
- wrappers вроде `optional(...)`, `nullable(...)`, `nullish(...)`
|
|
40
43
|
- branching combinators вроде `union(...)` и `discriminatedUnion(...)`
|
|
@@ -42,6 +45,27 @@ Root package включает:
|
|
|
42
45
|
|
|
43
46
|
Это основной runtime-facing API surface библиотеки.
|
|
44
47
|
|
|
48
|
+
Последовательные массивы assertions теперь stage-aware: совместимый кортеж вроде `[isString, hasLength({ min: 3 })]` поддерживается напрямую, а несовместимые комбинации отсекаются типовой системой.
|
|
49
|
+
Refinement assertions в этой модели являются staged-helper'ами, поэтому `validate(...)` и `matches.sync(...)` ожидают их после совместимого guard-а, а не в одиночку.
|
|
50
|
+
|
|
51
|
+
Структурные validators вроде `each(...)` сбрасывают assertion stage. Перед следующим refinement нужен новый совместимый guard, например `[each(isString), isDefined, hasLength({ min: 2 })]`.
|
|
52
|
+
|
|
53
|
+
### Встроенные проверки значений
|
|
54
|
+
|
|
55
|
+
Эти проверки доступны как assertions из root и `./assertions`, а как boolean type guards — из `./predicates`.
|
|
56
|
+
|
|
57
|
+
| Проверка | Допустимые значения | Код нарушения |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `isFiniteNumber` | Числа без `NaN` и бесконечностей | `number.finite` |
|
|
60
|
+
| `isInteger` | Целые числа | `number.integer` |
|
|
61
|
+
| `isSafeInteger` | Целые числа в безопасном диапазоне JavaScript | `number.safe-integer` |
|
|
62
|
+
| `isValidDate` | Экземпляры `Date` с корректным timestamp | `date.valid` |
|
|
63
|
+
| `isError` | Экземпляры `Error`, включая подклассы | `type.error` |
|
|
64
|
+
| `isRegExp` | Экземпляры `RegExp` | `type.regexp` |
|
|
65
|
+
| `isPromiseLike` | Объекты или функции с вызываемым свойством `then` | `type.promise-like` |
|
|
66
|
+
|
|
67
|
+
`isNumber` по-прежнему принимает бесконечности, а `isDate` — невалидные экземпляры `Date`. Для их отклонения используйте более строгие проверки. `isPromiseLike` проверяет наличие вызываемого `then`, не вызывая его и не проверяя тип результата.
|
|
68
|
+
|
|
45
69
|
## Метаданные и интроспекция
|
|
46
70
|
|
|
47
71
|
Тот же root package также включает:
|
|
@@ -67,7 +91,7 @@ Root package также содержит:
|
|
|
67
91
|
`validate(...)` и `validate.sync(...)` возвращают:
|
|
68
92
|
|
|
69
93
|
```typescript
|
|
70
|
-
type
|
|
94
|
+
type ValidationResult<T> =
|
|
71
95
|
| [ok: true, validated: T, violations: []]
|
|
72
96
|
| [ok: false, validated: unknown, violations: Violation[]]
|
|
73
97
|
```
|
|
@@ -77,6 +101,8 @@ type ValidationTuple<T> =
|
|
|
77
101
|
- `ok` показывает, прошла ли валидация;
|
|
78
102
|
- `validated` становится строго типизированным только в успешной ветке;
|
|
79
103
|
- `violations` пуст при успехе и содержит структурированные ошибки при неуспехе.
|
|
104
|
+
- `validate(...)` остаётся основным async-first entrypoint;
|
|
105
|
+
- `validate.sync(...)` явно выбрасывает ошибку при async validators и async object-level rules из `shape(...).refine(...)`.
|
|
80
106
|
|
|
81
107
|
## Subpath predicates
|
|
82
108
|
|
|
@@ -97,6 +123,12 @@ Predicates доступны из:
|
|
|
97
123
|
|
|
98
124
|
Используйте этот subpath, когда нужны guard-style runtime checks без более высокого validation layer.
|
|
99
125
|
|
|
126
|
+
В `isShape({ name: [isString, false] })` optional-поле может отсутствовать,
|
|
127
|
+
но присутствующее значение всегда проверяется предикатом. Это относится и к
|
|
128
|
+
явному `undefined`: чтобы разрешить его, используйте `Or(isString, isUndefined)`.
|
|
129
|
+
Shorthand `name: isString` и кортеж `[isString, true]` задают обязательное поле.
|
|
130
|
+
Наличие поля проверяется через `in`, включая свойства из цепочки прототипов.
|
|
131
|
+
|
|
100
132
|
## Subpath экспорта JSON Schema
|
|
101
133
|
|
|
102
134
|
JSON Schema export доступен из:
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
Быстрое правило выбора:
|
|
15
15
|
|
|
16
16
|
- используйте `@modulify/validator/predicates`, когда нужны только runtime checks и type guards;
|
|
17
|
-
- используйте built-in assertions вроде `isString`, `isDefined`, `hasLength(...)`, `oneOf(...)`, когда нужны машиночитаемые ошибки;
|
|
17
|
+
- используйте built-in guard/refinement assertions вроде `isString`, `isDefined`, `hasLength(...)`, `oneOf(...)`, когда нужны машиночитаемые ошибки;
|
|
18
18
|
- используйте combinators вроде `shape(...)`, `each(...)`, `tuple(...)`, `record(...)`, `union(...)`, `discriminatedUnion(...)`, когда валидация становится структурной;
|
|
19
19
|
- используйте `meta(...)` и `describe(...)`, когда другой слой нуждается в стабильных машиночитаемых descriptors;
|
|
20
20
|
- используйте `toJsonSchema(...)` только тогда, когда нужно представление для interoperability или экспорта, а не источник runtime truth.
|
|
@@ -44,6 +44,7 @@ const [ok, validated, violations] = validate.sync(input, createUser)
|
|
|
44
44
|
|
|
45
45
|
- используйте `.strict()` для request payload, если неизвестные ключи должны отклоняться;
|
|
46
46
|
- держите leaf checks маленькими и хорошо сочетаемыми друг с другом;
|
|
47
|
+
- если используете массив assertions, начинайте его с совместимого guard-а вроде `isString` перед строковыми refinement-проверками вроде `hasLength(...)`;
|
|
47
48
|
- используйте элемент кортежа `validated` внутри успешной ветки;
|
|
48
49
|
- используйте `violations` как структурированные данные для ответов API, логов или сопоставления с UI.
|
|
49
50
|
|
|
@@ -43,7 +43,7 @@ Violations — это прежде всего структурированные
|
|
|
43
43
|
## Контракт результата валидации
|
|
44
44
|
|
|
45
45
|
```typescript
|
|
46
|
-
type
|
|
46
|
+
type ValidationResult<T> =
|
|
47
47
|
| [ok: true, validated: T, violations: []]
|
|
48
48
|
| [ok: false, validated: unknown, violations: Violation[]]
|
|
49
49
|
```
|
|
@@ -53,7 +53,8 @@ type ValidationTuple<T> =
|
|
|
53
53
|
- `validate(...)` сужает `validated` в успешной ветке;
|
|
54
54
|
- `validate(...)` не сужает исходную входную переменную;
|
|
55
55
|
- `matches.sync(...)` — это API для сужения исходной переменной;
|
|
56
|
-
- `violations` при успехе всегда
|
|
56
|
+
- `violations` при успехе всегда пуст;
|
|
57
|
+
- `validate(...)` — async-first API, а `validate.sync(...)` и `matches.sync(...)` остаются специализированными sync API.
|
|
57
58
|
|
|
58
59
|
## Семантика wrappers
|
|
59
60
|
|
|
@@ -90,6 +91,8 @@ type ValidationTuple<T> =
|
|
|
90
91
|
|
|
91
92
|
- structural derivations намеренно сбрасывают object-level rules;
|
|
92
93
|
- mode switches намеренно сохраняют object-level rules.
|
|
94
|
+
- `.refine(...)` — async-first и может возвращать promise;
|
|
95
|
+
- `.refine.sync(...)` — явно sync-safe API для object-level rules.
|
|
93
96
|
|
|
94
97
|
## Контракт violations
|
|
95
98
|
|
|
@@ -58,6 +58,32 @@ const lengthDescriptor = describe(hasLength({ min: 3 }))
|
|
|
58
58
|
|
|
59
59
|
Это удобно для адаптеров и tooling-кода, который читает descriptors и хочет ветвиться по коду без ручных cast.
|
|
60
60
|
|
|
61
|
+
Та же точность теперь протекает и в `validate(...)` для параметризованных built-in assertions.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import {
|
|
65
|
+
collection,
|
|
66
|
+
hasLength,
|
|
67
|
+
isString,
|
|
68
|
+
validate,
|
|
69
|
+
} from '@modulify/validator'
|
|
70
|
+
|
|
71
|
+
const [ok, , violations] = validate.sync('ab', [isString, hasLength({ min: 3 })])
|
|
72
|
+
|
|
73
|
+
if (!ok) {
|
|
74
|
+
collection(violations).map(violation => {
|
|
75
|
+
switch (violation.violates.code) {
|
|
76
|
+
case 'type.string':
|
|
77
|
+
return violation.violates.name
|
|
78
|
+
case 'length.min':
|
|
79
|
+
return violation.violates.args[0]
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Для staged-вызовов вроде `[isString, hasLength({ min: 3 })]` невозможные ветки, например `'length.max'` или `'length.range'`, больше не попадают в union violations, а unsupported-type остаётся только на стороне descriptor-а и не протекает в staged-валидацию.
|
|
86
|
+
|
|
61
87
|
## Зачем нужен глобальный реестр
|
|
62
88
|
|
|
63
89
|
Точные literals на отдельных значениях полезны для локальной интроспекции.
|
|
@@ -158,7 +184,7 @@ const isAvailableEmail = assert(
|
|
|
158
184
|
Та же идея работает и для object-level refinement issues.
|
|
159
185
|
|
|
160
186
|
```typescript
|
|
161
|
-
import type {
|
|
187
|
+
import type { ShapeRefinementViolationInput } from '@modulify/validator'
|
|
162
188
|
import {
|
|
163
189
|
isEmail,
|
|
164
190
|
isString,
|
|
@@ -180,7 +206,7 @@ const signUpForm = shape({
|
|
|
180
206
|
path: ['confirmation', 'password'],
|
|
181
207
|
code: 'profile.password.mismatch',
|
|
182
208
|
args: [],
|
|
183
|
-
}] satisfies
|
|
209
|
+
}] satisfies ShapeRefinementViolationInput<'profile.password.mismatch'>
|
|
184
210
|
})
|
|
185
211
|
```
|
|
186
212
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Миграция с 0.2.1
|
|
2
|
+
|
|
3
|
+
[Индекс документации](./00-index.md)
|
|
4
|
+
[English](../en/09-migration.md)
|
|
5
|
+
|
|
6
|
+
Здесь описаны ещё не выпущенные изменения после `0.2.1`.
|
|
7
|
+
|
|
8
|
+
## Правила уровня объекта
|
|
9
|
+
|
|
10
|
+
`shape(...).refine(...)` стал async-first, даже если callback возвращает обычное
|
|
11
|
+
значение. Для правил, используемых через `validate.sync(...)`, `matches.sync(...)`
|
|
12
|
+
или `shape.check(...)`, нужен `shape(...).refine.sync(...)`.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const profile = shape({ password: isString, confirmation: isString })
|
|
16
|
+
.refine.sync(value => value.password === value.confirmation ? null : {
|
|
17
|
+
code: 'profile.password.mismatch',
|
|
18
|
+
})
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Асинхронные правила `.refine(...)` используются с `await validate(...)`.
|
|
22
|
+
|
|
23
|
+
## Последовательные assertions
|
|
24
|
+
|
|
25
|
+
Guard задаёт область допустимых значений, refinement проверяет свойства внутри неё:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
validate.sync('name', [isString, hasLength({ min: 3 })])
|
|
29
|
+
validate.sync(4, [isInteger, multipleOf(2)])
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Замените самостоятельные refinements вроде `validate.sync(value, hasLength(...))`
|
|
33
|
+
на совместимый guard и refinement после него. Несовместимые сочетания, например
|
|
34
|
+
`[isNumber, hasLength(...)]`, отклоняются TypeScript.
|
|
35
|
+
|
|
36
|
+
Структурные validators сбрасывают assertion stage. Для проверки длины внешнего
|
|
37
|
+
массива после проверки элементов используйте
|
|
38
|
+
`[each(isString), isDefined, hasLength({ min: 2 })]`.
|
|
39
|
+
|
|
40
|
+
## Имена публичных типов
|
|
41
|
+
|
|
42
|
+
Совместимых aliases для прежних имён нет. Обновите imports и аннотации:
|
|
43
|
+
|
|
44
|
+
| Прежнее имя | Новое имя |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `ValidationTuple` | `ValidationResult` |
|
|
47
|
+
| `InferMaybeManyViolations` | `InferViolations` |
|
|
48
|
+
| `ObjectDescriptor` | `ShapeDescriptor` |
|
|
49
|
+
| `InferObjectDescriptor` | `InferShape` |
|
|
50
|
+
| `PartialObjectDescriptor` | `PartialShapeDescriptor` |
|
|
51
|
+
| `MergeObjectDescriptors` | `MergeShapeDescriptors` |
|
|
52
|
+
| `ObjectShapeFieldSelector` | `ShapeFieldSelector` |
|
|
53
|
+
| `ObjectShapeRefinement` / `ObjectShapeAsyncRefinement` | `ShapeRefinement` |
|
|
54
|
+
| `ObjectShapeRefinementSync` / `ObjectShapeSyncRefinement` | `SyncShapeRefinement` |
|
|
55
|
+
| `ObjectShapeRefinementIssue` | `ShapeRefinementViolationInput` |
|
|
56
|
+
| `ObjectShapeRefineMethod` | `ShapeRefineMethod` |
|
|
57
|
+
| `ObjectShapeRefineMethodSync` | `ShapeRefineMethodSync` |
|
|
58
|
+
| `DescribeMaybeMany` | `DescribeConstraints` |
|
|
59
|
+
| `DescribeObjectDescriptor` | `DescribeShapeDescriptor` |
|
|
60
|
+
| `AssertionDescriptorConstraint` | `AssertionConstraintDescriptor` |
|
|
61
|
+
| `ConstraintDescriptorBase` | `BaseConstraintDescriptor` |
|
|
62
|
+
| `ValidatorDescriptor` | `OpaqueValidatorDescriptor` |
|
|
63
|
+
| `GenericObjectShapeRuleDescriptor` | `SyncObjectShapeRuleDescriptor` |
|
|
64
|
+
|
|
65
|
+
## Optional-поля в predicates
|
|
66
|
+
|
|
67
|
+
`isShape({ name: [isString, false] })` разрешает отсутствие `name`, но отклоняет
|
|
68
|
+
`{ name: 2 }` и `{ name: undefined }`. Для явного `undefined` используйте
|
|
69
|
+
`[Or(isString, isUndefined), false]`. Обязательное поле должно присутствовать,
|
|
70
|
+
даже если его предикат принимает `undefined`.
|
|
71
|
+
|
|
72
|
+
## Потребители пакета
|
|
73
|
+
|
|
74
|
+
Публичные root и subpath exports поддерживают ESM, CommonJS и strict TypeScript
|
|
75
|
+
с разрешением модулей NodeNext или Bundler. Используйте package entrypoints,
|
|
76
|
+
а не внутренние файлы `dist/`.
|
package/docs/ru/README.md
CHANGED
|
@@ -207,7 +207,7 @@ if (ok) {
|
|
|
207
207
|
|
|
208
208
|
## API объектных схем
|
|
209
209
|
|
|
210
|
-
`shape(...)` — это переиспользуемый API объектных схем. Он валидирует вложенные record-like objects и предоставляет небольшой неизменяемый API вроде `strict()`, `pick()`, `omit()`, `partial()`, `extend()`, `merge()`, `refine()` и `fieldsMatch(...)`.
|
|
210
|
+
`shape(...)` — это переиспользуемый API объектных схем. Он валидирует вложенные record-like objects и предоставляет небольшой неизменяемый API вроде `strict()`, `pick()`, `omit()`, `partial()`, `extend()`, `merge()`, async-first `refine()`, явного `refine.sync()` и `fieldsMatch(...)`.
|
|
211
211
|
|
|
212
212
|
```typescript
|
|
213
213
|
import {
|
|
@@ -273,11 +273,17 @@ const node = describe(registration)
|
|
|
273
273
|
|
|
274
274
|
В текущем API это обычно выглядит так:
|
|
275
275
|
|
|
276
|
-
-
|
|
276
|
+
- guard assertions вроде `isString`, `isNumber`, `isDefined`, `oneOf(...)` задают базовый домен значения;
|
|
277
|
+
- refinement assertions вроде `hasLength(...)`, `hasPattern(...)`, `startsWith(...)`, `hasValue(...)`, `multipleOf(...)` проверяют свойства уже внутри этого домена;
|
|
277
278
|
- композиция схем через combinators вроде `exact`, `optional`, `nullable`, `nullish`, `shape(...)`, `each(...)`;
|
|
278
279
|
- типизированная валидация через `validate(...)` или `validate.sync(...)`;
|
|
279
280
|
- narrowing исходной переменной в sync-коде через `matches.sync(...)`.
|
|
280
281
|
|
|
282
|
+
Последовательные массивы assertions теперь stage-aware. Кортеж вроде `[isString, hasLength({ min: 3 })]` типизируется, а несовместимые комбинации вроде `[isNumber, hasLength({ min: 3 })]` TypeScript отсекает.
|
|
283
|
+
Refinement assertions не предполагаются для одиночной передачи в `validate(...)` или `matches.sync(...)`.
|
|
284
|
+
|
|
285
|
+
`validate(...)` — основной async-first entrypoint. `validate.sync(...)` и `matches.sync(...)` остаются специализированными sync API и выбрасывают ошибку, если встречают async validators или async object-level rules из `shape(...).refine(...)`.
|
|
286
|
+
|
|
281
287
|
## Нарушения
|
|
282
288
|
|
|
283
289
|
`validate(...)` возвращает машиночитаемый список `Violation[]`, а `collection(...)` может обернуть его в небольшой helper API для точного поиска по path и обхода дерева.
|
|
@@ -357,6 +363,8 @@ const jsonSchema = toJsonSchema(profile)
|
|
|
357
363
|
- [Практические рецепты](./06-common-recipes.md) - практические примеры для валидации payload, выбора wrappers, переиспользуемых shape, сопоставления ошибок формы и экспорта JSON Schema.
|
|
358
364
|
- [Справка для AI](./07-ai-reference.md) - компактное описание контракта для agents, инструментов и быстрого поиска семантики библиотеки.
|
|
359
365
|
|
|
366
|
+
- [Миграция с 0.2.1](./09-migration.md)
|
|
367
|
+
|
|
360
368
|
## Заметки
|
|
361
369
|
|
|
362
370
|
- Assertions возвращают структурированные метаданные вместо сообщений.
|
package/package.json
CHANGED
|
@@ -3,40 +3,66 @@
|
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "Declarative validation util for JavaScript",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.3.0",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
"import": {
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"default": "./dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./dist/index.d.cts",
|
|
15
|
+
"default": "./dist/index.cjs"
|
|
16
|
+
},
|
|
12
17
|
"default": "./dist/index.mjs"
|
|
13
18
|
},
|
|
14
19
|
"./assertions": {
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/assertions.d.mts",
|
|
22
|
+
"default": "./dist/assertions.mjs"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/assertions.d.cts",
|
|
26
|
+
"default": "./dist/assertions.cjs"
|
|
27
|
+
},
|
|
18
28
|
"default": "./dist/assertions.mjs"
|
|
19
29
|
},
|
|
20
30
|
"./combinators": {
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/combinators.d.mts",
|
|
33
|
+
"default": "./dist/combinators.mjs"
|
|
34
|
+
},
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "./dist/combinators.d.cts",
|
|
37
|
+
"default": "./dist/combinators.cjs"
|
|
38
|
+
},
|
|
24
39
|
"default": "./dist/combinators.mjs"
|
|
25
40
|
},
|
|
26
41
|
"./json-schema": {
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
"import": {
|
|
43
|
+
"types": "./dist/json-schema.d.mts",
|
|
44
|
+
"default": "./dist/json-schema.mjs"
|
|
45
|
+
},
|
|
46
|
+
"require": {
|
|
47
|
+
"types": "./dist/json-schema.d.cts",
|
|
48
|
+
"default": "./dist/json-schema.cjs"
|
|
49
|
+
},
|
|
30
50
|
"default": "./dist/json-schema.mjs"
|
|
31
51
|
},
|
|
32
52
|
"./predicates": {
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
53
|
+
"import": {
|
|
54
|
+
"types": "./dist/predicates.d.mts",
|
|
55
|
+
"default": "./dist/predicates.mjs"
|
|
56
|
+
},
|
|
57
|
+
"require": {
|
|
58
|
+
"types": "./dist/predicates.d.cts",
|
|
59
|
+
"default": "./dist/predicates.cjs"
|
|
60
|
+
},
|
|
36
61
|
"default": "./dist/predicates.mjs"
|
|
37
62
|
}
|
|
38
63
|
},
|
|
39
64
|
"types": "dist/index.d.ts",
|
|
65
|
+
"sideEffects": false,
|
|
40
66
|
"typesVersions": {
|
|
41
67
|
"*": {
|
|
42
68
|
"assertions": [
|
|
@@ -55,36 +81,36 @@
|
|
|
55
81
|
},
|
|
56
82
|
"scripts": {
|
|
57
83
|
"build": "vite build",
|
|
58
|
-
"lint": "eslint src tests types",
|
|
84
|
+
"lint": "eslint src tests types scripts vite.config.ts release.config.mjs",
|
|
59
85
|
"prepare": "husky",
|
|
60
|
-
"release": "
|
|
61
|
-
"release:
|
|
62
|
-
"release:
|
|
63
|
-
"release:
|
|
86
|
+
"release": "conventional-release --tags",
|
|
87
|
+
"release:dry": "conventional-release --dry --verbose --tags",
|
|
88
|
+
"release:minor": "conventional-release --tags --release-as minor",
|
|
89
|
+
"release:patch": "conventional-release --tags --release-as patch",
|
|
90
|
+
"release:major": "conventional-release --tags --release-as major",
|
|
64
91
|
"stats": "gzip -c ./dist/index.mjs | wc -c",
|
|
65
92
|
"test": "vitest run",
|
|
66
|
-
"test:d": "vitest run --typecheck.only",
|
|
67
|
-
"test:
|
|
68
|
-
"test:coverage:html": "vitest run --coverage --reporter=html --outputFile.html=./reports/html/report.html",
|
|
93
|
+
"test:d": "vitest run --typecheck.only --coverage.enabled=false",
|
|
94
|
+
"test:package": "yarn build && node scripts/check-package.mjs",
|
|
69
95
|
"typecheck": "tsc --noEmit"
|
|
70
96
|
},
|
|
71
97
|
"devDependencies": {
|
|
72
|
-
"@commitlint/cli": "^
|
|
73
|
-
"@commitlint/config-conventional": "^
|
|
98
|
+
"@commitlint/cli": "^21.2.3",
|
|
99
|
+
"@commitlint/config-conventional": "^21.2.3",
|
|
74
100
|
"@eslint/js": "^10.0.1",
|
|
75
|
-
"@
|
|
76
|
-
"@
|
|
77
|
-
"@vitest/
|
|
78
|
-
"
|
|
79
|
-
"
|
|
101
|
+
"@modulify/conventional-release": "^0.1.3",
|
|
102
|
+
"@types/node": "^24.19.0",
|
|
103
|
+
"@vitest/coverage-v8": "5.0.2",
|
|
104
|
+
"@vitest/ui": "5.0.2",
|
|
105
|
+
"eslint": "^10.11.0",
|
|
106
|
+
"globals": "^17.12.0",
|
|
80
107
|
"husky": "^9.1.7",
|
|
81
|
-
"ts-node": "^10.9.2",
|
|
82
108
|
"tslib": "^2.8.1",
|
|
83
|
-
"typescript": "
|
|
84
|
-
"typescript-eslint": "^8.
|
|
85
|
-
"
|
|
86
|
-
"vite
|
|
87
|
-
"vitest": "
|
|
109
|
+
"typescript": "~6.0.3",
|
|
110
|
+
"typescript-eslint": "^8.70.1",
|
|
111
|
+
"unplugin-dts": "^1.1.1",
|
|
112
|
+
"vite": "^8.3.1",
|
|
113
|
+
"vitest": "5.0.2"
|
|
88
114
|
},
|
|
89
115
|
"resolutions": {
|
|
90
116
|
"minimatch@npm:10.2.1": "npm:10.2.4",
|