@modulify/validator 0.2.1 → 0.3.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 (88) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/README.md +12 -2
  3. package/dist/assert.cjs +99 -63
  4. package/dist/assert.d.cts +37 -0
  5. package/dist/assert.d.mts +37 -0
  6. package/dist/assert.d.ts +24 -3
  7. package/dist/assert.mjs +94 -64
  8. package/dist/assertions.cjs +283 -178
  9. package/dist/assertions.d.cts +56 -0
  10. package/dist/assertions.d.mts +56 -0
  11. package/dist/assertions.d.ts +43 -45
  12. package/dist/assertions.mjs +274 -201
  13. package/dist/checkers.cjs +23 -0
  14. package/dist/checkers.d.cts +8 -0
  15. package/dist/checkers.d.mts +8 -0
  16. package/dist/checkers.d.ts +1 -1
  17. package/dist/checkers.mjs +16 -0
  18. package/dist/combinators.cjs +305 -304
  19. package/dist/combinators.d.cts +16 -0
  20. package/dist/combinators.d.mts +16 -0
  21. package/dist/combinators.d.ts +15 -16
  22. package/dist/combinators.mjs +305 -314
  23. package/dist/constraints.cjs +23 -0
  24. package/dist/constraints.d.cts +4 -0
  25. package/dist/constraints.d.mts +4 -0
  26. package/dist/constraints.d.ts +2 -2
  27. package/dist/constraints.mjs +21 -0
  28. package/dist/extractors.cjs +6 -0
  29. package/dist/extractors.d.cts +2 -0
  30. package/dist/extractors.d.mts +2 -0
  31. package/dist/extractors.mjs +5 -0
  32. package/dist/index.cjs +140 -219
  33. package/dist/index.d.cts +12 -0
  34. package/dist/index.d.mts +12 -0
  35. package/dist/index.d.ts +9 -9
  36. package/dist/index.mjs +93 -222
  37. package/dist/json-schema.cjs +364 -489
  38. package/dist/json-schema.d.cts +14 -0
  39. package/dist/json-schema.d.mts +14 -0
  40. package/dist/json-schema.d.ts +3 -3
  41. package/dist/json-schema.mjs +365 -492
  42. package/dist/metadata.cjs +98 -7
  43. package/dist/metadata.d.cts +8 -0
  44. package/dist/metadata.d.mts +8 -0
  45. package/dist/metadata.d.ts +2 -2
  46. package/dist/metadata.mjs +93 -7
  47. package/dist/predicates.cjs +98 -41
  48. package/dist/predicates.d.cts +77 -0
  49. package/dist/predicates.d.mts +77 -0
  50. package/dist/predicates.d.ts +25 -4
  51. package/dist/predicates.mjs +92 -66
  52. package/dist/types/index.d.cts +984 -0
  53. package/dist/types/index.d.mts +984 -0
  54. package/dist/types/index.d.ts +984 -0
  55. package/dist/types/json-schema.d.cts +75 -0
  56. package/dist/types/json-schema.d.mts +75 -0
  57. package/dist/types/json-schema.d.ts +75 -0
  58. package/dist/violations.cjs +81 -0
  59. package/dist/violations.d.cts +29 -0
  60. package/dist/violations.d.mts +29 -0
  61. package/dist/violations.d.ts +1 -1
  62. package/dist/violations.mjs +80 -0
  63. package/docs/RELEASING.md +66 -0
  64. package/docs/en/00-index.md +2 -0
  65. package/docs/en/01-shape-api.md +35 -10
  66. package/docs/en/02-metadata-and-introspection.md +2 -1
  67. package/docs/en/03-violations.md +1 -1
  68. package/docs/en/04-json-schema-export.md +5 -0
  69. package/docs/en/05-public-api.md +35 -3
  70. package/docs/en/06-common-recipes.md +2 -1
  71. package/docs/en/07-ai-reference.md +5 -2
  72. package/docs/en/08-violation-code-types.md +28 -2
  73. package/docs/en/09-migration.md +75 -0
  74. package/docs/ru/00-index.md +2 -0
  75. package/docs/ru/01-shape-api.md +35 -10
  76. package/docs/ru/02-metadata-and-introspection.md +2 -1
  77. package/docs/ru/03-violations.md +1 -1
  78. package/docs/ru/04-json-schema-export.md +5 -0
  79. package/docs/ru/05-public-api.md +35 -3
  80. package/docs/ru/06-common-recipes.md +2 -1
  81. package/docs/ru/07-ai-reference.md +5 -2
  82. package/docs/ru/08-violation-code-types.md +28 -2
  83. package/docs/ru/09-migration.md +76 -0
  84. package/docs/ru/README.md +10 -2
  85. package/package.json +63 -37
  86. package/types/index.d.ts +275 -115
  87. package/dist/metadata.cjs.js +0 -130
  88. package/dist/metadata.es.js +0 -131
