@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,273 @@
1
+ # Common Recipes
2
+
3
+ [Documentation index](./00-index.md)
4
+ [Russian translation](../ru/06-common-recipes.md)
5
+
6
+ This guide answers the practical question:
7
+
8
+ > Which part of `@modulify/validator` should I use for task `X`?
9
+
10
+ It is intentionally recipe-oriented. Use it when you already understand the project direction and want a fast path to a concrete implementation.
11
+
12
+ ## Pick The Right Layer First
13
+
14
+ Use this quick rule of thumb:
15
+
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;
18
+ - use combinators such as `shape(...)`, `each(...)`, `tuple(...)`, `record(...)`, `union(...)`, `discriminatedUnion(...)` when validation becomes structural;
19
+ - use `meta(...)` and `describe(...)` when another layer needs stable machine-readable descriptors;
20
+ - use `toJsonSchema(...)` only when you need an interoperability/export view, not as the source of runtime truth.
21
+
22
+ ## Validate An API Payload
23
+
24
+ Use `shape(...)` plus leaf assertions.
25
+
26
+ ```typescript
27
+ import {
28
+ hasLength,
29
+ isDefined,
30
+ isString,
31
+ shape,
32
+ validate,
33
+ } from '@modulify/validator'
34
+
35
+ const createUser = shape({
36
+ email: [isDefined, isString],
37
+ password: [isString, hasLength({ min: 8 })],
38
+ }).strict()
39
+
40
+ const [ok, validated, violations] = validate.sync(input, createUser)
41
+ ```
42
+
43
+ Practical pattern:
44
+
45
+ - use `.strict()` for request payloads when unknown keys should be rejected;
46
+ - keep leaf checks small and composable;
47
+ - use the `validated` tuple item inside the success branch;
48
+ - use `violations` as structured data for API responses, logs, or UI mapping.
49
+
50
+ ## Narrow An Existing Variable In Sync Code
51
+
52
+ Use `matches.sync(...)`.
53
+
54
+ ```typescript
55
+ import {
56
+ isDefined,
57
+ isString,
58
+ matches,
59
+ } from '@modulify/validator'
60
+
61
+ const value: unknown = source()
62
+
63
+ if (matches.sync(value, [isDefined, isString])) {
64
+ value.toUpperCase()
65
+ }
66
+ ```
67
+
68
+ Use this when you want to narrow the original variable itself.
69
+
70
+ Do not use `validate.sync(...)` for this purpose if your main goal is narrowing the original variable. `validate.sync(...)` narrows the `validated` tuple item, not the original input binding.
71
+
72
+ ## Model Optional, Nullable, And Nullish Fields
73
+
74
+ Use the wrapper that matches your runtime meaning:
75
+
76
+ - `optional(x)` means `undefined` is accepted;
77
+ - `nullable(x)` means `null` is accepted;
78
+ - `nullish(x)` means both `null` and `undefined` are accepted.
79
+
80
+ ```typescript
81
+ const profile = shape({
82
+ nickname: optional(isString),
83
+ middleName: nullable(isString),
84
+ bio: nullish(isString),
85
+ })
86
+ ```
87
+
88
+ Practical rule:
89
+
90
+ - use `optional(...)` for omitted or unset fields;
91
+ - use `nullable(...)` when `null` is a meaningful explicit value;
92
+ - use `nullish(...)` only when both cases are intentionally allowed.
93
+
94
+ ## Reuse One Shape In Multiple Views
95
+
96
+ Build one base shape, then derive from it.
97
+
98
+ ```typescript
99
+ import {
100
+ exact,
101
+ isString,
102
+ optional,
103
+ shape,
104
+ } from '@modulify/validator'
105
+
106
+ const account = shape({
107
+ id: isString,
108
+ nickname: optional(isString),
109
+ role: exact('admin'),
110
+ }).strict()
111
+
112
+ const publicAccount = account.pick(['id', 'nickname'])
113
+ const editableAccount = account.partial()
114
+ const adminAccount = account.extend({ team: isString })
115
+ ```
116
+
117
+ Use this pattern when one domain object appears in:
118
+
119
+ - API payloads;
120
+ - form state;
121
+ - internal service boundaries;
122
+ - public views with a reduced field set.
123
+
124
+ Remember that structural derivations such as `pick()`, `omit()`, `partial()`, `extend()`, and `merge()` intentionally drop object-level rules.
125
+
126
+ ## Attach UI Metadata Without Coupling UI To Validation
127
+
128
+ Use `meta(...)` on any constraint.
129
+
130
+ ```typescript
131
+ import {
132
+ isString,
133
+ meta,
134
+ shape,
135
+ } from '@modulify/validator'
136
+
137
+ const registration = shape({
138
+ email: meta(isString, {
139
+ title: 'Email',
140
+ description: 'Primary login address',
141
+ }),
142
+ })
143
+ ```
144
+
145
+ This is useful when a separate layer needs:
146
+
147
+ - field titles;
148
+ - placeholders or display hints;
149
+ - domain-specific metadata for rendering;
150
+ - descriptor-driven tooling.
151
+
152
+ Only some metadata keys are later mapped into JSON Schema. Other keys remain library-specific.
153
+
154
+ ## Build Field Error State For Forms
155
+
156
+ Use `collection(...)` on top of `violations`.
157
+
158
+ ```typescript
159
+ import {
160
+ collection,
161
+ isString,
162
+ shape,
163
+ validate,
164
+ } from '@modulify/validator'
165
+
166
+ const schema = shape({
167
+ profile: shape({
168
+ email: isString,
169
+ }),
170
+ })
171
+
172
+ const [ok, validated, violations] = validate.sync(input, schema)
173
+ const errors = collection(violations)
174
+
175
+ const rootErrors = errors.at([])
176
+ const emailErrors = errors.at(['profile', 'email'])
177
+ ```
178
+
179
+ This is the recommended shape when you need exact path lookups instead of string parsing.
180
+
181
+ ## Add Cross-Field Rules
182
+
183
+ Use `fieldsMatch(...)` for common confirmation cases and `refine(...)` for everything else.
184
+
185
+ ```typescript
186
+ const registration = shape({
187
+ password: isString,
188
+ confirmPassword: isString,
189
+ }).fieldsMatch(['password', 'confirmPassword'])
190
+ ```
191
+
192
+ ```typescript
193
+ const registration = shape({
194
+ password: isString,
195
+ confirmPassword: isString,
196
+ }).refine(value => {
197
+ return value.password === value.confirmPassword
198
+ ? []
199
+ : [{
200
+ path: ['confirmPassword'],
201
+ code: 'shape.fields.mismatch',
202
+ args: [['password', 'confirmPassword']],
203
+ }]
204
+ })
205
+ ```
206
+
207
+ Choose `fieldsMatch(...)` when the rule is exactly equality between two selectors.
208
+
209
+ Choose `refine(...)` when:
210
+
211
+ - more than two fields participate;
212
+ - the rule is domain-specific;
213
+ - the output path needs custom control;
214
+ - you want a custom descriptor for introspection.
215
+
216
+ ## Write A Custom Validator That Tooling Can Understand
217
+
218
+ Use `custom(...)` and provide `describe()` if the validator should participate in public introspection.
219
+
220
+ ```typescript
221
+ import { custom } from '@modulify/validator'
222
+
223
+ const isoDate = custom({
224
+ check(value: unknown): value is string {
225
+ return typeof value === 'string'
226
+ },
227
+ run() {
228
+ return []
229
+ },
230
+ describe() {
231
+ return {
232
+ kind: 'stringFormat' as const,
233
+ format: 'iso-date' as const,
234
+ }
235
+ },
236
+ })
237
+ ```
238
+
239
+ Without `describe()`, the validator remains intentionally opaque to `describe(...)` and `toJsonSchema(...)`.
240
+
241
+ ## Export JSON Schema Safely
242
+
243
+ Use `toJsonSchema(...)` in two different modes depending on the consumer.
244
+
245
+ ```typescript
246
+ import { toJsonSchema } from '@modulify/validator/json-schema'
247
+
248
+ const schema = toJsonSchema(profile)
249
+ const strictSchema = toJsonSchema(profile, { mode: 'strict' })
250
+ ```
251
+
252
+ Use best-effort mode when:
253
+
254
+ - external consumers can tolerate permissive `{}` nodes;
255
+ - a partial export is better than no export;
256
+ - you want the broadest schema view.
257
+
258
+ Use strict mode when:
259
+
260
+ - lossy export would be misleading;
261
+ - JSON Schema is part of a contract;
262
+ - unsupported runtime semantics must fail loudly.
263
+
264
+ ## Recommended Reading Order
265
+
266
+ If you are new to the library, the shortest useful reading path is:
267
+
268
+ 1. `README.md`
269
+ 2. `01-shape-api.md`
270
+ 3. `03-violations.md`
271
+ 4. `02-metadata-and-introspection.md`
272
+ 5. `04-json-schema-export.md`
273
+ 6. `07-ai-reference.md` when you want the compact contract summary
@@ -0,0 +1,215 @@
1
+ # AI Reference
2
+
3
+ [Documentation index](./00-index.md)
4
+ [Russian translation](../ru/07-ai-reference.md)
5
+
6
+ This page is a compact contract summary for AI agents, code generators, IDE tools, and human readers who want the shortest possible set of stable rules.
7
+
8
+ Treat it as the "canonical quick reference" layer above the longer guides.
9
+
10
+ ## Core Model
11
+
12
+ `@modulify/validator` is organized into three runtime layers:
13
+
14
+ - predicates: runtime checks and type guards;
15
+ - assertions: machine-readable leaf failures;
16
+ - combinators: structural composition over assertions and validators.
17
+
18
+ The library does not center itself around built-in human-readable messages.
19
+
20
+ Violations are structured data first.
21
+
22
+ ## Canonical Entry Points
23
+
24
+ Use the root package for:
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
+ Use `@modulify/validator/predicates` for standalone guard-style runtime checks.
37
+
38
+ Use `@modulify/validator/json-schema` for:
39
+
40
+ - `toJsonSchema(...)`
41
+ - `JsonSchemaExportError`
42
+
43
+ ## Validation Result Contract
44
+
45
+ ```typescript
46
+ type ValidationTuple<T> =
47
+ | [ok: true, validated: T, violations: []]
48
+ | [ok: false, validated: unknown, violations: Violation[]]
49
+ ```
50
+
51
+ Important consequences:
52
+
53
+ - `validate(...)` narrows the `validated` tuple item in the success branch;
54
+ - `validate(...)` does not narrow the original input variable;
55
+ - `matches.sync(...)` is the API that narrows the original variable;
56
+ - `violations` is always empty on success.
57
+
58
+ ## Wrapper Semantics
59
+
60
+ - `optional(x)` accepts `undefined`
61
+ - `nullable(x)` accepts `null`
62
+ - `nullish(x)` accepts `null | undefined`
63
+
64
+ These wrappers model runtime acceptance, not UI wording.
65
+
66
+ ## Shape Semantics
67
+
68
+ `shape(...)` validates plain record-like objects.
69
+
70
+ Defaults:
71
+
72
+ - unknown keys are allowed;
73
+ - unknown-key mode is `'passthrough'`;
74
+ - object-level rules are empty.
75
+
76
+ Mode switches:
77
+
78
+ - `.strict()` keeps the same fields and rules, but rejects unknown keys;
79
+ - `.passthrough()` keeps the same fields and rules, but allows unknown keys.
80
+
81
+ Structural derivations:
82
+
83
+ - `.pick(...)`
84
+ - `.omit(...)`
85
+ - `.partial(...)`
86
+ - `.extend(...)`
87
+ - `.merge(...)`
88
+
89
+ Important rule:
90
+
91
+ - structural derivations intentionally drop object-level rules;
92
+ - mode switches intentionally keep object-level rules.
93
+
94
+ ## Violations Contract
95
+
96
+ Violations are machine-readable objects with:
97
+
98
+ - failed value;
99
+ - path;
100
+ - semantic subject in `violates`.
101
+
102
+ Do not depend on text parsing for downstream processing.
103
+
104
+ Prefer:
105
+
106
+ - `violations`
107
+ - `collection(...)`
108
+ - path-based lookups
109
+
110
+ Over:
111
+
112
+ - string matching;
113
+ - ad-hoc message parsing;
114
+ - field-name extraction from text.
115
+
116
+ ## Metadata Contract
117
+
118
+ `meta(...)` attaches opaque machine-readable metadata to any constraint.
119
+
120
+ `describe(...)` returns a stable recursive descriptor tree.
121
+
122
+ Custom validators may participate in this contract by exposing `describe()`.
123
+
124
+ Without `describe()`, a custom validator remains opaque and usually appears as:
125
+
126
+ ```typescript
127
+ { kind: 'validator' }
128
+ ```
129
+
130
+ ## JSON Schema Contract
131
+
132
+ `toJsonSchema(...)` is a derived interoperability layer.
133
+
134
+ It is not the source of runtime truth.
135
+
136
+ Best-effort mode:
137
+
138
+ - default mode;
139
+ - unsupported nodes become permissive `{}` schemas;
140
+ - unsupported shape rules may be dropped with `$comment`.
141
+
142
+ Strict mode:
143
+
144
+ - throws `JsonSchemaExportError`;
145
+ - exposes `descriptor`, `reason`, and `path`.
146
+
147
+ Important mismatch:
148
+
149
+ - JSON Schema `required` is only an approximation of runtime `undefined` semantics;
150
+ - do not assume perfect semantic parity between runtime validation and exported JSON Schema.
151
+
152
+ ## Canonical Patterns
153
+
154
+ Use this when you want to validate a payload:
155
+
156
+ ```typescript
157
+ const [ok, validated, violations] = validate.sync(input, schema)
158
+ ```
159
+
160
+ Use this when you want to narrow the original variable:
161
+
162
+ ```typescript
163
+ if (matches.sync(value, schema)) {
164
+ // value is narrowed here
165
+ }
166
+ ```
167
+
168
+ Use this when you want reusable object schemas:
169
+
170
+ ```typescript
171
+ const schema = shape({...}).strict()
172
+ const partial = schema.partial()
173
+ const subset = schema.pick([...])
174
+ ```
175
+
176
+ Use this when another layer needs machine-readable introspection:
177
+
178
+ ```typescript
179
+ const descriptor = describe(schema)
180
+ ```
181
+
182
+ Use this when another system needs an export view:
183
+
184
+ ```typescript
185
+ const jsonSchema = toJsonSchema(schema)
186
+ ```
187
+
188
+ ## Do / Do Not
189
+
190
+ Do:
191
+
192
+ - keep leaf constraints small and composable;
193
+ - treat `violations` as data, not messages;
194
+ - use `shape(...)` for reusable object contracts;
195
+ - add `describe()` to custom validators that should participate in tooling;
196
+ - use strict JSON Schema export only when lossy export is unacceptable.
197
+
198
+ Do not:
199
+
200
+ - expect `validate(...)` to narrow the original input binding;
201
+ - assume `toJsonSchema(...)` is a full mirror of runtime semantics;
202
+ - assume structural shape derivations keep object-level rules;
203
+ - rely on undocumented private internals instead of `describe(...)`;
204
+ - build downstream logic around human-readable strings.
205
+
206
+ ## Best Sources Of Truth
207
+
208
+ For implementation details and edge cases, prefer this order:
209
+
210
+ 1. `README.md`
211
+ 2. `docs/en/*.md`
212
+ 3. `tests/*.test.ts`
213
+ 4. `tests/*.test-d.ts`
214
+
215
+ Tests are the most precise source for behavior that is easy to misunderstand from prose alone.