@atscript/typescript 0.1.49 → 0.1.51

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.
@@ -1,293 +0,0 @@
1
- # Validation — @atscript/typescript
2
-
3
- > Runtime data validation, type guards, error handling, and custom validator plugins.
4
-
5
- ## Basic Usage
6
-
7
- Every generated `.as` interface/type has a `.validator()` factory:
8
-
9
- ```ts
10
- import { User } from './models/user.as'
11
-
12
- // Create a validator
13
- const validator = User.validator()
14
-
15
- // Validate — throws ValidatorError on failure
16
- validator.validate(data)
17
-
18
- // Safe mode — returns boolean, no throw
19
- if (validator.validate(data, true)) {
20
- // data is narrowed to User (type guard)
21
- data.name // TypeScript knows this exists
22
- }
23
- ```
24
-
25
- ## The `Validator` Class
26
-
27
- ```ts
28
- import { Validator } from '@atscript/typescript/utils'
29
-
30
- // Create from any annotated type
31
- const validator = new Validator(someAnnotatedType, {
32
- // Options (all optional):
33
- partial: false, // allow missing required props
34
- unknownProps: 'error', // 'error' | 'strip' | 'ignore'
35
- errorLimit: 10, // max errors before stopping
36
- plugins: [], // custom validator plugins
37
- skipList: new Set(), // property paths to skip
38
- })
39
- ```
40
-
41
- ### `validate(value, safe?, context?)`
42
-
43
- ```ts
44
- // Throwing mode (default) — throws ValidatorError on failure
45
- validator.validate(data)
46
-
47
- // Safe mode — returns false instead of throwing
48
- const isValid = validator.validate(data, true)
49
-
50
- // With external context — passed to plugins
51
- validator.validate(data, true, { userId: '123' })
52
- ```
53
-
54
- The `validate` method is a **TypeScript type guard** — when it returns `true`, the value is narrowed to the interface's data type.
55
-
56
- ## Validator Options
57
-
58
- ### `partial` — Allow Missing Properties
59
-
60
- ```ts
61
- // Top-level properties only
62
- User.validator({ partial: true }).validate(data, true)
63
-
64
- // All levels (deep partial)
65
- User.validator({ partial: 'deep' }).validate(data, true)
66
-
67
- // Custom function — decide per object type
68
- User.validator({
69
- partial: (objectType, path) => {
70
- return path === '' // only root object is partial
71
- },
72
- }).validate(data, true)
73
- ```
74
-
75
- ### `unknownProps` — Handle Extra Properties
76
-
77
- ```ts
78
- // Error on unknown properties (default)
79
- User.validator({ unknownProps: 'error' }).validate(data, true)
80
-
81
- // Silently remove unknown properties from the value
82
- User.validator({ unknownProps: 'strip' }).validate(data, true)
83
-
84
- // Ignore unknown properties
85
- User.validator({ unknownProps: 'ignore' }).validate(data, true)
86
- ```
87
-
88
- **Note**: `'strip'` mutates the input object — it deletes unknown keys.
89
-
90
- ### `skipList` — Skip Specific Paths
91
-
92
- ```ts
93
- User.validator({
94
- skipList: new Set(['password', 'address.zip']),
95
- }).validate(data, true)
96
- ```
97
-
98
- ### `replace` — Substitute Type at Runtime
99
-
100
- ```ts
101
- User.validator({
102
- replace: (type, path) => {
103
- if (path === 'status') return customStatusType
104
- return type
105
- },
106
- }).validate(data, true)
107
- ```
108
-
109
- ## Error Handling
110
-
111
- ### `ValidatorError`
112
-
113
- When `validate()` throws (non-safe mode), it throws a `ValidatorError`:
114
-
115
- ```ts
116
- import { ValidatorError } from '@atscript/typescript/utils'
117
-
118
- try {
119
- validator.validate(data)
120
- } catch (e) {
121
- if (e instanceof ValidatorError) {
122
- // e.message — first error message (with path prefix)
123
- // e.errors — full structured error array
124
- console.log(e.errors)
125
- }
126
- }
127
- ```
128
-
129
- ### Error Structure
130
-
131
- ```ts
132
- interface TError {
133
- path: string // dot-separated path, e.g. "address.city"
134
- message: string // human-readable error message
135
- details?: TError[] // nested errors (for unions — shows why each branch failed)
136
- }
137
- ```
138
-
139
- ### Reading Errors in Safe Mode
140
-
141
- ```ts
142
- const validator = User.validator()
143
- if (!validator.validate(data, true)) {
144
- // Errors are on the validator instance
145
- for (const error of validator.errors) {
146
- console.log(`${error.path}: ${error.message}`)
147
- }
148
- }
149
- ```
150
-
151
- ### Error Examples
152
-
153
- ```ts
154
- // Missing required property
155
- { path: 'name', message: 'Expected string, got undefined' }
156
-
157
- // Wrong type
158
- { path: 'age', message: 'Expected number, got string' }
159
-
160
- // Pattern validation
161
- { path: 'email', message: 'Value is expected to match pattern "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$"' }
162
-
163
- // Custom annotation message
164
- { path: 'name', message: 'Name is required' } // from @meta.required "Name is required"
165
-
166
- // Unknown property
167
- { path: 'foo', message: 'Unexpected property' }
168
-
169
- // Union — shows why each branch failed
170
- {
171
- path: 'data',
172
- message: 'Value does not match any of the allowed types: [string(0)], [number(1)]',
173
- details: [
174
- { path: 'data', message: 'Expected string, got boolean' },
175
- { path: 'data', message: 'Expected number, got boolean' },
176
- ]
177
- }
178
-
179
- // Array validation
180
- { path: '2.name', message: 'Expected string, got number' } // 3rd element's name is wrong
181
- ```
182
-
183
- ### Error Limit
184
-
185
- By default, the validator stops collecting errors after 10. Customize:
186
-
187
- ```ts
188
- User.validator({ errorLimit: 50 }).validate(data, true)
189
- ```
190
-
191
- ## What Gets Validated
192
-
193
- | Type Kind | Validation |
194
- | -------------- | --------------------------------------------------------------------------------------------- |
195
- | `string` | Type check + `@meta.required` (non-empty) + `@expect.minLength/maxLength` + `@expect.pattern` |
196
- | `number` | Type check + `@expect.int` + `@expect.min/max` |
197
- | `boolean` | Type check + `@meta.required` (must be true) |
198
- | `null` | Exact `null` check |
199
- | `undefined` | Exact `undefined` check |
200
- | `any` | Always passes |
201
- | `never` | Always fails |
202
- | `phantom` | Always passes (skipped) |
203
- | `object` | Recursively validates all props, handles unknown props, pattern props |
204
- | `array` | Type check + `@expect.minLength/maxLength` + recursively validates each element |
205
- | `union` | At least one branch must pass |
206
- | `intersection` | All branches must pass |
207
- | `tuple` | Array length must match + each element validated against its position |
208
- | `literal` | Exact value match |
209
- | `optional` | `undefined` is accepted; if value is present, validated against inner type |
210
-
211
- ## Custom Validator Plugins
212
-
213
- Plugins intercept validation at every node in the type tree. They can accept, reject, or defer to default validation.
214
-
215
- ### Plugin Signature
216
-
217
- ```ts
218
- type TValidatorPlugin = (
219
- ctx: TValidatorPluginContext,
220
- def: TAtscriptAnnotatedType,
221
- value: any
222
- ) => boolean | undefined
223
- // ↑ true = accept, false = reject, undefined = fall through to default
224
- ```
225
-
226
- ### Plugin Context
227
-
228
- ```ts
229
- interface TValidatorPluginContext {
230
- opts: TValidatorOptions // current validator options
231
- validateAnnotatedType(def, value) // call default validation for a specific type
232
- error(message, path?, details?) // report an error
233
- path: string // current dot-separated path
234
- context: unknown // external context from validate(data, safe, context)
235
- }
236
- ```
237
-
238
- ### Plugin Example — Custom Date Validation
239
-
240
- ```ts
241
- const datePlugin: TValidatorPlugin = (ctx, def, value) => {
242
- // Only intercept string types tagged as dates
243
- if (def.type.kind === '' && def.type.tags.has('date')) {
244
- if (typeof value !== 'string') {
245
- ctx.error('Expected date string')
246
- return false
247
- }
248
- const parsed = Date.parse(value)
249
- if (isNaN(parsed)) {
250
- ctx.error(`Invalid date: "${value}"`)
251
- return false
252
- }
253
- return true
254
- }
255
- // Return undefined to fall through to default validation
256
- return undefined
257
- }
258
-
259
- User.validator({ plugins: [datePlugin] }).validate(data, true)
260
- ```
261
-
262
- ### Plugin Return Values
263
-
264
- | Return | Meaning |
265
- | ----------- | ----------------------------------------------------------------------------------- |
266
- | `true` | Value is accepted — skip all further validation for this node |
267
- | `false` | Value is rejected — error should be reported via `ctx.error()` before returning |
268
- | `undefined` | Plugin doesn't handle this type — fall through to next plugin or default validation |
269
-
270
- ### Plugin Example — Coerce String to Number
271
-
272
- ```ts
273
- const coercePlugin: TValidatorPlugin = (ctx, def, value) => {
274
- if (def.type.kind === '' && def.type.designType === 'number' && typeof value === 'string') {
275
- const num = Number(value)
276
- if (!isNaN(num)) {
277
- // Validate the coerced value against the full type (respects @expect.min etc.)
278
- return ctx.validateAnnotatedType(def, num)
279
- }
280
- }
281
- return undefined
282
- }
283
- ```
284
-
285
- ### Multiple Plugins
286
-
287
- Plugins run in order. The first plugin to return `true` or `false` wins — subsequent plugins and default validation are skipped for that node:
288
-
289
- ```ts
290
- User.validator({
291
- plugins: [authPlugin, datePlugin, coercePlugin],
292
- }).validate(data, true)
293
- ```