@@ -19,6 +19,8 @@ The root package exports:
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 @@ Use the root package when you need the main validation API, composed validators,
33
35
 
34
36
  The root package includes:
35
37
 
36
- - low-level assertion construction through `assert(...)`
37
- - built-in assertions such as `isString`, `isNumber`, `isBoolean`, `isNull`, `isEmail`, `hasLength(...)`, `oneOf(...)`
38
+ - low-level assertion construction through `assert(...)` and `refine(...)`
39
+ - built-in guard assertions such as `isString`, `isNumber`, `isBoolean`, `isNull`, `isEmail`, `oneOf(...)`
40
+ - built-in refinement assertions such as `hasLength(...)`, `hasSize(...)`, `hasPattern(...)`, `startsWith(...)`, `hasValue(...)`, `multipleOf(...)`
38
41
  - structural combinators such as `shape(...)`, `each(...)`, `tuple(...)`, `record(...)`
39
42
  - wrapper combinators such as `optional(...)`, `nullable(...)`, `nullish(...)`
40
43
  - branching combinators such as `union(...)` and `discriminatedUnion(...)`
@@ -42,6 +45,27 @@ The root package includes:
42
45
 
43
46
  This is the main runtime-facing API surface of the library.
44
47
 
48
+ Sequential assertion arrays are stage-aware: a compatible tuple such as `[isString, hasLength({ min: 3 })]` is supported directly, while incompatible combinations are rejected by the type system.
49
+ Refinement assertions are staged helpers, so `validate(...)` and `matches.sync(...)` expect them to appear after a compatible guard instead of being passed on their own.
50
+
51
+ Structural validators such as `each(...)` reset the assertion stage. Use a new compatible guard before subsequent refinements, for example `[each(isString), isDefined, hasLength({ min: 2 })]`.
52
+
53
+ ### Built-in value guards
54
+
55
+ These checks are available as assertions from the root and `./assertions`, and as boolean type guards from `./predicates`.
56
+
57
+ | Check | Accepted values | Violation code |
58
+ | --- | --- | --- |
59
+ | `isFiniteNumber` | Numbers excluding `NaN` and infinities | `number.finite` |
60
+ | `isInteger` | Integer numbers | `number.integer` |
61
+ | `isSafeInteger` | Integers within JavaScript's safe integer bounds | `number.safe-integer` |
62
+ | `isValidDate` | `Date` instances with a valid timestamp | `date.valid` |
63
+ | `isError` | `Error` instances, including subclasses | `type.error` |
64
+ | `isRegExp` | `RegExp` instances | `type.regexp` |
65
+ | `isPromiseLike` | Objects or functions with a callable `then` property | `type.promise-like` |
66
+
67
+ `isNumber` still accepts infinities; `isDate` still accepts invalid `Date` instances. Use the stricter checks when those values must be rejected. `isPromiseLike` checks the presence of a callable `then`, without executing it or inspecting the resolved value.
68
+
45
69
  ## Metadata And Introspection
46
70
 
47
71
  The same root package also includes:
@@ -67,7 +91,7 @@ These are the main utilities for post-processing machine-readable validation fai
67
91
  `validate(...)` and `validate.sync(...)` return:
68
92
 
