schematox 0.0.6 → 0.1.2
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/README.md +229 -263
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +32 -0
- package/dist/error.d.ts +3 -14
- package/dist/error.js +1 -12
- package/dist/index.js +16 -33
- package/dist/index.ts +21 -59
- package/dist/parse.d.ts +5 -0
- package/dist/parse.js +169 -0
- package/dist/struct.d.ts +45 -0
- package/dist/struct.js +121 -0
- package/dist/types/compounds.ts +67 -0
- package/dist/types/constructors.ts +73 -0
- package/dist/types/extensions.ts +27 -0
- package/dist/types/primitives.ts +59 -0
- package/dist/types/struct.ts +70 -0
- package/dist/utils/fp.d.ts +0 -3
- package/dist/utils/fp.js +1 -13
- package/dist/validate.d.ts +6 -0
- package/dist/validate.js +179 -0
- package/dist/verify-primitive.d.ts +3 -0
- package/dist/verify-primitive.js +65 -0
- package/package.json +5 -3
- package/dist/base-schema-parser.d.ts +0 -5
- package/dist/base-schema-parser.js +0 -141
- package/dist/base-schema-validator.d.ts +0 -5
- package/dist/base-schema-validator.js +0 -146
- package/dist/general-schema-parser.d.ts +0 -4
- package/dist/general-schema-parser.js +0 -132
- package/dist/general-schema-validator.d.ts +0 -4
- package/dist/general-schema-validator.js +0 -125
- package/dist/programmatic-schema/array.d.ts +0 -23
- package/dist/programmatic-schema/array.js +0 -40
- package/dist/programmatic-schema/boolean.d.ts +0 -57
- package/dist/programmatic-schema/boolean.js +0 -57
- package/dist/programmatic-schema/buffer.d.ts +0 -72
- package/dist/programmatic-schema/buffer.js +0 -61
- package/dist/programmatic-schema/number-union.d.ts +0 -66
- package/dist/programmatic-schema/number-union.js +0 -61
- package/dist/programmatic-schema/number.d.ts +0 -81
- package/dist/programmatic-schema/number.js +0 -71
- package/dist/programmatic-schema/object.d.ts +0 -25
- package/dist/programmatic-schema/object.js +0 -43
- package/dist/programmatic-schema/string-union.d.ts +0 -66
- package/dist/programmatic-schema/string-union.js +0 -61
- package/dist/programmatic-schema/string.d.ts +0 -81
- package/dist/programmatic-schema/string.js +0 -71
- package/dist/types/base-detailed-schema-types.ts +0 -121
- package/dist/types/compound-schema-types.ts +0 -147
- package/dist/x-closure.d.ts +0 -8
- package/dist/x-closure.js +0 -18
package/README.md
CHANGED
|
@@ -1,46 +1,68 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Schematox
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Schematox is a lightweight typesafe schema defined parser/validator. All schemas are JSON compatible.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Instead of supporting all possible JS/TS data structures, the library is focusing on fixed set of schema types: string, number, boolean, literal, object, array, union. Each schema can have parameters: optional, nullable, description. Each primitive schema has "brand" parameter as mean of making its subject type [nominal](https://github.com/Microsoft/TypeScript/wiki/FAQ#can-i-make-a-type-alias-nominal). The rest parameters is schema specific range limiters.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Check defined schema correctness using non generic type `Schema`
|
|
9
|
-
- Programmatically defined schemas supported as mean of creation statically defined schema
|
|
10
|
-
- Clear separation of concerns for validating and parsing logic:
|
|
11
|
-
- Parser is used for dealing with an outer uknnown interface
|
|
12
|
-
- Validator is used for dealing with an internal known interface
|
|
13
|
-
- Schematox uses an Ether-style error handling
|
|
14
|
-
- We offer first-class support for branded base schema primitive types
|
|
15
|
-
- We have zero dependencies. The runtime code logic is small and easy to grasp, consisting of just a couple of functions. Most of the library code is tests and types.
|
|
7
|
+
Library supports static schema definition which means your schemas could be completely independent from schematox. One could use such schemas as source for generation other structures like DB models.
|
|
16
8
|
|
|
17
|
-
|
|
9
|
+
Features:
|
|
18
10
|
|
|
19
|
-
|
|
11
|
+
- Statically defined JSON compatible schema
|
|
12
|
+
- Check defined schema correctness using non generic type "Schema"
|
|
13
|
+
- Programmatically defined schema (struct)
|
|
14
|
+
- Schema subject verification methods:
|
|
15
|
+
- parse: constructs new object based on the given schema and subject
|
|
16
|
+
- validate: checks and returns reference to the original schema subject
|
|
17
|
+
- guard: validates and narrows schema subject type in the current scope
|
|
18
|
+
- Ether-style error handling (no unexpected throws)
|
|
19
|
+
- First-class support for branded primitives (primitive nominal types alias)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Check out [github issues](https://github.com/incerta/schematox/issues) to know what we are planning to support soon.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Currently we on version 0. The public API is mostly defined however few thing left before the first major release:
|
|
24
24
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
25
|
+
- Record and tuple schema support
|
|
26
|
+
- Allow parser to replace value before it's validated (similar to [coercing](https://docs.superstructjs.org/guides/03-coercing-data) concept)
|
|
27
|
+
- Clearly defined supported versions of typescript/node
|
|
28
|
+
- Support "deno" runtime and publish package on "deno.land"
|
|
29
|
+
- Have a benchmark that compares library performance with other parsers
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
The library is small so exploring README.md is enough for understanding its API, checkout limitations/examples and you good to go:
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
- [Install](#install)
|
|
34
|
+
- [Limitations](#limitations)
|
|
35
|
+
- [Static schema example](#static-schema-example)
|
|
36
|
+
- [Programmatic schema example](#programmatic-schema-example)
|
|
37
|
+
- [Example for all supported schema types](#example-for-all-supported-schema-types)
|
|
38
|
+
- [String](#string)
|
|
39
|
+
- [Number](#number)
|
|
40
|
+
- [Boolean](#boolean)
|
|
41
|
+
- [Literal](#literal)
|
|
42
|
+
- [Object](#object)
|
|
43
|
+
- [Array](#array)
|
|
44
|
+
- [Union](#union)
|
|
45
|
+
- [Schema parameters](#schema-parameters)
|
|
46
|
+
- [Error shape](#error-shape)
|
|
47
|
+
|
|
48
|
+
## Install
|
|
34
49
|
|
|
35
50
|
```sh
|
|
36
51
|
npm install schematox
|
|
37
52
|
```
|
|
38
53
|
|
|
39
|
-
##
|
|
54
|
+
## Limitations
|
|
55
|
+
|
|
56
|
+
Currently we support max 7 layers of depth for compound schema type: object, array, union.
|
|
57
|
+
|
|
58
|
+
Because of this we can check structural correctness of the statically defined schema using non generic type `Schema`. Important detail is that `union` schema type is also compound type so each nested definition counts as +1 layer of depth.
|
|
59
|
+
|
|
60
|
+
## Static schema example
|
|
40
61
|
|
|
41
62
|
Statically defined schema:
|
|
42
63
|
|
|
43
64
|
```typescript
|
|
65
|
+
import { parse, validate, guard } from 'schematox'
|
|
44
66
|
import type { Schema } from 'schematox'
|
|
45
67
|
|
|
46
68
|
export const userSchema = {
|
|
@@ -50,266 +72,283 @@ export const userSchema = {
|
|
|
50
72
|
type: 'string',
|
|
51
73
|
brand: ['idFor', 'User'],
|
|
52
74
|
},
|
|
53
|
-
|
|
54
|
-
type: 'string',
|
|
55
|
-
optional: true,
|
|
56
|
-
brand: ['format', 'email'],
|
|
57
|
-
},
|
|
58
|
-
updatedAt: {
|
|
59
|
-
type: 'number',
|
|
60
|
-
brand: ['format', 'msUnixTimestamp'],
|
|
61
|
-
},
|
|
62
|
-
canRead: {
|
|
63
|
-
type: 'array',
|
|
64
|
-
of: {
|
|
65
|
-
type: 'string',
|
|
66
|
-
brand: ['idFor', 'User'],
|
|
67
|
-
},
|
|
68
|
-
minLength: 1,
|
|
69
|
-
},
|
|
70
|
-
canWrite: {
|
|
71
|
-
type: 'array',
|
|
72
|
-
of: {
|
|
73
|
-
type: 'string',
|
|
74
|
-
brand: ['idFor', 'User'],
|
|
75
|
-
},
|
|
76
|
-
minLength: 1,
|
|
77
|
-
},
|
|
78
|
-
bio: { type: 'string', optional: true },
|
|
75
|
+
name: { type: 'string' },
|
|
79
76
|
},
|
|
80
77
|
} as const satisfies Schema
|
|
81
|
-
```
|
|
82
78
|
|
|
83
|
-
|
|
79
|
+
const subject = {
|
|
80
|
+
id: '1' as SubjectType<typeof userSchema.id>,
|
|
81
|
+
name: 'John',
|
|
82
|
+
} as unknown
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
import { object, string, number, boolean, array } from 'schematox'
|
|
87
|
-
|
|
88
|
-
const userIdX = string().brand('idFor', 'User')
|
|
89
|
-
const emailX = string().brand('format', 'email')
|
|
90
|
-
const msUnixTimestampX = number().brand('format', 'msUnixTimestamp')
|
|
91
|
-
|
|
92
|
-
export const userSchema = object({
|
|
93
|
-
id: userIdX,
|
|
94
|
-
email: emailX,
|
|
95
|
-
updatedAt: updatedAtX,
|
|
96
|
-
canRead: array(userId),
|
|
97
|
-
canWrite: array(userId),
|
|
98
|
-
bio: string().optional(true),
|
|
99
|
-
})
|
|
100
|
-
```
|
|
84
|
+
const parsed = parse(userSchema, subject)
|
|
101
85
|
|
|
102
|
-
|
|
86
|
+
if (parsed.error) {
|
|
87
|
+
throw Error('Not expected')
|
|
88
|
+
}
|
|
103
89
|
|
|
104
|
-
|
|
105
|
-
import { parse, validate, x } from 'schematox'
|
|
106
|
-
import { userSchema, userId, emailX, msUnixTimestampX } './schema'
|
|
90
|
+
console.log(parsed.data) // { id: '1', name: 'John' }
|
|
107
91
|
|
|
108
|
-
|
|
92
|
+
const validated = validate(userSchema, subject)
|
|
109
93
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
94
|
+
if (validated.error) {
|
|
95
|
+
throw Error('Not expected')
|
|
96
|
+
}
|
|
113
97
|
|
|
114
|
-
|
|
98
|
+
console.log(validated.data) // { id: '1', name: 'John' }
|
|
115
99
|
|
|
116
|
-
|
|
117
|
-
id:
|
|
118
|
-
|
|
119
|
-
updatedAt: Date.now() as MsUnixTimestamp,
|
|
120
|
-
canRead: [userId],
|
|
121
|
-
canWrite: [userId],
|
|
122
|
-
bio: undefined,
|
|
100
|
+
if (guard(userSchema, subject)) {
|
|
101
|
+
// { id: string & { __idFor: 'User' }; name: string }
|
|
102
|
+
subject
|
|
123
103
|
}
|
|
104
|
+
```
|
|
124
105
|
|
|
125
|
-
|
|
106
|
+
## Programmatic schema example
|
|
126
107
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
108
|
+
Same schema but defined programmatically:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { object, string } from 'schematox'
|
|
112
|
+
import type { SubjectType } from 'schematox'
|
|
113
|
+
|
|
114
|
+
const struct = object({
|
|
115
|
+
id: string().brand('idFor', 'User'),
|
|
116
|
+
name: string(),
|
|
117
|
+
})
|
|
131
118
|
|
|
132
|
-
|
|
119
|
+
const subject = { id: '1', name: 'John' } as unknown
|
|
133
120
|
|
|
134
|
-
const
|
|
121
|
+
const parsed = struct.parse(subject)
|
|
135
122
|
|
|
136
|
-
// We need a type guard before accessing the validated.data
|
|
137
123
|
if (parsed.error) {
|
|
138
124
|
throw Error('Not expected')
|
|
139
125
|
}
|
|
140
126
|
|
|
141
|
-
console.log(
|
|
127
|
+
console.log(parsed.data) // { id: '1', name: 'John' }
|
|
142
128
|
|
|
143
|
-
|
|
129
|
+
const validated = struct.validate(subject)
|
|
144
130
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const validatedByX = userSchemaX.validate(schemaSubject)
|
|
149
|
-
```
|
|
131
|
+
if (validated.error) {
|
|
132
|
+
throw Error('Not expected')
|
|
133
|
+
}
|
|
150
134
|
|
|
151
|
-
|
|
135
|
+
console.log(validated.data) // { id: '1', name: 'John' }
|
|
152
136
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
email: string & { __format: 'email' }
|
|
157
|
-
updatedAt: number & { __format: 'msUnixTimestamp' }
|
|
158
|
-
canRead: Array<string & { __idFor: 'User' }>
|
|
159
|
-
canWrite: Array<string & { __idFor: 'User' }>
|
|
137
|
+
if (struct.guard(subject)) {
|
|
138
|
+
// { id: string & { __idFor: 'User' }; name: string }
|
|
139
|
+
subject
|
|
160
140
|
}
|
|
161
141
|
```
|
|
162
142
|
|
|
163
|
-
|
|
143
|
+
All programmatically defined schemas are the same as static, one just needs to access it through `__schema` key. We can mix static/programmatic schemas either accessing it through `__schema` or wrap it by `{ __schema: T }` if consumer is programmatic schema.
|
|
164
144
|
|
|
165
|
-
|
|
166
|
-
import {
|
|
167
|
-
array,
|
|
168
|
-
object,
|
|
169
|
-
string,
|
|
170
|
-
number,
|
|
171
|
-
boolean,
|
|
172
|
-
stringUnion,
|
|
173
|
-
numberUnion,
|
|
174
|
-
} from 'schematox'
|
|
145
|
+
## Example for all supported schema types
|
|
175
146
|
|
|
176
|
-
|
|
147
|
+
We distinguish two main categories of schema units:
|
|
148
|
+
|
|
149
|
+
- primitive: string, number, boolean, literal
|
|
150
|
+
- compound: object, array, union
|
|
177
151
|
|
|
178
|
-
|
|
152
|
+
Any schema share optional/nullable/description parameters. Any compound schema could have any other schema type as its member including itself. Any primitive schema can have "brand" parameter.
|
|
179
153
|
|
|
180
|
-
|
|
154
|
+
### String
|
|
181
155
|
|
|
182
|
-
|
|
156
|
+
```typescript
|
|
157
|
+
const schema = {
|
|
183
158
|
type: 'string',
|
|
184
159
|
optional: true,
|
|
185
|
-
|
|
160
|
+
nullable: true,
|
|
186
161
|
brand: ['x', 'y'],
|
|
187
162
|
minLength: 1,
|
|
188
|
-
maxLength:
|
|
163
|
+
maxLength: 2,
|
|
189
164
|
description: 'x',
|
|
190
165
|
} as const satisfies Schema
|
|
191
166
|
|
|
192
|
-
const
|
|
167
|
+
const struct = string()
|
|
193
168
|
.optional()
|
|
194
|
-
.
|
|
195
|
-
.minLength(1)
|
|
196
|
-
.maxLength(1)
|
|
169
|
+
.nullable()
|
|
197
170
|
.brand('x', 'y')
|
|
198
|
-
.
|
|
171
|
+
.minLength(1)
|
|
172
|
+
.maxLength(2)
|
|
173
|
+
.description('x')
|
|
199
174
|
|
|
200
|
-
//
|
|
175
|
+
// (string & { __x: 'y' }) | undefined | null
|
|
176
|
+
type FromSchema = SubjectType<typeof schema>
|
|
177
|
+
type FromStruct = SubjectType<typeof struct>
|
|
178
|
+
```
|
|
201
179
|
|
|
202
|
-
|
|
180
|
+
### Number
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const schema = {
|
|
203
184
|
type: 'number',
|
|
204
185
|
optional: true,
|
|
205
|
-
|
|
186
|
+
nullable: true,
|
|
206
187
|
brand: ['x', 'y'],
|
|
207
188
|
min: 1,
|
|
208
|
-
max:
|
|
189
|
+
max: 2,
|
|
209
190
|
description: 'x',
|
|
210
191
|
} as const satisfies Schema
|
|
211
192
|
|
|
212
|
-
const
|
|
193
|
+
const struct = number()
|
|
213
194
|
.optional()
|
|
214
|
-
.
|
|
215
|
-
.min(1)
|
|
216
|
-
.max(1)
|
|
195
|
+
.nullable()
|
|
217
196
|
.brand('x', 'y')
|
|
218
|
-
.
|
|
197
|
+
.min(1)
|
|
198
|
+
.max(2)
|
|
199
|
+
.description('x')
|
|
200
|
+
|
|
201
|
+
// (number & { __x: 'y' }) | undefined | null
|
|
202
|
+
type FromSchema = SubjectType<typeof schema>
|
|
203
|
+
type FromStruct = SubjectType<typeof struct>
|
|
204
|
+
//
|
|
205
|
+
```
|
|
219
206
|
|
|
220
|
-
|
|
207
|
+
### Boolean
|
|
221
208
|
|
|
222
|
-
|
|
209
|
+
```typescript
|
|
210
|
+
const schema = {
|
|
223
211
|
type: 'boolean',
|
|
224
212
|
optional: true,
|
|
225
|
-
|
|
213
|
+
nullable: true,
|
|
226
214
|
brand: ['x', 'y'],
|
|
227
215
|
description: 'x',
|
|
228
216
|
} as const satisfies Schema
|
|
229
217
|
|
|
230
|
-
const
|
|
218
|
+
const struct = boolean() //
|
|
231
219
|
.optional()
|
|
232
|
-
.
|
|
220
|
+
.nullable()
|
|
233
221
|
.brand('x', 'y')
|
|
234
|
-
.description('
|
|
222
|
+
.description('x')
|
|
235
223
|
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
of: ['x', 'y', 'z'],
|
|
241
|
-
optional: true,
|
|
242
|
-
brand: ['x', 'y'],
|
|
243
|
-
description: 'x',
|
|
244
|
-
} as const satisfies Schema
|
|
224
|
+
// (boolean & { __x: 'y' }) | undefined | null
|
|
225
|
+
type FromSchema = SubjectType<typeof schema>
|
|
226
|
+
type FromStruct = SubjectType<typeof struct>
|
|
227
|
+
```
|
|
245
228
|
|
|
246
|
-
|
|
247
|
-
.optional()
|
|
248
|
-
.brand('x', 'y')
|
|
249
|
-
.description('y')
|
|
229
|
+
### Literal
|
|
250
230
|
|
|
251
|
-
|
|
231
|
+
Could be string/number/boolean literal
|
|
252
232
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
233
|
+
```typescript
|
|
234
|
+
const schema = {
|
|
235
|
+
type: 'literal',
|
|
236
|
+
of: 'x',
|
|
256
237
|
optional: true,
|
|
238
|
+
nullable: true,
|
|
257
239
|
brand: ['x', 'y'],
|
|
258
240
|
description: 'x',
|
|
259
241
|
} as const satisfies Schema
|
|
260
242
|
|
|
261
|
-
const
|
|
243
|
+
const struct = literal('x') //
|
|
262
244
|
.optional()
|
|
245
|
+
.nullable()
|
|
263
246
|
.brand('x', 'y')
|
|
264
|
-
.description('
|
|
247
|
+
.description('x')
|
|
265
248
|
|
|
266
|
-
|
|
249
|
+
// ('x' & { __x: 'y' }) | undefined | null
|
|
250
|
+
type FromSchema = SubjectType<typeof schema>
|
|
251
|
+
type FromStruct = SubjectType<typeof struct>
|
|
252
|
+
```
|
|
267
253
|
|
|
268
|
-
|
|
254
|
+
### Object
|
|
269
255
|
|
|
270
|
-
|
|
256
|
+
```typescript
|
|
257
|
+
const schema = {
|
|
271
258
|
type: 'object',
|
|
272
259
|
of: {
|
|
273
|
-
x: 'string',
|
|
274
|
-
y: 'number
|
|
260
|
+
x: { type: 'string' },
|
|
261
|
+
y: { type: 'number' },
|
|
275
262
|
},
|
|
263
|
+
optional: true,
|
|
264
|
+
nullable: true,
|
|
265
|
+
description: 'x',
|
|
276
266
|
} as const satisfies Schema
|
|
277
267
|
|
|
278
|
-
const
|
|
268
|
+
const struct = object({
|
|
279
269
|
x: string(),
|
|
280
|
-
y: number()
|
|
270
|
+
y: number(),
|
|
281
271
|
})
|
|
272
|
+
.optional()
|
|
273
|
+
.nullable()
|
|
274
|
+
.description('x')
|
|
282
275
|
|
|
283
|
-
//
|
|
276
|
+
// { x: string; y: number } | undefined | null
|
|
277
|
+
type FromSchema = SubjectType<typeof schema>
|
|
278
|
+
type FromStruct = SubjectType<typeof struct>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Array
|
|
284
282
|
|
|
285
|
-
|
|
283
|
+
```typescript
|
|
284
|
+
const schema = {
|
|
286
285
|
type: 'array',
|
|
287
|
-
of: 'string',
|
|
286
|
+
of: { type: 'string' },
|
|
287
|
+
optional: true,
|
|
288
|
+
minLength: 1,
|
|
289
|
+
maxLength: 1000,
|
|
290
|
+
description: 'x',
|
|
288
291
|
} as const satisfies Schema
|
|
289
292
|
|
|
290
|
-
const
|
|
293
|
+
const struct = array(string())
|
|
294
|
+
.optional()
|
|
295
|
+
.nullable()
|
|
296
|
+
.minLength(1)
|
|
297
|
+
.maxLength(1000)
|
|
298
|
+
.description('x')
|
|
299
|
+
|
|
300
|
+
// string[] | undefined | null
|
|
301
|
+
type FromSchema = SubjectType<typeof schema>
|
|
302
|
+
type FromStruct = SubjectType<typeof struct>
|
|
291
303
|
```
|
|
292
304
|
|
|
293
|
-
|
|
305
|
+
### Union
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
const schema = {
|
|
309
|
+
type: 'union',
|
|
310
|
+
of: [{ type: 'string' }, { type: 'number' }],
|
|
311
|
+
optional: true,
|
|
312
|
+
nullable: true,
|
|
313
|
+
description: 'x',
|
|
314
|
+
} as const satisfies Schema
|
|
315
|
+
|
|
316
|
+
const struct = union([string(), number()])
|
|
317
|
+
.optional()
|
|
318
|
+
.nullable()
|
|
319
|
+
.description('x')
|
|
320
|
+
|
|
321
|
+
// string | number | undefined | null
|
|
322
|
+
type FromSchema = SubjectType<typeof schema>
|
|
323
|
+
type FromStruct = SubjectType<typeof struct>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Schema parameters
|
|
327
|
+
|
|
328
|
+
- `optional?: boolean` – does `undefined` is valid value
|
|
329
|
+
- `nullable?: boolean` – does `null` is valid value
|
|
330
|
+
- `brand?: [string, string]` – make primitive type nominal "['idFor', 'User'] -> T & { \_\_idFor: 'User' }"
|
|
331
|
+
- `minLength/maxLength/min/max` – schema type dependent limiting characteristics
|
|
332
|
+
- `description?: string` – description of the particular schema property which can be used to provide more detailed information for the user/developer on validation/parse error
|
|
333
|
+
|
|
334
|
+
## Error shape
|
|
294
335
|
|
|
295
336
|
Nested schema example. Subject `0` is invalid, should be a `string`:
|
|
296
337
|
|
|
297
338
|
```typescript
|
|
298
|
-
import {
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
object({
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
const result = schemaX.parse({ x: { y: [{ z: 0 }] } })
|
|
339
|
+
import { object, array, string } from 'schematox'
|
|
340
|
+
|
|
341
|
+
const struct = object({
|
|
342
|
+
x: object({
|
|
343
|
+
y: array(
|
|
344
|
+
object({
|
|
345
|
+
z: string(),
|
|
346
|
+
})
|
|
347
|
+
),
|
|
348
|
+
}),
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
const result = struct.parse({ x: { y: [{ z: 0 }] } })
|
|
313
352
|
```
|
|
314
353
|
|
|
315
354
|
The `result.error` shape is:
|
|
@@ -331,76 +370,3 @@ It's always an array with at least one entry. Each entry includes:
|
|
|
331
370
|
- `schema`: The specific section of `schema` where the invalid value is found.
|
|
332
371
|
- `subject`: The specific part of the validated subject where the invalid value exists.
|
|
333
372
|
- `path`: Traces the route from the root to the error subject, with strings as keys and numbers as array indexes.
|
|
334
|
-
|
|
335
|
-
## Parse and validate differences
|
|
336
|
-
|
|
337
|
-
The parser returns `data` as new object/primitive without references to the parsed subject. Parser manages the `null` value as `undefined` and subsequently replaces it with `undefined`. It also swaps `optional` values with the `default` value from schema. One can infer schema parsed subject type by using `XParsed<typeof schema>` generic.
|
|
338
|
-
|
|
339
|
-
The validator on the other hand returns the evaluated subject itself and not applying any mutation/transformation to it. The validator should be used in exceptional cases when we known subject type but not sure that it actually correct. One can infer schema validated subject type by using `XValidated<typeof schema>` generic.
|
|
340
|
-
|
|
341
|
-
So the difference between `XParsed` and `XValidated` is just about handling `default` schema value. `XParsed` narrows optional schema subject type with default value in the way that it will not be optional, because optional `undefined` and `null` values will be replaced by the `default`. `XValidated` just ignores `default` schema value.
|
|
342
|
-
|
|
343
|
-
Examples:
|
|
344
|
-
|
|
345
|
-
```typescript
|
|
346
|
-
const optionalStrX = x({ type: 'string', optional: true } as const)
|
|
347
|
-
|
|
348
|
-
/* Parser doesn't check subject type */
|
|
349
|
-
|
|
350
|
-
expect(optionalStrX.parse(0).error).toStrictEqual([
|
|
351
|
-
{
|
|
352
|
-
code: 'INVALID_TYPE',
|
|
353
|
-
schema: { type: 'string', optional: true },
|
|
354
|
-
subject: 0,
|
|
355
|
-
path: [],
|
|
356
|
-
},
|
|
357
|
-
])
|
|
358
|
-
|
|
359
|
-
/* Validator does */
|
|
360
|
-
|
|
361
|
-
// @ts-expect-error 'number' is not 'string'
|
|
362
|
-
expect(optionalStrX.validate(0).error).toStrictEqual([
|
|
363
|
-
{
|
|
364
|
-
code: 'INVALID_TYPE',
|
|
365
|
-
schema: { type: 'string', optional: true },
|
|
366
|
-
subject: 0,
|
|
367
|
-
path: [],
|
|
368
|
-
},
|
|
369
|
-
])
|
|
370
|
-
|
|
371
|
-
/* Parser treats `null` as `undefined` */
|
|
372
|
-
|
|
373
|
-
expect(optionalStrX.parse(null).data).toBe(undefined)
|
|
374
|
-
expect(optionalStrX.parse(null).error).toBe(undefined)
|
|
375
|
-
|
|
376
|
-
/* Validator is not */
|
|
377
|
-
|
|
378
|
-
// @ts-expect-error 'null' is not 'string | undefined'
|
|
379
|
-
expect(optionalStrX.validate(null).error).toStrictEqual([
|
|
380
|
-
{
|
|
381
|
-
code: 'INVALID_TYPE',
|
|
382
|
-
schema: { type: 'string', optional: true },
|
|
383
|
-
subject: null,
|
|
384
|
-
path: [],
|
|
385
|
-
},
|
|
386
|
-
])
|
|
387
|
-
|
|
388
|
-
const defaultedStrX = x({ type: 'string', optional: true, default: 'y' })
|
|
389
|
-
|
|
390
|
-
/* Parser uses `default` value on `null` or `undefined` subject */
|
|
391
|
-
|
|
392
|
-
expect(defaultedStrX.parse(null).data).toBe('y')
|
|
393
|
-
expect(defaultedStrX.parse(undefined).data).toBe('y')
|
|
394
|
-
|
|
395
|
-
/* Validator ignores default value */
|
|
396
|
-
|
|
397
|
-
expect(defaultedStrX.validate(undefined).data).toBe(undefined)
|
|
398
|
-
|
|
399
|
-
/* Parser returning new object */
|
|
400
|
-
|
|
401
|
-
expect(arrX.parse(subject).data === subject).toBe(false)
|
|
402
|
-
|
|
403
|
-
/* Validator keeps the subject reference */
|
|
404
|
-
|
|
405
|
-
expect(arrX.validate(subject).data === subject).toBe(true)
|
|
406
|
-
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const PARAMS_BY_SCHEMA_TYPE: {
|
|
2
|
+
readonly string: Set<"optional" | "nullable" | "brand" | "description" | "minLength" | "maxLength">;
|
|
3
|
+
readonly number: Set<"optional" | "nullable" | "brand" | "description" | "min" | "max">;
|
|
4
|
+
readonly boolean: Set<"optional" | "nullable" | "brand" | "description">;
|
|
5
|
+
readonly literal: Set<"optional" | "nullable" | "brand" | "description">;
|
|
6
|
+
readonly object: Set<"optional" | "nullable" | "description">;
|
|
7
|
+
readonly array: Set<"optional" | "nullable" | "description" | "minLength" | "maxLength">;
|
|
8
|
+
readonly union: Set<string>;
|
|
9
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PARAMS_BY_SCHEMA_TYPE = void 0;
|
|
4
|
+
exports.PARAMS_BY_SCHEMA_TYPE = {
|
|
5
|
+
string: new Set([
|
|
6
|
+
'optional',
|
|
7
|
+
'nullable',
|
|
8
|
+
'brand',
|
|
9
|
+
'description',
|
|
10
|
+
'minLength',
|
|
11
|
+
'maxLength',
|
|
12
|
+
]),
|
|
13
|
+
number: new Set([
|
|
14
|
+
'optional',
|
|
15
|
+
'nullable',
|
|
16
|
+
'brand',
|
|
17
|
+
'description',
|
|
18
|
+
'min',
|
|
19
|
+
'max',
|
|
20
|
+
]),
|
|
21
|
+
boolean: new Set(['optional', 'nullable', 'brand', 'description']),
|
|
22
|
+
literal: new Set(['optional', 'nullable', 'brand', 'description']),
|
|
23
|
+
object: new Set(['optional', 'nullable', 'description']),
|
|
24
|
+
array: new Set([
|
|
25
|
+
'optional',
|
|
26
|
+
'nullable',
|
|
27
|
+
'description',
|
|
28
|
+
'minLength',
|
|
29
|
+
'maxLength',
|
|
30
|
+
]),
|
|
31
|
+
union: new Set(['optional', 'nullable', 'description']),
|
|
32
|
+
};
|