69
93
  ```typescript
70
- type ValidationTuple<T> =
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 @@ Practical meaning:
77
101
  - `ok` tells whether validation succeeded;
78
102
  - `validated` becomes strongly typed only in the success branch;
79
103
  - `violations` is empty on success and contains structured failures on error.
104
+ - `validate(...)` is the default async-first entrypoint;
105
+ - `validate.sync(...)` throws explicitly when it encounters async validators or async object-level `shape(...).refine(...)` rules.
80
106
 
81
107
  ## Predicates Subpath
82
108
 
@@ -97,6 +123,12 @@ This subpath contains reusable runtime/type-guard helpers and predicate combinat
97
123
 
98
124
  Use this subpath when you want guard-style runtime checks without pulling in the higher-level validation layer.
99
125
 
126
+ In `isShape({ name: [isString, false] })`, an optional field may be absent,
127
+ but a present value is always checked by its predicate. This includes explicit
128
+ `undefined`: use `Or(isString, isUndefined)` to allow it.
129
+ The shorthand `name: isString` and the tuple `[isString, true]` define required fields.
130
+ Field presence is checked with `in`, including properties in the prototype chain.
131
+
100
132
  ## JSON Schema Export Subpath
101
133
 
102
134
  JSON Schema export is available from:
@@ -14,7 +14,7 @@ It is intentionally recipe-oriented. Use it when you already understand the proj
14
14
  Use this quick rule of thumb:
15
15
 
16
16
  - use `@modulify/validator/predicates` when you only need runtime checks and type guards;
17
- - use built-in assertions such as `isString`, `isDefined`, `hasLength(...)`, `oneOf(...)` when you want machine-readable failures;
17
+ - use built-in guard/refinement assertions such as `isString`, `isDefined`, `hasLength(...)`, `oneOf(...)` when you want machine-readable failures;
18
18
  - use combinators such as `shape(...)`, `each(...)`, `tuple(...)`, `record(...)`, `union(...)`, `discriminatedUnion(...)` when validation becomes structural;
19
19
  - use `meta(...)` and `describe(...)` when another layer needs stable machine-readable descriptors;
20
20
  - use `toJsonSchema(...)` only when you need an interoperability/export view, not as the source of runtime truth.
@@ -44,6 +44,7 @@ Practical pattern:
44
44
 
45
45
  - use `.strict()` for request payloads when unknown keys should be rejected;
46
46
  - keep leaf checks small and composable;
47
+ - when you use an assertion array, start with a compatible guard such as `isString` before string refinements like `hasLength(...)`;
47
48
  - use the `validated` tuple item inside the success branch;
48
49
  - use `violations` as structured data for API responses, logs, or UI mapping.
49
50
 
@@ -43,7 +43,7 @@ Use `@modulify/validator/json-schema` for:
43
43
  ## Validation Result Contract
44
44
 
45
45
  ```typescript
46
- type ValidationTuple<T> =
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 @@ Important consequences:
53
53
  - `validate(...)` narrows the `validated` tuple item in the success branch;
54
54
  - `validate(...)` does not narrow the original input variable;
55
55
  - `matches.sync(...)` is the API that narrows the original variable;
56
- - `violations` is always empty on success.
56
+ - `violations` is always empty on success;
57
+ - `validate(...)` is async-first, while `validate.sync(...)` and `matches.sync(...)` are specialized sync APIs.
57
58
 
58
59
  ## Wrapper Semantics
59
60
 
@@ -90,6 +91,8 @@ Important rule:
90
91
 
91
92
  - structural derivations intentionally drop object-level rules;
92
93
  - mode switches intentionally keep object-level rules.
94
+ - `.refine(...)` is async-first and may return a promise;
95
+ - `.refine.sync(...)` is the explicitly sync-safe object-level rule API.
93
96
 
94
97
  ## Violations Contract
95
98
 
@@ -58,6 +58,32 @@ In TypeScript this means:
58
58
 
59
59
  This is useful when adapters inspect descriptors and want code-aware branching without hand-written casts.
60
60
 
61
+ The same precision now flows into `validate(...)` for parameterized built-ins.
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
+ For staged calls like `[isString, hasLength({ min: 3 })]`, impossible branches such as `'length.max'` or `'length.range'` are no longer carried into the violation union, and the unsupported-type branch stays on the descriptor side instead of leaking into staged validation.
86
+
61
87
  ## What The Global Registry Solves
62
88
 
63
89
  Exact literals on individual values are good for local introspection.
@@ -158,7 +184,7 @@ This part does not depend on the global union extraction. The literal is preserv
158
184
  The same idea applies to object-level refinement issues.
159
185
 
160
186
  ```typescript
161
- import type { ObjectShapeRefinementIssue } from '@modulify/validator'
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 ObjectShapeRefinementIssue<'profile.password.mismatch'>
209
+ }] satisfies ShapeRefinementViolationInput<'profile.password.mismatch'>
184
210
  })
185
211
  ```
186
212
 
@@ -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/`.
@@ -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)
@@ -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(...)` добавляет синхронное object-level rule, которое запускается только после того, как базовая shape уже успешно провалидировалась как объект.
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
- `refine(...)` специально остаётся тонким:
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
 
@@ -120,7 +120,7 @@ const violation: Violation = {
120
120
  `validate(...)` и `validate.sync(...)` возвращают:
121
121
 
122
122
  ```typescript
123
- type ValidationTuple<T> =
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(...)`.
@@ -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`, `hasLength(...)`, `oneOf(...)`
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 ValidationTuple<T> =
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 ValidationTuple<T> =
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 { ObjectShapeRefinementIssue } from '@modulify/validator'
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 ObjectShapeRefinementIssue<'profile.password.mismatch'>
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/`.